2024 ❤️ Vue3 的学习笔记

355 阅读12分钟

1、Composition Api

OptionsAPI 选项式API:配置 name,data,methods...,是分散的

CompositionsAPI 组合式:用函数的方式将 name,data,methods...组合到一起

1.setup

setup 语法糖,添加组件名称(标签中)需要安装插件vite-plugin-setup-extent

<script setup lang="ts" name="Hello"></script>

2.ref

创建【基本类型】的响应式数据,返回一个RefImpl的实例对象

对于let name = ref('张三')来说,name不是响应式的,name.value是响应式的。

接收的数据也可以是【对象类型】,内部其实还是调用了 reactive 函数

3.reactive

创建【对象类型】的响应式数据,返回一个Proxy的实例对象

定义的响应式数据是深层次的

对比:

  • ref 创建的变量必须使用.value
  • reactive 重新分配一个新对象时会失去响应式(可以使用Object.assign去整体替换)

4.toRefs 与 toRef

将一个响应式对象中的每一个属性装换为 ref 对象,toRefs可以批量替换

<script lang="ts" setup name="Hello">
let aa = reactive({
  name: 'judian',
  age: 18
})
// toRefs将aa对象的n个属性批量取出,都保持响应式
let {name,age} = toRefs(aa);
// toRef将aa对象的name属性取出,保持响应式
let name2 = toRef(aa, 'name'); 
</script>

5.computed

根据已有的数据计算出新数据

计算属性有记忆,仅在依赖的数据改变时执行。函数是只要调用就执行

<script setup>
// 只读,不能修改
let fullName = computed(() => {
  return firstName.value.slice(0,1).toUppercase() + firstName.value.slice(1) '-' + lastName.value
})
// 想要修改添加get,set
let fullName2 = computed({
  // 读取
  get(){
    return firstName.value + '-' + lastName.value
  },
  // 修改
  set(val){
    const [first,last] = val.split('-')
    firstName.value = first
    lastName.value = last
  }
})
</script>

6.watch

监视数据变化,只监视以下四种数据:

  • ref 定义的数据
  • reactive 定义的数据
  • 函数返回一个值(getter 函数)() => xx
  • 一个包含上述内容的数组

ref定义的基本类型数据,监视的是其 value 值的改变

<script setup>
let sum = ref(0);
const stopWatch = watch(sum, (newval,oldval) => {
  if(newval >= 10) stopWatch()
})
// 监视 sum,且大于 10 的时候停止监视
</script>

ref定义的对象类型数据,监视的是对象的地址值,若想监视内部数据需要手动开启深度监视{deep: true}

若修改的是 ref 定义的对象中的属性,new 和 old 都是新值,因为他们是同一个对象。若修改整个 ref 定义的对象,new 是新值 ,old 是旧值,因为他们不是同一个对象了

<script setup>
let person = ref({
  name: 'judian',
  age: 18
})
watch(person, (newval,oldval) => {
  console.log("person改变了")
},{deep: true})
</script>

reactive 定义的对象类型的数据,默认开启了深度监视且不可关闭

<script setup>
let person = reactive({
  name: 'judian',
  age: 18
})
watch(person, (newval,oldval) => {
  console.log("person改变了")
})
</script>

监视 ref 或reactive 定义的对象类型中的某个属性:

  • 若该属性不是对象类型需要写成函数的形式(getter函数,一个函数一个返回值)
  • 若该属性依然是对象类型,可以直接写也可以写成函数的形式,建议写成函数的形式

监视的是对象,是地址值,需要关注对象内部,要手动开启深度监视

<script setup>
let person = reactive({
  name: 'judian',
  age: 18
})
watch(() => person.name, (newval,oldval) => {
  console.log("person.name改变了")
})
// 一个函数,一个返回值(getter 函数) () => { return person.name} 简写了,
</script>

监视多个数据,数组的形式

<script setup>
let sum = ref(0);
let person = reactive({
  name: 'judian',
  age: 18
});
watch([() => person.name, sum], (newval,oldval) => {
  // val是一个数组的形式[person.name,sum]
  console.log("person.name,sum改变了")
})
</script>

7.watchEffect

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

对比

  • watch:要明确指出监视的数据
  • watchEffect:不用指出,函数中用到那些属性就监视那些属性

8.标签ref属性

用于注册模板引用

  • 用在普通 DOM 标签上,获取的是 DOM 节点
  • 用在组件标签上,获取的是组件实例对象
<template>
  <Person ref="per" />
</template>
<script setup>
let per = ref();
// per.value.name 子组件里定义的 name
</script>

子组件:要使用defineExpose暴露内容

<script setup>
let name = ref('judian');
defineExpose({name});
// 使用defineExpose将组件中的数据交给外部
</script>

9.一些TS-接口泛型

// 定义接口,限制每个 person 对象的格式
export interface PersonInter {
  name: string,
  age: number,
  x?: number // 可有可无
}
<script setup>
import {type PersonInter} from "@/types"
let personList:Array<PersonInter> = [
  {name: 'judian', age: 18},
  {name: 'judian', age: 18, x: 9},
]
</script>

personList:Array<PersonInter>可以写成一个自定义类型的形式

// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>
<template>
  <Person :list="persons"/>
</template>
<script lang="ts" setup name="App">
import {type Persons} from './types'
let persons = reactive<Persons>([
  {name:'judian',age:18}
])
</script>

10.props

Person.vue

<script setup>
  // 只接收 list
  defineProps(['list']);
  
  // 接收并保存 props
  let props = defineProps(['list']);
  console(props.list);
  
  // 接收+限制类型(可有可无)
  defineProps<{list?:Persons}>();
  
  // 接收+限制类型(可有可无)+ 指定默认值
  withDefaults(defineProps<{list?:Persons}>(),{
    list: () => [{name: 'judian', age: 18}]
  })
</script>

11.生命周期

生命周期分为四个阶段:创建,挂载,更新,销毁

vue2的生命周期

创建:beforeCreatecreated

挂载:beforeMountmounted

更新:beforeUpdateupdated

销毁:beforeDestorydestoryed

vue3的生命周期

创建:setup

挂载:onBeforeMountonMounted

更新:onBeforeUpdateonUpdated

销毁:onBeforeUnmountonUnmounted

常用的钩子:onMounted挂载完毕、onUpdated更新完毕、onBeforeUnmount卸载之前

12.自定义 hook

本质是一个函数,把setup函数中使用的composition API进行了封装,类似于 vue2 的 mixin

优势:复用代码,让 setup 中的逻辑更清晰

useSum.ts

export default function() {
  let sum = ref(0);
  const increment = () => {
    sum.value += 1
  }
  const decrement = () => {
    sun.value -= 1
  }
  onMounted(() => {
    increment()
  })
  
  // 向外暴露数据
  return {
    sum,
    increment,
    decrement
  }
}

uesDog.ts

import axios, {AxiosError} from "axios";
export default function() {
  let dogList = reactive<string[]>([]);
  
  async function getDog() {
    try {
      let {data} = await axios.get("https://dog.ceo/api/breed/pembroke/images/random");
      dogList.push(data.message)
    }catch (error) {
      const err = <AxiosError>error
      console.log(err.message)
    }
  }
  
  onMounted(() => {
    getDog()
  })
  
  return {
    dogList,
    getDog
  }
}

在组件中的使用

<template>
  <h2>当前求和为:{{sum}}</h2>
  <button @click="increment">点我+1</button>
  <button @click="decrement">点我-1</button>
  <hr>
  <img v-for="dog in dogList" :key="dog" :src="dog"> 
  <button @click="getDog">再来一只狗</button>
</template><script setup lang="ts">
  import useSum from './hooks/useSum'
  import useDog from './hooks/useDog'
​
  let {sum,increment,decrement} = useSum()
  let {dogList,getDog} = useDog()
</script>

2、路由

1.工作模式

  • history 模式:不带有#,需要服务器处理路径问题
  • hash 模式:带#,不需要处理
const router = createRouter({
  history: createWebHistory(), // history
})
​
// history: createWebHashHistory() hash模式

2.命名路由

routes: [
  name: 'zhuye',
  path: '/home',
  component: Home
]

3.routerLink to

<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link><!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
<router-link :to="{name:'zhuye'}">跳转</router-link>

4.嵌套路由

news添加 detail

{
    name:'xinwen',
    path:'/news',
    component:News,
    children:[
        {
            name:'xiang',
            path:'detail',
          	// path:'detail/:a/:b/:content'
            component:Detail
        }
    ]
},

跳转路由添加完整路径

<router-link to="/news/detail">xxxx</router-link>
<!-- 或 -->
<router-link :to="{path:'/news/detail'}">xxxx</router-link>
<router-link :to="{name:'xiang'}">xxxx</router-link>

5.传参

  • query
  • params:若使用to的对象写法,必须使用name配置项,不能用path。并需要提前在规则中占位
// query
<router-link to="/news/detail?a=1&b=2&content=欢迎你">跳转</router-link>
<router-link :to="{path: '/news/detail', query: {a: 1, b: 2, content: '欢迎你'}}">跳转</router-link>

// 接收参数
import {useRoute} from 'vue-router'
const route = useRoute()
// route.query

// params
<RouterLink :to="`/news/detail/1/2/欢迎你`">{{news.title}}</RouterLink>
<router-link :to="{name: 'xiang', params: {a: 1, b: 2, content: '欢迎你'}}">跳转</router-link>

{
    name:'xiang',
    path:'detail/:a/:b/:content?',
		...
}
// params 传参数,一一对应,并且只能使用 name,:content?表示可传可不传

// 接收参数
route.params

6.路由的 props 配置

让路由组件更方便的收到参数(可以将路由参数作为props传给组件)

{
    name:'xiang',
    path:'detail/:a/:b/:content',
    component:Detail,

  // props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
  // props:{a:1,b:2,c:3}, 

  // props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
  // props:true

  // props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
  props(route){
    return route.query
  }
}

defineProps(['a','b','c'])

7.replace属性

控制路由跳转时操作浏览器历史记录的模式

浏览器的历史纪录有两种方式:push 和 replace

  • push:追加历史纪录,默认的
  • replace:替换当前纪录
<RouterLink replace .......>News</RouterLink>

8.编程式导航

路由组件的两个重要的属性:$route$router变成了两个hooks

import {useRoute,useRouter} from 'vue-router'const route = useRoute()
const router = useRouter()
​
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)

9.重定向

将特定的路径,重新定向到已有路由。

{
    path:'/',
    redirect:'/about'
}

3、pinia

安装:npm install pinia

引入:main.ts

import {createPinia} from "pinia";
const pinia = createPinia();
app.use(pinia)

1.store

是一个保存:状态,业务逻辑的实体,每个组件都可以读取、写入它

有三个概念:state,getter,action。相当于组件中data,computed,methods

src/store/count.ts

import {defineStore} from "pinia"

export const useCountStore = defineStore('count',{
  // 动作
  action: {},
  // 状态
  state() {
    return {
      sum: 6
    }
  },
  // 计算
  getters: {}
})

src/store/talk.ts

export const useTalkStore = defineStore('talk',{
  action: {},
  state() {
    return {
      talkList:[
        {id:'01',content:'你今天有点怪,哪里怪?怪好看的!'},
        {id:'02',content:'草莓、蓝莓、蔓越莓,你想我了没?'}
      ]
    }
  },
  getters: {}
})

组件中使用 state 中的数据

<template>
  <h2>当前求和为:{{ countStore.sum }}</h2>
</template>
<script setup lang="ts" name="Count">    
  import {useCountStore} from '@/store/count'
  const countStore = useCountStore()
</script>

<!-- v-model.number 尽可能的转成数字 -->
<select v-model.number="n">
  <option value="1">1</option>
  <option value="2">2</option>
</select>

2.修改数据

  • 直接修改:countStore.sum = 666
  • 批量修改:countStore.$patch({sum: 999})
  • 借助 action 修改
export const useCountStore = defineStore('count',{
  // 动作
  action: {
    //加
    increment(value:number) {
      if (this.sum < 10) {
        //操作countStore中的sum
        this.sum += value
      }
    },
    //减
    decrement(value:number){
      if(this.sum > 1){
        this.sum -= value
      }
    }
  },
  ...
})

组件中调用 actioncountStore.increment(9)

3.storeToRefs

借助 storeToRefs 将 store 中的对象转换 ref 对象

pinia提供的storeToRefs只会将数据做转换,不会对方法进行包裹,而VuetoRefs会转换store中数据。

<template>
    <div class="count">
        <h2>当前求和为:{{sum}}</h2>
    </div>
</template><script setup lang="ts" name="Count">
  import { useCountStore } from '@/store/count'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'
​
    /* 得到countStore */
  const countStore = useCountStore()
  /* 使用storeToRefs转换countStore,随后解构 */
  const {sum} = storeToRefs(countStore)
</script>

4.getters

当 state 中的数据,需要经过处理后再使用时可以使用getters配置

export const useCountStore = defineStore('count',{
  ...
  // 计算
  getters:{
    bigSum:(state):number => state.sum *10,
    upperSchool():string{
      return this.school.toUpperCase()
    }
  }
})

组件中读取数据:let {bigSum} = storeToRefs(countStore)

5.$subscribe

通过 store 的$subscribe()方法监听 state 及其变化,as string

talkList = JSON.parse(localStorage.getItem('talk') as string) || []

talkStore.$subscribe((mutate,state) => {
  localStorage.setItem('talk', JSON.stringify(state.talkList))
})

6.store组合式写法

import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
  // talkList就是state
  const talkList = reactive(
    JSON.parse(localStorage.getItem('talkList') as string) || []
  )

  // getATalk函数相当于action
  async function getATalk(){
    // 发请求,下面这行的写法是:连续解构赋值+重命名
    let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
    // 把请求回来的字符串,包装成一个对象
    let obj = {id:nanoid(),title}
    // 放到数组中
    talkList.unshift(obj)
  }
  return {talkList,getATalk}
})

4、组件通信

与 vue2 的区别

  • 移除事件总线,使用mitt代替
  • vuex 换成 pinia
  • .sync优化到 v-model 里面
  • $listeners所有东西合并到$attrs
  • $children被砍掉了

1.props

  • 父传子:属性值是非函数
  • 子传父:属性值是函数
<Child :car="car" :sendToy="getToy"/>

<h4>我的玩具:{{ toy }}</h4>
<h4>父给我的车:{{ car }}</h4>
<button @click="sendToy(toy)">玩具给父亲</button>
const toy = ref('奥特曼')
defineProps(['car','sendToy'])

2.自定义事件

常用于:子 => 父,推荐使用kebab-case的事件名

区分原生事件

  • 原生事件:名字是特定的,事件对象$event包含事件相关的对象 pageX、target....
  • 自定义事件:名字是任意的,事件对象$event是调用 emit 时所提供的数据,可以是任意类型
<!--在父组件中,给子组件绑定自定义事件:-->
<Child @send-toy="toy = $event"/>
​
//子组件中,触发事件:
this.$emit('send-toy', 具体数据)

3.mitt

与消息订阅与发布pubsub类似,可以实现任意组件间的通信

  • 接收数据的:提前绑定好事件(订阅消息)
  • 提供数据的:在合适的时候触发事件(发布消息)

npm i mitt

emitter.ts

const emitter = mitt();
// 绑定事件
emitter.on('abc',(val) => {})
// 触发事件
emitter.emit('abc',123)
// 清理事件
emitter.all.clear()
// 创建并暴露mitt
export default emitter

接收数据的组件中:绑定事件,同时在销毁前解绑事件

// 绑定事件
emitter.on('send-toy',(value)=>{
  console.log('send-toy事件被触发',value)
})
onUnmounted(()=>{
  // 解绑事件
  emitter.off('send-toy')
})

提供数据的组件中:在合适的时候触发事件

import emitter from "@/utils/emitter";

function sendToy(){
  // 触发事件
  emitter.emit('send-toy',toy.value)
}

4.v-model

v-model 本质

  • $event对于原生事件,就是事件对象,$event.target.value
  • 对于自定义事件,$event是触发事件时所传递的数据
<!-- 使用v-model指令 -->
<input type="text" v-model="userName">
<!-- v-model的本质是下面这行代码 -->
<input 
  type="text" 
  :value="userName" 
  @input="userName =(<HTMLInputElement>$event.target).value"
>

组件上的 v-model::modelValue+update:modelValue

<!-- 组件标签上使用v-model指令 -->
<AtguiguInput v-model="userName"/>

<!-- 组件标签上v-model的本质 -->
<AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
<template>
  <div class="box">
    <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
    <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
    <input 
       type="text" 
       :value="modelValue" 
       @input="emit('update:model-value',(<HTMLInputElement>$event.target).value)"
    >
  </div>
</template>

<script setup lang="ts" name="AtguiguInput">
  // 接收props
  defineProps(['modelValue'])
  // 声明事件
  const emit = defineEmits(['update:model-value'])
</script>

value 就是 modelValue。v-model 传递的默认 value

如果value可以更换,那么就可以在组件标签上多次使用v-model

<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>

5.$attrs

用于实现当前组件的父组件,向当前组件的子组件通信(祖 -> 孙)

$attrs是一个对象,包含所有父组件传入的标签属性

$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

父组件:

v-bind="{x:100,y:200}" ==> :x=100 :y=200

<template>
  <div class="father">
    <h3>父组件</h3>
    <Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
  </div>
</template><script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref } from "vue";
    let a = ref(1)
    let b = ref(2)
    let c = ref(3)
    let d = ref(4)
​
    function updateA(value){
        a.value = value
    }
</script>

子组件:

<template>
    <div class="child">
        <h3>子组件</h3>
        <GrandChild v-bind="$attrs"/>
    </div>
</template><script setup lang="ts" name="Child">
    import GrandChild from './GrandChild.vue'
</script>

孙组件:

<template>
    <div class="grand-child">
        <h3>孙组件</h3>
        <h4>a:{{ a }}</h4>
        <h4>b:{{ b }}</h4>
        <h4>c:{{ c }}</h4>
        <h4>d:{{ d }}</h4>
        <h4>x:{{ x }}</h4>
        <h4>y:{{ y }}</h4>
        <button @click="updateA(666)">点我更新A</button>
    </div>
</template>

<script setup lang="ts" name="GrandChild">
    defineProps(['a','b','c','d','x','y','updateA'])
</script>

6.refsrefs、parent

  • $refs用于 :父→子。
  • $parent用于:子→父。

原理如下:

需要使用defineExpose()暴露

属性说明
$refs值为对象,包含所有被ref属性标识的DOM元素或组件实例。
$parent值为对象,当前组件的父组件实例对象。

7.provide/inject

实现祖孙组件直接通信

  • 在祖先中间中通过provide向后代组件提供数据
  • 在后代组件中通过inject配置来声明接收数据

父组件,使用 provide 提供数据

  let money = ref(100)
  // 用于更新money的方法
  function updateMoney(value:number){
    money.value += value
  }
  // 提供数据
  provide('moneyContext',{money,updateMoney})
  provide('car',car)

注意:子组件中不用编写任何东西,是不受到任何打扰的

孙组件,使用inject接收数据,inject('car','默认值')

<button @click="updateMoney(6)">点我</button>
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})

updateMoney孙组件修改祖组件的数据,{money:0,updateMoney:(x:number)=>{}}默认值

8.slot

  • 默认插槽
父组件中:
        <Category title="今日热门游戏">
          <ul>
            <li v-for="g in games" :key="g.id">{{ g.name }}</li>
          </ul>
        </Category>
子组件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <!-- 默认插槽 -->
            <slot></slot>
          </div>
        </template>
  • 具名插槽
父组件中:
        <Category title="今日热门游戏">
          <template v-slot:s1>
            <ul>
              <li v-for="g in games" :key="g.id">{{ g.name }}</li>
            </ul>
          </template>
          <template #s2>
            <a href="">更多</a>
          </template>
        </Category>
子组件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <slot name="s1"></slot>
            <slot name="s2"></slot>
          </div>
        </template>
  • 作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。

数据在子组件,父使用子的数据,需要子通过插槽给父

新闻数据在News组件中,但使用数据所遍历出来的结构由App组件决定

父组件中:
      <Game v-slot="params">
      <!-- <Game v-slot:default="params"> -->
      <!-- <Game #default="params"> -->
        <ul>
          <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
        </ul>
      </Game>
​
子组件中:
      <template>
        <div class="category">
          <h2>今日游戏榜单</h2>
          <slot :games="games" a="哈哈"></slot>
        </div>
      </template>
​
      <script setup lang="ts" name="Category">
        import {reactive} from 'vue'
        let games = reactive([
          {id:'asgdytsa01',name:'英雄联盟'},
          {id:'asgdytsa02',name:'王者荣耀'},
          {id:'asgdytsa04',name:'斗罗大陆'}
        ])
      </script>
组件关系传递方式
父子props,v-model,$refs,默认插槽、具名插槽
子父props,自定义事件,v-model,$parent,作用域插槽
祖孙,孙祖$attrs,provide、inject
兄弟,任意组件mitt,pinia

总结

5、其他API

1.shallowRef、shallowReactive

  • shallowRef:创建一个响应式数据,但只对顶层属性进行响应式处理。只跟踪引用值的变化,不关心值内部的属性变化
  • shallowReactive:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性不变。对象的顶层属性是响应式的,但嵌套对象的属性不是
const myVal = shallowRef(initVal);
const myObj = shallowReacitve({...});

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

2.readonly、shallowReadonly

  • readonly:创建一个对象的只读副本
const original = reactive({...});
const readOnlyCopy = readonly(original);                      

对象的所有嵌套属性都将变为只读

任何尝试修改这个对象的操作都会被阻止(开发模式下会发出警告)

应用:创建不可改变的状态快照,保护全局状态或配置不被修改

  • shallowReadonly:作用于readonly类似,但只作用于对象的顶层属性,
const original = reactive({...});
const shallowReadOnlyCopy = shallowReadonly(original); 

只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。

适用于只需保护对象顶层属性的场景

3.toRaw、markRaw

  • toRaw:用于获取一个响应式对象的原始对象,返回的对象不再是响应式的,不会触发视图更新

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法,不建议保存对原始对象的持久引用,请谨慎使用

何时使用?在需要将响应式对象传递给非 Vue 的库或者外部系统时,使用toRaw可以确保他们收到的是普通对象。

let person = reactive({name: 'judian',age: 18})
let rawPer = toRaw(person)
  • markRaw:标记一个对象,使其永远不会变为响应式的

例如使用mockjs时,为了防止勿把其变成响应式对象可以使用这个标记

let citys = markRaw([
     {id:'asdda01',name:'北京'},
     {id:'asdda02',name:'上海'},
     {id:'asdda03',name:'天津'},
     {id:'asdda04',name:'重庆'}
])
// 根据原始对象citys去创建响应式对象citys2 —— 创建失败,因为citys被markRaw标记了
let citys2 = reactive(citys)

4.customRef

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。

接收一个 get,set 函数

实现防抖效果(useSumRef.ts

import {customRef } from "vue";
​
export default function(initValue:string,delay:number){
  let msg = customRef((track,trigger)=>{
    // track跟踪,trigger触发
    let timer:number
    return {
      get(){
        track() // 告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
        return initValue
      },
      set(value){
        clearTimeout(timer)
        timer = setTimeout(() => {
          initValue = value
          trigger() //通知Vue数据msg变化了
        }, delay);
      }
    }
  }) 
  return {msg}
}

组件中使用:

let {msg} = useSumRef("haha",200)
  • track告诉 vue 持续关注
  • trigger通知 vue 我已经改变

6、新组件

1.Teleport

是一种能够将我们的组件 html 结构移动到指定位置的技术

<teleport to='body' >
    <div class="modal" v-show="isShow">
      <h2>我是一个弹窗</h2>
      <p>我是弹窗中的一些内容</p>
      <button @click="isShow = false">关闭弹窗</button>
    </div>
</teleport>

teleport包裹的 modal 被放到 body 下,以 body 为父级居中展示,

解决filter:saturate(0%)饱和度,图片变灰色添加这个 modal 只在他父级居中了

2.Suspense

等待异步组件时渲染一些额外内容:组件的数据是通过异步任务获取的

  • 异步组件引入
  • 使用Suspense包裹组件,并配置好defaultfallback
import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
​
​
<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
          <template v-slot:default>
            <Child/>
          </template>
          <template v-slot:fallback>
            <h3>加载中.......</h3>
          </template>
        </Suspense>
    </div>
</template>

3.全局 API 转移到应用对象

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

4.非兼容性改变

  • 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from
  • keyCode 作为 v-on 修饰符的支持。
  • v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。
  • v-ifv-for 在同一个元素身上使用时的优先级发生了变化。
  • 移除了$on$off$once 实例方法。
  • 移除了过滤器 filter
  • 移除了$children 实例 propert