2024-01-27
Vue
0
请注意,本文编写于 516 天前,最后修改于 485 天前,其中某些信息可能已经过时。

目录

创建 Vue3 项目
Vue2 写法示例
组合式API与选项式API
Options API
Composition API
setup
拉开序幕
setup的返回值
setup语法糖
响应式数据
基本类型的响应式数据
对象类型的响应式数据
ref 与 reactive 对比
toRefs 与 toRef
计算属性
监视
监视 ref 定义的基本类型数据
监视 ref 定义的对象类型数据
监视 reactive 定义的数据
监视 ref 或 reactive 定义的对象中的<span style="color: skyblue">某个属性</span>
监视上述多个数据
watchEffect
标签的 ref 属性
布局样式
props

渐进式
JavaScript 框架

易学易用,性能出色,适用场景丰富的 Web 前端框架。

创建 Vue3 项目

bash
# 使用vite直接创建项目,配置项较少,可选项目类型 npm create vite
bash
# 使用vue创建项目,提供较多配置项 npm create vue@latest

Vue 官方 VS Code 插件:

main.ts 中的 createApp(App).mount('#app'),vue的 createApp() 方法将根组件 App 挂载到 #app 的div上

Vue的三个标签(Vue2中不能有多个根标签):

vue
<template> <div class="app"> <h1>Hello</h1> </div> </template> <script lang="ts"> export default { name: 'App' } </script> <style> .app { background-color: #ddd; box-shadow: 0 0 10px; border-radius: 6px; padding: 20px; } </style>

Vue2 写法示例

vue
<template> <div class="person"> <h2>姓名:{{ name }}</h2> <h2>年龄:{{ age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="showTel">查看联系方式</button> </div> </template> <script lang="ts"> export default { name: 'Person', data() { return { name: '张三', age: 18, tel: '1888888888' } }, methods: { changeName() { this.name = 'ZhangSan' }, changeAge() { this.age += 1 }, showTel() { alert(this.tel) } } } </script> <style scoped> .person { background-color: skyblue; box-shadow: 0 0 10px; border-radius: 6px; padding: 20px; } button { margin: 2px; } </style>

组合式API与选项式API

Vue2:选项式API(OptionsAPI

Vue3:组合式API(CompositionAPI

Options API

Options类型的 API,数据、方法、计算属性等,是分散在:datamethodscomputed中的,若想新增或者修改一个需求,就需要分别修改:datamethodscomputed,不便于维护和复用。

1.gif

2.gif

Composition API

可以用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

3.gif

4.gif

动图原创作者:大帅老猿

setup

拉开序幕

setupVue3 中的一个新的配置项,其值为一个函数,组件中所用到的:数据、方法、计算属性、监视等,均配置在 setup

  • setup 函数返回的对象中的内容,可直接在模板中使用
  • setup 中访问 thisundefined
  • setupbeforeCreate 之前

setup 可以和选项式API同时存在,选项式API可以访问到 setup 中的属性和方法,反之不可

vue
<script lang="ts"> export default { name: 'Person', // setup 中的 this 是undefined setup() { // 数据 -> 此时数据不是响应式 let name = '张三' let age = 18 let tel = '188888' // 方法 function changeName() { name = 'zhangsan' // name值变化,页面不变 } const changeAge = () => { age += 1 } const showTel = () => { alert(tel) } return {name, age, changeName, changeAge, showTel} } } </script>

setup的返回值

  • 对象:渲染到模板的对象和方法等
  • 函数:自定义渲染内容,类似于react函数组件用return来指定渲染内容

setup语法糖

vue
<script lang="ts"> export default { name: 'Person', } </script> <script setup lang="ts"> let name = '张三' let age = 18 let tel = '188888' function changeName() { name = 'zhangsan' } const changeAge = () => { age += 1 } const showTel = () => { alert(tel) } </script>

响应式数据

基本类型的响应式数据

Vue3 中的 ref 不同于 Vue2 中的 refVue3 中,ref 是一个函数,从 vue 中引用,const age = ref(18),返回一个 RefImpl 的实例对象,简称 ref对象refref 对象的 value 属性是响应式的

  • JS中操作数据需要:xxx.value,模板中不需要.value,直接使用
  • 对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的
vue
<script setup lang="ts"> import { ref } from 'vue' let name = ref('张三') const age = ref(18) let tel = '188888' // 方法 function changeName() { name.value = 'zhangsan' } const changeAge = () => { age.value += 1 } const showTel = () => { alert(tel) } </script>

对象类型的响应式数据

reactive 定义对象类型的响应式数据,不能用于定义基本类型,但 ref 也可用于定义对象类型,其内部也是调用了 reactive

let 响应式对象= reactive(源对象),返回一个 Proxy 的实例对象(简称:响应式对象),定义的响应式数据是“深层次”的

vue
<script setup lang="ts"> import { reactive } from 'vue'; let car = reactive({ brand: 'BMW M3 GTR', price: 100 }) let games = reactive([ {id: 'asjelf1', name: 'Titanfall 2'}, {id: 'asjelf2', name: 'NFS Heat'}, {id: 'asjelf3', name: 'Brotato'}, ]) console.log('car', car) const changeCar = () => { car.price += 10 car.brand = 'RSR' } const changeGame = () => { games[0].name = 'Titanfall 3? 不可能辣' } </script>

refreactive 对比

宏观角度:

  1. ref用来定义:基本类型数据对象类型数据
  2. reactive用来定义:对象类型数据

区别:

  1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value
  2. reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)

推荐使用原则:

  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,refreactive都可以
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive

toRefstoRef

toRefs 用于在解构 reactive 定义的响应式对象时取得 ref 响应式数据,即:将 reactive 定义的响应式对象变为由 ref 响应式数据组成的对象

toRef 则是取出其中的某一组key-value

vue
<script setup lang="ts"> import { reactive, toRefs } from 'vue' let person = reactive({ name: '张三', age: 18 }) let { name, age } = toRefs(person) console.log('toRefs(person)', toRefs(person)) let function changeName() { name.value += '~' } function changeAge() { age.value += 1 } </script>

image.png

计算属性

单向绑定

vue
<input type="text" :value="lastName">

双向绑定

vue
<input type="text" v-model="firstName">

计算属性有缓存,重复使用时不会重复调用其中的方法

vue
<template> <div class="person"> 姓:<input type="text" v-model="firstName"> <br> 名:<input type="text" v-model="lastName"> <br> <button @click="changeFullName">将全名改为lisi</button> <br> 全名:<span>{{fullName}}</span><br> 全名:<span>{{fullName}}</span><br> 全名:<span>{{fullName}}</span><br> </div> </template> <script lang="ts"> export default { name: 'Computed', // setup 中的 this 是undefined } </script> <script setup lang="ts"> import { ref, computed } from 'vue' let firstName = ref('zhang') let lastName = ref('san') // fullName 是计算属性,且是只读的 // let fullName = computed(() => { // return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value // }) // fullName 是计算属性,且是可读可写的 let fullName = computed({ get() { return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value }, set(changedValue) { const [fN, lN] = changedValue.split('-') firstName.value = fN lastName.value = lN } }) const changeFullName = () => { fullName.value = 'li-si' } </script> <style scoped> .person { background-color: pink; box-shadow: 0 0 10px; border-radius: 6px; padding: 20px; } button { margin: 2px; } </style>

GIF 2024-1-28 22-24-48.gif

监视

watch 监视数据的变化(与 Vue2 中作用一致)

Vue3 中的 watch 只能监视以下四种数据:

  1. 一个函数,返回一个值
  2. 一个 ref
  3. 一个 reactive 响应式对象
  4. ...或是由以上类型的值组成的数组

监视 ref 定义的基本类型数据

直接写数据名,监视的是其 value 值的改变

vue
<template> <div class="sum"> <h1>监视 ref 类型数据</h1> <h2>当前求和为:{{ sum }}</h2> <button @click="add">点击sum+1</button> </div> </template> <script setup lang="ts"> import { ref, watch } from 'vue'; let sum = ref(0); const add = () => { sum.value += 1; }; const stopWatch = watch(sum, (newValue, oldValue) => { console.log(`sum由${oldValue}变为${newValue}`); if (newValue >= 10) { // 停止监视 stopWatch(); } }) </script>

监视 ref 定义的对象类型数据

默认情况下不传第三个参数,监视的是对象的地址值,若要监视对象内部属性的变化,需要手动开启深度监视

vue
<template> <div class="sum"> <h1>监视 ref 对象类型数据</h1> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="changePerson">修改此人</button> </div> </template> <script lang="ts"> export default { name: 'Sum' } </script> <script setup lang="ts"> import { ref, watch } from 'vue'; let person = ref({ name: '张三', age: 18 }); function changeName() { person.value.name += '~'; } function changeAge() { person.value.age += 1; } function changePerson() { person.value = { name: '李四', age: 50 }; } // 第三个参数为配置项:deep开启深度监视,immediate在初始时立即监视 watch(person, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); }, { deep: true }) </script>

注意

由于 watch 监视的是对象的地址值,因此,如果直接修改整个 ref 定义的对象,则 newValue 为修改后的值,oldValue 为修改前的值;而如果仅修改对象中的属性值,对象在内存中的地址没有发生变化,因此 newValueoldValue 都为修改后的值。

1.gif

Vue 3 官方文档:

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器
  • flush:调整回调函数的刷新时机。参考回调的刷新时机watchEffect()
  • onTrack / onTrigger:调试侦听器的依赖。参考调试侦听器
  • once: 回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。

监视 reactive 定义的数据

隐式创建深层监听,{deep: false} 不可用

vue
<script setup lang="ts"> import { reactive, watch } from 'vue'; let person = reactive({ name: '张三', age: 18 }); function changeName() { person.name += '~'; } function changeAge() { person.age += 1; } function changePerson() { // person的地址没有变化,因此 newValue 和 oldValue 仍然相同 Object.assign(person, { name: '李四', age: 50 }); } watch(person, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); }) </script>

监视 refreactive 定义的对象中的某个属性

  • 若该属性值不是对象类型,需要 getter 函数
  • 若该属性值是对象类型,建议写函数
vue
<script setup lang="ts"> import { reactive, watch } from 'vue'; let person = reactive({ name: '张三', age: 18, car: { brand: '奔驰', price: 100 } }); function changeName() { person.name += '~'; } function changeAge() { person.age += 1; } function changePerson() { // person的地址没有变化,因此 newValue 和 oldValue 仍然相同 Object.assign(person, { name: '李四', age: 50 }); } function changeBrand() { person.car.brand = '保时捷' } function changePrice() { person.car.price = 150 } function changeCar() { person.car = { brand: '宝马', price: 160 } } /*watch(() => person.name, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); }, {deep: true})*/ watch(() => person.car, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); }, {deep: true}) </script>

有趣的现象

js
watch(person.car, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); })

修改 car 的属性会监视到,而直接修改 car 却监视不到

js
watch(() => person.car, (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); })

修改 car 的属性不会监视,而直接修改 car 能够监视到,此时需要开启 deep: true 才能监视到 car 属性的变化(无论是否为 reactive),因为 personreactive 定义的对象,而 person.car 不是

监视上述多个数据

js
watch([() => person.name, () => person.car.brand], (newValue, oldValue) => { console.log('Person变化', newValue, oldValue); }, {deep: true})

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • watch 要明确指出监视的数据
  • watchEffect 不需要明确指出监视的数据
  • 二者都能监听响应式数据的变化,监听数据变化的方式不同
vue
<template> <div class="sum"> <h2>需求:当护甲低于40或kd大于5时,打印</h2> <h2>当前护甲:{{ armor }}</h2> <h2>当前kd:{{ kd }}</h2> <button @click="changeArmor">armor</button> <button @click="changeKd">kd</button> </div> </template> <script lang="ts"> export default { name: 'Sum' } </script> <script setup lang="ts"> import { ref, watch, watchEffect } from 'vue'; let armor = ref(100); let kd = ref(0); function changeArmor() { armor.value -= 10; } function changeKd() { kd.value += 1; } // watch 实现 /*watch([armor, kd], (value) => { let [newArmor, newKd] = value; if (newArmor <= 40 || newKd >= 5) { console.log('打印'); } })*/ // watchEffect 实现 watchEffect(() => { if (armor.value < 40 || kd.value > 5) { console.log('打印'); } }) </script>

标签的 ref 属性

用于注册模板引用

ref 作用于普通 html 标签上,拿到的是 DOM 元素;作用于 Vue 组件上,拿到的是该组件实例,若需要拿到某些属性,需要在该组件中的 setup 中使用 defineExpose 进行暴露

vue
<template> <div class="sum"> <h1>中国</h1> <h2 ref="province">山东</h2> <h3>烟台</h3> <button @click="showH2">输出h2元素</button> </div> </template> <script lang="ts"> export default { name: 'City' } </script> <script setup lang="ts"> import { ref } from 'vue'; let province = ref(); function showH2() { console.log(province.value) } </script>

布局样式

一个 <style> 标签可以使用 scopedmodule attribute 来帮助封装当前组件的样式。使用了不同封装模式的多个 <style> 标签可以被混合入同一个组件。

props

组件间传递数据

App.vue

vue
<template> <!-- <City /> --> <!-- <Sum /> --> <!-- <Computed /> --> <Person a="haha" :personList="personList" /> <!-- <Car /> --> </template> <script setup lang="ts"> import Car from './components/Car.vue'; import City from './components/City.vue'; import Computed from './components/Computed.vue'; import Person from './components/Person.vue'; import Sum from './components/Sum.vue'; import { reactive } from 'vue'; import { type Persons } from '@/types'; let personList = reactive<Persons>([ { id: '3456-7890', name: '张三', age: 18 }, { id: '3456-7891', name: '李四', age: 20 }, { id: '3456-7892', name: '王五', age: 22, x: 166 } ]); </script> <script lang="ts"> export default { name: 'App' } </script>

components/Person.vue

vue
<template> <div class="person"> <ul> <!-- 不写 key 默认为 index --> <!-- 可以写数组,也可以写遍历的次数 --> <li v-for="person in personList" :key="person.id"> <span>{{ person.name }} - {{ person.age }}</span> </li> </ul> </div> </template> <script lang="ts"> export default { name: 'Person', } </script> <script setup lang="ts"> import type { Persons } from '@/types'; // 直接接收props // let props = defineProps(['a', 'personList']) // 接收props并限制类型 // let props = defineProps<{ personList: Persons }>() // 接收props + 限制类型 + 限制必要性 + 指定默认值 let props = withDefaults(defineProps<{ personList?: Persons }>(), { personList: () => [{ id: '000', name: '康师傅', age: 30 }] }) console.log('props',props) </script>

types/index.ts

typescript
export interface PersonType { id: string, name: string, age: number, x?: number } export type Persons = PersonType[]

本文作者:Morales

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!