Vue3回顾

核心:ref、reactive、computed、watch、生命周期…

Vue3相比Vue2的区别:

  • 性能的提升
  • 源码的升级
    • 使用Proxy代替defineProperty实现响应式
    • 重写虚拟DOM的实现和Tree-Shaking
  • 可以更好的支持TS
  • 新的特性
    1. Composition API(组合API):
      • setup
      • refreactive
      • computedwatch
    2. 新的内置组件
      • Fragment
      • Teleport
      • Suspense
    3. 其他改变:
      1. 新的生命周期钩子
      2. data选项应始终被声明为一个函数
      3. 移除keyCode支持v-on的修饰符

1.Options API与Composition API

选项式API即Vue2中的写法,通过data、methods等分别定义。

组合式API即Vue3中的写法,通过写在set up

data、methods 和 set up 是可以同时存在的

前者可以this.读出set up里的东西,而反之不行。

​ 这是因为set up 在beforeCreated之前执行,且set up中this指向undefined。基本在vue3的setup中,都不能去碰this

2.ref与reactive

set up语法糖写法<script setup>

在script标签中通过name=指定组件名字,先要安装一个插件vite-plugin-vue-setup-extend

  • 基本数据类型的响应式写法:

    1
    2
    3
    4
    5
    6
    7
    8
    <script lang="ts" setup>
    import {ref} from 'vue'
    let name = ref('张三')

    function changeName(){
    name.value = 'zhang-san'
    }
    </script>
  • 引用类型的响应式写法

    1
    2
    import {reactive} from 'vue'
    let car = reactive({brand:'奔驰',price:100})

ref可以定义基本、引用类型

reactive只能定义引用类型

但是其实ref处理引用类型数据,用的是reactive去处理

  • 使用原则如下:

    • 基本类型的响应数据,必须用ref
    • 引用类型的响应式数据,且层级不深,ref、reactive都可以
    • 引用类型的响应式数据,且层级深,推荐用reactive
  • 注意:

    1.如果从一个响应式的对象上,去直接解构属性,属性将丢失响应式

    如:

    1
    2
    3
    import {reactive} from 'vue'
    let car = reactive({brand:'奔驰',price:100})
    let {brand,price} = car //brand和price会丢失响应式

    2.reactive定义的对象,不能直接修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let car = reactive({brand:'奔驰',price: 80})
    //要修改car,不能直接修改
    car = {brand:"宝马",price:100}
    //或
    car = reactive({brand:"宝马",price:100})
    //都不行

    //得这样修改
    Object.assign(car, {brand:"宝马",price:100})

3.toRefs与toRef,storeToRefs

都是将一个响应式的数据,解构出来的东西,仍具响应式

1
2
3
4
5
import {reactive,toRefs,toRef} from 'vue'
let person = reactive({name:'张三',age:18})

let {name,age} = toRefs(person)
let n = toRef(person,'age')
  • storeToRefs:将store中的state属性,进行解构后仍然有响应式,不会对方法进行ref包裹

4.computed

页面中多次用某个值,只计算一次

vue3中的写法

1
2
3
4
5
6
7
8
9
10
// 写法一:这么定义的fullName是一个只读的计算属性,不能对fullName进行修改
let fullName = computed(()=>{
return firstName.value + '-' + lastName.value
})

//写法二:计算属性,且可读可写
let fullName = computed({
get(){},
set(){}
})

5.watch

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

  1. ref定义的数据
  2. reactive定义的数据
  3. 函数返回一个值
  4. 一个包含上述内容的数组

watch(被监视的数据,监视的回调,配置对象(deep,immediate...))

对于四种情况,监视写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//1.监视ref定义的基本类型数据
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了')
// 停止监视
if(newValue >= 10){
stopWatch()
}
})

//2.监视ref定义的引用类型数据,直接写数据名(person),监视的是对象的地址值;若想监视对象内部的属性,手动开启深度监视
watch(person,(newValue,oldValue)=>{
console.log('person变化了')
},{deep:true})

//3.监视reactive定义的引用数据,不用写deep,默认就是开启深度监视的

//4.监视响应式对象中某个属性,要写成函数式
watch(()=>person.age,(newValue,oldValue)=>{
console.log('person变化了')
})

//5.监视多个数据
watch([()=>person.age, car],(newValue,oldValue)=>{
console.log('变化了')
})

6.watchEffect

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

watch:要指明监视的数据

watchEffect:不用明确指明,自动追踪

1
2
3
4
5
watchEffect(()=>{
if(temp.value > 30){
console.log("变化了")
}
})

7.标签的ref属性

用于注册模板引用

  • 用在普通DOM标签上,获取的是DOM节点
  • 用在组件标签上,获取的是组件实例对象

8.props

父组件给子组件传值常用,子给父传数据时,需要父先自定义一个事件。具体例子在13节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--父组件给子组件Person传list-->
<Person :list="personList"></Person>

<!--子组件接收list-->
<script lang="ts">
import {withDefaults} from 'vue'
import {type Persons} from '@/types'

// 情况一:仅接收list即可
defineProps(['list'])

// 情况二:接收list并限制类型
defineProps<{list:Persons}>()

// 情况三:接收list+限制类型+限制必要性+指定默认值
withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'1',name:'admin'}]
})
</script>

9.生命周期函数

和Vue2相似,都是4个大阶段:创建、挂载、更新、销毁

  • 创建:setup
  • 挂载:onBeforeMountonMounted
  • 更新:onBeforeUpdateonUpdated
  • 销毁:onBeforeUnmountonUnmounted

10.hooks

类似Vue2里的mixin,本质就是将模块化开发发挥到极致,将一些ts封装成xxx.ts文件,这些文件命名为useXXX,比如useDog.ts

正是因为hooks,Composition才发挥出了威力

这些hooks文件里,支持写数据、方法,还支持写钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// hooks/useDog.ts
import {reactive, onMounted} from 'vue'
import axios from 'axios'

export default function(){
//数据
let dogList = reactive(['https:....jpg'])
//方法
async function getDog(){}
//钩子函数
onMounted(()=>{})

return {dogList,getDog}
}

//vue组件使用
import useDog from '@/hooks/useDog'
const {dogList,getDog} = useDog()

11.路由

点击导航,视觉上消失的路由组件,默认是被销毁了,需要的时候再去挂载

​ vue2中通过keep-alive缓存组件

12.Pinia

vue2中用的是vuex,vue3中用的pinia

一个使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//count.ts
import {defineStore} from 'pinia'

export const useCountStore = defineStore('count',{
//actions里面放置的是一个一个的方法,用于响应组件中的“动作”
actions:{
increment(value){
this.sum += 1
}
}
//真正存数据的地方
state(){
return{
sum: 6,
school:xkd
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//vue组件中
<h2>求和结果{{countStore.sum}}</h2>
<script setup lang="ts">
//引入useCountStore
import {useCountStore} from '@/store/count'
//使用useCountStore,得到一个专门保存count相关的store
const countStore = useCountStore()

let n = ref(1)

//修改数据
function add(){
// 方式一:
countStore.sum += 1

//方式二:
countStore.$patch({
sum:888,
school:'北大'
})

// 方式三:
countStore.increment(n.value)
}
</script>

pinia中也有getters,对数据进行计算处理,像computed

pinia中有$subscribe,像watch

13.组件通信

  • 1.props

    父给子传递car,子给父传递toy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <h>父组件</h>
    <h4>汽车:{{car}}</h4>
    <Child :car="car" :sendToy="getToy" />
    <h4 v-show="toy">子给父的玩具:{{toy}}</h4>

    <script lang="ts" setup>
    import Child from './Child.vue'
    import {ref} from 'vue'
    //数据
    let car = ref('奔驰')
    //方法
    function getToy(value:string){
    console.log('父拿到子传来的数据',value)
    }
    </script>


    <h>子组件</h>
    <h4>玩具:{{toy}}</h4>
    <h4>父给的汽车:{{car}}</h4>
    <button @click="sendToy(toy)">把玩具toy传给父</button>

    <script lang="ts" setup>
    import {ref} from 'vue'
    //数据
    let toy = ref('奥特曼')
    //声明接收props
    defineProps(['car','sendToy'])
    </script>
  • 2.自定义事件

    子声明一个事件,并且在合适时机去触发;父给子组件绑定该事件,从而实现子给父传值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <h>子组件</h>
    <h4>玩具:{{toy}}</h4>
    <button @click="emit('send-toy',toy)">把玩具toy传给父</button>

    <script lang="ts" setup>
    import {ref} from 'vue'
    //数据
    let toy = ref('奥特曼')
    //声明事件,合适时机触发
    const emit = defineEmits(['send-toy'])
    </script>


    <h>父组件</h>
    <Child @send-toy="saveToy" /> <!--绑定该事件-->
    <h4 v-show="toy">子给父的玩具:{{toy}}</h4>

    <script lang="ts" setup>
    import Child from './Child.vue'
    import {ref} from 'vue'
    let toy = ref('')
    //方法
    function saveToy(value:string){
    console.log('父拿到子的数据',value)
    toy.value = value
    }
    </script>
  • 3.mitt

    像vue2中的pubsub、$bus还有vue3中的mitt,原理都是 提供数据的:在合适时机触发事件;接收数据的:提前绑定好事件。可以实现任意组件间通信

    举一个兄弟组件通信的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <h>哥哥</h>
    <button @click="emitter.emit('send-toy',toy)"></button> <!--触发事件-->

    <h>弟弟</h>
    <h4>
    哥哥给的玩具{{toy}}
    </h4>
    <script>
    //给emitter绑定send-toy事件
    emitter.on('send-toy',(value:any)=>{
    toy.value = value
    })
    //组件销毁卸载后,解绑事件
    onUnmounted(()=>{
    emitter.off('send-toy')
    })
    </script>
  • 4.v-model

    实际很少用,但是UI组件库会发现大量使用。

    面试可以吹一波,v-model原理解析:

    • v-model用在html标签上时

      1
      2
      3
      <input v-model="username">
      其实就是下面这句:
      <input :value="username" @input="username = (<HTMLInputElement>$event.target).value">
    • v-model用在组件上

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <SuInput v-model="username"></SuInput>
      翻译:
      <SuInput :modelValue="username" @update:modelValue="username = $event"></SuInput> <!--vuu3写法-->
      <SuInput :value="username" @input="username = $event"></SuInput> <!--vue2写法-->

      自定义的组件SuInput中应该写:
      <input :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)"/>
      <script>
      defineProps(['modelValue'])
      const emit = defineEmits(['update:modelValue'])
      </script>

      $event是什么?

      • 对于原生事件,$event就是事件对象==》能.target
      • 对于自定义事件,$event就是触发事件时,所传递的数据==》不能.target
  • 5.$attrs

    实现当前组件的父组件,向当前组件的子组件通信。(祖–>孙,孙–>祖)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <h>祖先</h>
    <Child :a="a" :updateA="updateA"></Child>

    <h>孩子</h>
    <GrandChild v-bind="$attrs"></GrandChild>

    <h>孙子</h>
    <button @click="updateA(6)"></button>
    <script>
    defineProps(['a','updateA'])
    </script>
  • 6.$refs、$parent

    • $refs:父–》子
    • $parent:子–》父
  • 7.provide、inject

    任意组件的通信

    provide向后代提供数据,inject拿到数据

    子给父传递的时候通过触发事件,绑定事件的方式。父中定义一个事件并将该事件provide给子组件,子组件inject接收事件并在合适时机触发该事件

1
2
provide('qian', money) //父 向后代提供数据
let money = inject('qian',"我是默认值") //子 拿到父传来的数据 且能设置默认值
  • pinia

  • slot

14.其他API

  • shallowRef()shallowReacrive()

    绕开深度响应,浅层式API创建的状态只在其顶层是响应式,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,提升性能.

  • readonlyshallowReadonly

    readonly:创建一个对象的深只读副本

    shallowReadonly:只作用于对象的顶层属性

  • toRawmarkRaw

    toRaw:获取一个响应式对象的原始对象

    markRaw:标记一个对象,使其永远不会变成响应式

  • customRef

    自定义Ref

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import {customRef} from 'vue'
    let initValue = 'hello'
    let timer:number

    // track(跟踪) trigger(触发)
    let msg = customRef((track,trigger)=>{
    return {
    get(){
    track() //告诉Vue数据msg很重要,你要对msg进行持续关注,一旦msg变化就去更新
    return initValue
    },
    set(){
    clearTimeout(timer)
    timer = setTimeout(()=>{
    initValue = value
    triggeer() //通知Vue,数据msg变化了
    })
    }
    }
    })
  • Teleport

    将组件html结构移动到指定位置的技术

    1
    2
    3
    4
    5
    6
    7
    <teleport to='body'>
    <div class="modal" v-show="isShow">
    <h2>弹窗标题</h2>
    <p>弹窗内容</p>
    <button @click="isShow = false">关闭弹窗</button>
    </div>
    </teleport>
  • Suspense

    等待异步组件时渲染一些额外的内容

    • 异步引入组件
    • 使用Suspense包裹组件,并配置好defaultfallback
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <div class = "app">
    <h3>我是APP组件</h3>
    <Suspense>
    <template v-slot:default>
    <Child></Child>
    </template>
    <template v-slot:fallback>
    <h3>加载中.....</h3>
    </template>
    </Suspense>
    </div>

    <script>
    import {defineAsyncComponent, Suspense} from 'vue'
    const Child = defineAsyncComponent(()=>import('./Child.vue'))
    </script>
  • 全局API转移到应用对象

    • app.component:注册组件
    • app.config:注册配置属性
    • app.directive:注册指令
    • app.mount:挂载组件
    • app.unmount:卸载组件
    • app.use:使用组件

15.与Vue2非兼容性改变

官网文档