Skip to content

前言

Vue3.0从20年九月发布第一个One Piece版本,到现在一直在更新优化;中文版的官方文档也已经放出;根据自己的学习和总结给大家分享一下Vue3新增了哪些功能和特性。

ref

  • ref可以代理字符串、数字、boolean等基本类型值
  • ref声明的值需要通过.value去改变
  • ref目的是为了引用原始类型值,但仍然可以引用非基本类型值例如对象
  • ref 本质也是reactive 可以简单地把 ref(1) 理解为这个样子 reactive({value: 1}),有兴趣的可以看一下源码
js
import { ref } from "vue";
//用法1、代理基本类型
var refBaseType = ref(1);

const add = () => {
  refBaseType.value++; //值改变,视图更新
};
//用法二、
const refObj = ref({ num: 1 })
const add = () => {
 //需要通过.value去访问
  refObj.value.num = 2 //值改变,触发视图更新
}

//用法三、
//可以通过ref代理某个对象下面的值,复制修改响应式数据不影响原对象
const refObj = { num: 1 }

const refBaseType = ref(refObj.num)

const add = () => {
    refBaseType.value++
    console.log(refBaseType.value) //值改变,视图更新
    console.log(refObj.num) //值不变
}
import { ref } from "vue";
//用法1、代理基本类型
var refBaseType = ref(1);

const add = () => {
  refBaseType.value++; //值改变,视图更新
};
//用法二、
const refObj = ref({ num: 1 })
const add = () => {
 //需要通过.value去访问
  refObj.value.num = 2 //值改变,触发视图更新
}

//用法三、
//可以通过ref代理某个对象下面的值,复制修改响应式数据不影响原对象
const refObj = { num: 1 }

const refBaseType = ref(refObj.num)

const add = () => {
    refBaseType.value++
    console.log(refBaseType.value) //值改变,视图更新
    console.log(refObj.num) //值不变
}

reactive

  • reactive接受一个可代理的对象,但不能是字符串、数字、boolean等基本类型值
  • reactive不需要通过.value去访问属性值
  • reactive解构会造成响应式丢失
js
import { reactive,ref,toRefs } from 'vue';

const obj = reactive({num:1})
const add = () => {
  obj.num++ //值改变,视图更新
}

// reactive解构会造成响应式丢失
const book = reactive({
  author: '尤雨溪',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book 
title = 'vue3 good' // 响应式丢失,视图不更新

// // 解决办法
let { author, title } = toRefs(book) 
title.value = 'vue3 good' // 通过.value 因为现在title为ref,视图更新

//直接赋值会造成响应式丢失
const book = reactive({
  author: '尤雨溪',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let book1 = reactive({})

setTimeout(()=>{
    console.log("我要更新book1对象")
    book1 = book  //直接赋值,视图不更新
    book1 = toRefs(book) //toRefs创建ref类型的数据,视图不更新
},3000)

//解决办法
//将一开始的book1对象就用ref去创建,因为.value改变的时候可以做到响应式
const book1 = ref({})

setTimeout(()=>{
    console.log("我要更新book1对象")
    book1.value = book  //直接赋值,视图更新
},3000)
import { reactive,ref,toRefs } from 'vue';

const obj = reactive({num:1})
const add = () => {
  obj.num++ //值改变,视图更新
}

// reactive解构会造成响应式丢失
const book = reactive({
  author: '尤雨溪',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = book 
title = 'vue3 good' // 响应式丢失,视图不更新

// // 解决办法
let { author, title } = toRefs(book) 
title.value = 'vue3 good' // 通过.value 因为现在title为ref,视图更新

//直接赋值会造成响应式丢失
const book = reactive({
  author: '尤雨溪',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let book1 = reactive({})

setTimeout(()=>{
    console.log("我要更新book1对象")
    book1 = book  //直接赋值,视图不更新
    book1 = toRefs(book) //toRefs创建ref类型的数据,视图不更新
},3000)

//解决办法
//将一开始的book1对象就用ref去创建,因为.value改变的时候可以做到响应式
const book1 = ref({})

setTimeout(()=>{
    console.log("我要更新book1对象")
    book1.value = book  //直接赋值,视图更新
},3000)

shallowReactive

  • 用法同reactive,用于定义一个浅响应数据只代理第一层,当数据结构比较复杂时,每层都用proxy代理消耗性能
js
import { shallowReactive } from 'vue'

const obj = shallowReactive({ foo: { bar: 1 } })

const add = () => {
  obj.foo.bar = 2 //值改变,不触发视图更新
  obj.foo = { bar: 2 } //值改变,视图更新
}
import { shallowReactive } from 'vue'

const obj = shallowReactive({ foo: { bar: 1 } })

const add = () => {
  obj.foo.bar = 2 //值改变,不触发视图更新
  obj.foo = { bar: 2 } //值改变,视图更新
}

readonly

  • 用于定义一个只可读数据,接受一个Object对象
js
import { readonly } from 'vue'

const obj = readonly({ text: 'hi' })

const add = () => {
  obj.text = "Hello" //报错,无法分配到 "text" ,因为它是只读属性。
}
import { readonly } from 'vue'

const obj = readonly({ text: 'hi' })

const add = () => {
  obj.text = "Hello" //报错,无法分配到 "text" ,因为它是只读属性。
}

shallowReadonly

-用于定义一个浅只读的数据,接受一个Object对象

js
import { shallowReadonly } from 'vue'

const obj = shallowReadonly({ text: {num:1} })

const add = () => {
  obj.text.num = 2 //值改变,视图更新
}
import { shallowReadonly } from 'vue'

const obj = shallowReadonly({ text: {num:1} })

const add = () => {
  obj.text.num = 2 //值改变,视图更新
}

toRef

  • 创建一个ref类型数据, 并和以前的数据关联
  • 相当于引用, 修改响应式数据会影响原始数据
  • 第一个参数为object对象;第二个参数为对象中的属性名
js
//用法:如果想让响应式数据和原始的数据关联起来, 并且更新响应式数据之后还不想更新UI, 那么就可以使用toRef

const obj = { num: 1 }
//const obj = reactive({ num: 1}) //reactive创建的对象会触发视图更新

const refVal = toRef(obj, 'num')
const add = () => {
    refVal.value++
    console.log(refVal.value) //值改变,视图不更新
    console.log(obj.num) //值改变,视图不更新
}
//用法:如果想让响应式数据和原始的数据关联起来, 并且更新响应式数据之后还不想更新UI, 那么就可以使用toRef

const obj = { num: 1 }
//const obj = reactive({ num: 1}) //reactive创建的对象会触发视图更新

const refVal = toRef(obj, 'num')
const add = () => {
    refVal.value++
    console.log(refVal.value) //值改变,视图不更新
    console.log(obj.num) //值改变,视图不更新
}

toRefs

  • 创建一个ref类型数据, 并和以前的数据关联
  • 相当于引用, 修改响应式数据会影响原始数据
  • 传入的参数只有一个且为object对象
js
//用法一、reactive解构赋值会失去响应式,那么就可以使用toRefs
import { reactive, toRefs } from 'vue'

const obj = reactive({ text: "hi", num: 1 })

let { text, num } = toRefs(obj)
const add = () => {
  text.value = "Hello" //值改变,视图更新
  num.value = 2 //值改变,视图更新
}

//用法二、批量创建ref类型数据, 并和以前数据关联,不触发视图更新

import { reactive, toRefs } from 'vue'

const obj = { foo: 1, num: 1 }
//const obj = reactive({ foo: 1, num: 1 })  //reactive创建的对象会触发视图更新
   
const state = toRefs(obj)

const add = () => {
  state.foo.value = 2 
  state.num.value = 2
  console.log(state.foo.value) // 2 值改变,视图不更新
  console.log(obj.foo) // 2 值改变,视图不更新
}
//用法一、reactive解构赋值会失去响应式,那么就可以使用toRefs
import { reactive, toRefs } from 'vue'

const obj = reactive({ text: "hi", num: 1 })

let { text, num } = toRefs(obj)
const add = () => {
  text.value = "Hello" //值改变,视图更新
  num.value = 2 //值改变,视图更新
}

//用法二、批量创建ref类型数据, 并和以前数据关联,不触发视图更新

import { reactive, toRefs } from 'vue'

const obj = { foo: 1, num: 1 }
//const obj = reactive({ foo: 1, num: 1 })  //reactive创建的对象会触发视图更新
   
const state = toRefs(obj)

const add = () => {
  state.foo.value = 2 
  state.num.value = 2
  console.log(state.foo.value) // 2 值改变,视图不更新
  console.log(obj.foo) // 2 值改变,视图不更新
}

shallowRef

  • 这是一个浅层的 ref ,只代理.value 可用于优化性能
js
import { shallowRef, triggerRef } from 'vue'

const obj = shallowRef({ num: 1 })

//shallowRef只代理 ref 对象本身,也就是说只有 .value 是被代理的,而 .value 所引用的对象并没有被代理

const add = () => {
  obj.value.num = 2 //值改变,视图不更新
  triggerRef(obj) // 可通过修改值后立即驱动视图更新
   
  obj.value = { num: 2 } //值改变,视图更新
 
}
import { shallowRef, triggerRef } from 'vue'

const obj = shallowRef({ num: 1 })

//shallowRef只代理 ref 对象本身,也就是说只有 .value 是被代理的,而 .value 所引用的对象并没有被代理

const add = () => {
  obj.value.num = 2 //值改变,视图不更新
  triggerRef(obj) // 可通过修改值后立即驱动视图更新
   
  obj.value = { num: 2 } //值改变,视图更新
 
}

unref

  • 接收一个值,如果这个值是 ref 就返回 .value,否则原样返回
js
//vue3 unref 源码 
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T { 
    return isRef(ref) ? (ref.value as any) : ref 
}

//用法一
import { ref, unref } from 'vue'

const val = "hi"
const refVal = ref(1)

const unrefVal = unref(val) //输出hi
const unrefObj = unref(refVal) //输出1
//vue3 unref 源码 
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T { 
    return isRef(ref) ? (ref.value as any) : ref 
}

//用法一
import { ref, unref } from 'vue'

const val = "hi"
const refVal = ref(1)

const unrefVal = unref(val) //输出hi
const unrefObj = unref(refVal) //输出1

markRaw

  • 将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据

markRaw 函数所做的事情,就是在数据对象上定义 __v_skip 属性,从而跳过代理

js
import { markRaw, reactive } from 'vue'

//通过markRow代理过的对象,不会触发视图更新
//markRow可用来数据改变但不需要视图改变的情况,用于提升性能。
const obj = { foo: 1 }
const obj2 = markRaw(obj)
const state = reactive(obj2)

const add = () => {
  state.foo = 2 //值改变,视图不更新
  console.log(state) //2
  console.log(obj) //2 
}
import { markRaw, reactive } from 'vue'

//通过markRow代理过的对象,不会触发视图更新
//markRow可用来数据改变但不需要视图改变的情况,用于提升性能。
const obj = { foo: 1 }
const obj2 = markRaw(obj)
const state = reactive(obj2)

const add = () => {
  state.foo = 2 //值改变,视图不更新
  console.log(state) //2
  console.log(obj) //2 
}

Vue3 的 VNode 对象带有 __v_skip: true 标识,用于跳过代理(实际上,只要带有 __v_skip 属性并且值为 true 的对象,都不会是被代理),例如:

js
// obj 是原始数据对象
const obj = reactive({
  foo: 0,
  __v_skip: true
})
// obj 是原始数据对象
const obj = reactive({
  foo: 0,
  __v_skip: true
})

toRaw

  • toRaw方法用于拿到原始数据,对原始数据进行修改,不会更新UI界面,
  • 与markRow()方法类似可用于提升性能,不同的是 markRow接收的不是被代理过的响应式数据
  • toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的
js
import { toRaw, reactive, ref } from 'vue'

//代理reactive对象
const obj = reactive({ num: 1 })
const obj2 = toRaw(obj)

const add = () => {
  obj2.num = 2 //值改变,视图不会更新
}

//代理ref创建的对象

const obj = ref({ num: 1 })
const obj2 = toRaw(obj.value) 
//与reactive不同的是,需要用.value去获取原始数据
//因为经过Vue处理之后,.value中保存的才是当初创建时传入的那个原始数据

const add = () => {
   obj2.num = 2 //值改变,视图不会更新
   console.log(obj.value) //输出 { num: 2 }
}
import { toRaw, reactive, ref } from 'vue'

//代理reactive对象
const obj = reactive({ num: 1 })
const obj2 = toRaw(obj)

const add = () => {
  obj2.num = 2 //值改变,视图不会更新
}

//代理ref创建的对象

const obj = ref({ num: 1 })
const obj2 = toRaw(obj.value) 
//与reactive不同的是,需要用.value去获取原始数据
//因为经过Vue处理之后,.value中保存的才是当初创建时传入的那个原始数据

const add = () => {
   obj2.num = 2 //值改变,视图不会更新
   console.log(obj.value) //输出 { num: 2 }
}

isRef

  • 用于判断数据是否是ref创建的,Vue3创建ref的时候会增加__v_isRef: true属性来标识ref数据
js
import { ref, isRef } from 'vue'

const val = ref(1)

console.log(isRef(val)) //true
import { ref, isRef } from 'vue'

const val = ref(1)

console.log(isRef(val)) //true

isReactive

  • 判断数据对象是否是 reactive
js
import { reactive, isReactive } from 'vue'

const obj = reactive({ foo: 1 })

console.log(isReactive(obj)) //true
import { reactive, isReactive } from 'vue'

const obj = reactive({ foo: 1 })

console.log(isReactive(obj)) //true

isReadonly

  • 判断数据对象是否是readonly只可读
js
import { readonly, isReadonly } from 'vue'

const val = readonly({ foo: 1 })

console.log(isReadonly(val)) //true
import { readonly, isReadonly } from 'vue'

const val = readonly({ foo: 1 })

console.log(isReadonly(val)) //true

isProxy

  • 用于判断对象是否是reactive 或 readonly 创建的代理对象
js
import { readonly, reactive, isProxy } from 'vue'

const obj = reactive({ foo: 1 })
const val = readonly({ foo: 1 })

console.log(isProxy(obj)) //true
console.log(isProxy(val)) //true
import { readonly, reactive, isProxy } from 'vue'

const obj = reactive({ foo: 1 })
const val = readonly({ foo: 1 })

console.log(isProxy(obj)) //true
console.log(isProxy(val)) //true

computed

  • 跟vue2是计算属性
js
//写法-
const val = ref(1)

//vue3中计算属性的函数中如果只传入一个回调函数,表示的是get
const doubule = computed(() => val.value * 2)

const add = () => {
  val.value = 3
  console.log(doubule.value) // 6 需要通过.value去访问
}
//写法二
const val = ref(1)
var obj = { foo: 1 }
const doubule = computed({
  get() {
     //dobule的返回值
     return obj.foo * 2
  },
  set(value) {
     //写你的逻辑代码
     val.value++
     obj.foo = val.value
  }
//写法-
const val = ref(1)

//vue3中计算属性的函数中如果只传入一个回调函数,表示的是get
const doubule = computed(() => val.value * 2)

const add = () => {
  val.value = 3
  console.log(doubule.value) // 6 需要通过.value去访问
}
//写法二
const val = ref(1)
var obj = { foo: 1 }
const doubule = computed({
  get() {
     //dobule的返回值
     return obj.foo * 2
  },
  set(value) {
     //写你的逻辑代码
     val.value++
     obj.foo = val.value
  }

watch

  • watch监听某个属性的变化,一旦发生变化,就会触发对应的回调函数执行,在里面进行一些具体的操作,从而达到事件监听的效果。
  • 需手动传入监听的数据,返回新值和旧值。
  • 一共传入三个参数:
    • 第一个参数是:选择要监听的属性。
    • 第二个参数是:设置的回调函数。即监听到变化时应该执行的函数 。
    • 第三个参数是:可以设置deep (深度监听) 其值为true和false。还可以设置immediate (是否以当前值执行回调函数) 其值为true和false。
  • 与vue2不同的是 vue2需要通过computed计算才会返回新值和旧值,否则返回的都是新值。
js
import { ref, reactive, watch } from 'vue'
//用法一、监听ref创建的数据
const val = ref(0)

watch(val,(newVal, oldVal) => {
     console.log(newVal) //1 输出新值
     console.log(oldVal) //0 输出旧值
  },
  {
    immediate: false, //是否在初始化监听
    deep: false //是否开启深度监听
  }
)
const add = () => {
   val.value = 1 //值改变
}

//用法二、监听多个ref创建的数据
let mes = ref("会好的");
let mess = ref("我是谁");
watch(
  [mes, mess],
  (newValue, odlValue) => {
    console.log("变化----", newValue, odlValue);
  },
  { immediate: true }
);
setTimeout(()=>{
  mes.value = "会更好的"
  mess.value = "我是谁啊"
},200)

//第三种情况 监听reactive创建的数据
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  rea,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  },
  { immediate: true }
);

//用法四、监听reactive创建数据的属性(基本数据)
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  () => rea.name,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  }
);
setTimeout(() => {
  rea.name = "我是谁啊";
  console.log("改变rea.obj", rea);
}, 200);

//用法五、监听reactive创建数据的属性(引用数据)
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  () => rea.obj,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  },
  {
    deep:true
  }
);
setTimeout(() => {
  rea.obj.salary = 18
  console.log("改变rea.obj", rea);
}, 200);
import { ref, reactive, watch } from 'vue'
//用法一、监听ref创建的数据
const val = ref(0)

watch(val,(newVal, oldVal) => {
     console.log(newVal) //1 输出新值
     console.log(oldVal) //0 输出旧值
  },
  {
    immediate: false, //是否在初始化监听
    deep: false //是否开启深度监听
  }
)
const add = () => {
   val.value = 1 //值改变
}

//用法二、监听多个ref创建的数据
let mes = ref("会好的");
let mess = ref("我是谁");
watch(
  [mes, mess],
  (newValue, odlValue) => {
    console.log("变化----", newValue, odlValue);
  },
  { immediate: true }
);
setTimeout(()=>{
  mes.value = "会更好的"
  mess.value = "我是谁啊"
},200)

//第三种情况 监听reactive创建的数据
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  rea,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  },
  { immediate: true }
);

//用法四、监听reactive创建数据的属性(基本数据)
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  () => rea.name,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  }
);
setTimeout(() => {
  rea.name = "我是谁啊";
  console.log("改变rea.obj", rea);
}, 200);

//用法五、监听reactive创建数据的属性(引用数据)
let rea = reactive({
  name: "我是谁",
  obj: {
    salary: 20,
  },
});
watch(
  () => rea.obj,
  (newValue, oldValue) => {
    console.log("变化----", newValue, oldValue);
  },
  {
    deep:true
  }
);
setTimeout(() => {
  rea.obj.salary = 18
  console.log("改变rea.obj", rea);
}, 200);

watchEffect

  • watchEffect也是监听数据变化。
  • 当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
  • 与watch不同的是:
    • 不需要手动传入依赖
    • 每次初始化都会执行
    • 无法获取到原值,只能得到变化后的值
  • 一共有两个参数:
    • 第一个参数effect --- 也是函数 --- 也有参数:叫onInvalidate,也是一个函数,用于清除 effect 产生的副作用。
    • 第二个参数options --- 主要作用是指定调度器,即何时运行副作用函数,配置项有:
      • flush:(默认pre(更新前),post (更新后), sync (同步,低效不建议使用))
      • onTrack 和 onTrigger 选项可用于调试一个侦听器的行为(当然只开发阶段有效)
js
import { ref, reactive, watchEffect } from 'vue'
//用法一、
const obj = reactive({ foo: 1 })

const val = ref(0)

watchEffect(() => {
      console.log(obj.foo)
      console.log(val.value)
})

const stop = watchEffect(() => {
  /* ... */
})

// 手动停止依赖
stop()

const add = () => {
  val.value++
  obj.foo++ 
}
//用法二、watchEffect接受一个函数作为参数 ,可用于清除副作用
watchEffect(async () => {
  const data = await fetch(obj.foo);
});

//当 obj.foo 变化后,意味着将会再次发送请求,那么之前的请求怎么办呢?是否应该将之前的请求标记为 invalidate

watchEffect(async (onInvalidate) => {
  let validate = true;
  onInvalidate(() => {
    validate = false;
  });
  const data = await fetch(obj.foo);
  if (validate) {
    /* 正常使用 data */
  } else {
    /* 说明当前副作用已经无效了,抛弃即可 */
  }
});
watchEffect((onInvalidate) => {
  let timer = getList(title.value);
  // 清除副作用
  onInvalidate(() => {
    clearTimeout(timer);
  });
});

// 在组件更新后触发,这样你就可以访问更新的 DOM。
// 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。(默认pre(更新前),post (更新后), sync (同步,低效不建议使用))
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: "post",
  }
);
import { ref, reactive, watchEffect } from 'vue'
//用法一、
const obj = reactive({ foo: 1 })

const val = ref(0)

watchEffect(() => {
      console.log(obj.foo)
      console.log(val.value)
})

const stop = watchEffect(() => {
  /* ... */
})

// 手动停止依赖
stop()

const add = () => {
  val.value++
  obj.foo++ 
}
//用法二、watchEffect接受一个函数作为参数 ,可用于清除副作用
watchEffect(async () => {
  const data = await fetch(obj.foo);
});

//当 obj.foo 变化后,意味着将会再次发送请求,那么之前的请求怎么办呢?是否应该将之前的请求标记为 invalidate

watchEffect(async (onInvalidate) => {
  let validate = true;
  onInvalidate(() => {
    validate = false;
  });
  const data = await fetch(obj.foo);
  if (validate) {
    /* 正常使用 data */
  } else {
    /* 说明当前副作用已经无效了,抛弃即可 */
  }
});
watchEffect((onInvalidate) => {
  let timer = getList(title.value);
  // 清除副作用
  onInvalidate(() => {
    clearTimeout(timer);
  });
});

// 在组件更新后触发,这样你就可以访问更新的 DOM。
// 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。(默认pre(更新前),post (更新后), sync (同步,低效不建议使用))
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: "post",
  }
);

customRef

  • 自定义 ref,常用来定义需要异步获取的响应式数据
js
import { customRef, defineComponent, ref } from 'vue'
// 自定义hook防抖的函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间.默认是200毫秒
function useDebouncedRef<T>(value: T, delay = 200) {
  // 准备一个存储定时器的id的变量
  let timeOutId: number
  return customRef((track, trigger) => {
    return {
      // 返回数据的
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      // 设置数据的
      set(newValue: T) {
        // 清理定时器
        clearTimeout(timeOutId)
        // 开启定时器
        timeOutId = setTimeout(() => {
          value = newValue
          // 告诉Vue更新界面
          trigger()
        }, delay)
      },
    }
  })
}

const keyword = useDebouncedRef('abc', 500)
import { customRef, defineComponent, ref } from 'vue'
// 自定义hook防抖的函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间.默认是200毫秒
function useDebouncedRef<T>(value: T, delay = 200) {
  // 准备一个存储定时器的id的变量
  let timeOutId: number
  return customRef((track, trigger) => {
    return {
      // 返回数据的
      get() {
        // 告诉Vue追踪数据
        track()
        return value
      },
      // 设置数据的
      set(newValue: T) {
        // 清理定时器
        clearTimeout(timeOutId)
        // 开启定时器
        timeOutId = setTimeout(() => {
          value = newValue
          // 告诉Vue更新界面
          trigger()
        }, delay)
      },
    }
  })
}

const keyword = useDebouncedRef('abc', 500)

PropType

  • 更好的推断TS类型
js
//父组件
<CompositionAPI :list="[{id:1,num:2}]"></CompositionAPI>

//子组件
import { PropType } from "vue";
interface myList {
  id: number;
  num: number;
}

defineProps({
  list: Array as PropType<myList[]>,
});
//父组件
<CompositionAPI :list="[{id:1,num:2}]"></CompositionAPI>

//子组件
import { PropType } from "vue";
interface myList {
  id: number;
  num: number;
}

defineProps({
  list: Array as PropType<myList[]>,
});

withDefaults&defineProps & defineEmits

  • 在setup中直接用于接受props和emit,可做ts类型推导
  • withDefaults给props设置默认值
  • 只能在<script setup>中使用
js
interface Props {
  /** banner类型 */
  type?: '1' | '2' | '3';
  /** 主题颜色 */
  color?: string;
}

withDefaults(defineProps<Props>(), {
  type: '1',
  color: '#409eff'
});

// Emits
interface Emits {
  (e: 'close', id: number): void
  (e: 'show', name: string, age: number): void
}
const emit = defineEmits<Emits>();

emit('close', 1)
emit('show', '1', 2)

//参数相同时
const emit = defineEmits<(e: 'close' | 'show', id: number) => void>()

emit('close', 1)
emit('show', 1)
interface Props {
  /** banner类型 */
  type?: '1' | '2' | '3';
  /** 主题颜色 */
  color?: string;
}

withDefaults(defineProps<Props>(), {
  type: '1',
  color: '#409eff'
});

// Emits
interface Emits {
  (e: 'close', id: number): void
  (e: 'show', name: string, age: number): void
}
const emit = defineEmits<Emits>();

emit('close', 1)
emit('show', '1', 2)

//参数相同时
const emit = defineEmits<(e: 'close' | 'show', id: number) => void>()

emit('close', 1)
emit('show', 1)

defineAsyncComponent

  • 用于引入一部组件
js
import { defineAsyncComponent } from 'vue' 
const CompositionAPI = defineAsyncComponent( () => import('./CompositionAPI.vue') )
import { defineAsyncComponent } from 'vue' 
const CompositionAPI = defineAsyncComponent( () => import('./CompositionAPI.vue') )

style vars

  • 支持将组件状态驱动的 CSS 变量注入到“单个文件组件”样式中。
js
<template>
  <div class="m-CompositionAPI">
    <div class="text">测试style vars</div>
  </div>
</template>

<script setup lang="ts">
const color = "#3b6af9";
</script>

<style lang="less">
.m-CompositionAPI {
  .text {
    color: v-bind(color);
  }
}
</style>
<template>
  <div class="m-CompositionAPI">
    <div class="text">测试style vars</div>
  </div>
</template>

<script setup lang="ts">
const color = "#3b6af9";
</script>

<style lang="less">
.m-CompositionAPI {
  .text {
    color: v-bind(color);
  }
}
</style>

provide && inject

  • 与 Vue2中的 provide 和 inject 作用相同,只不过在Vue3中需要手动从 vue 中导入
  • 这里简单说明一下这两个方法的作用:
    • provide :向子组件以及子孙组件传递数据。接收两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值
    • inject :接收父组件或祖先组件传递过来的数据。接收一个参数 key,即父组件或祖先组件传递的数据名称
js
// A.vue
<script>
import {provide} from 'vue'
export default {
    setup() {
        const obj= {
            name: '前端印象',
            age: 22
        }
        // 向子组件以及子孙组件传递名为info的数据
        provide('info', obj)
    }
}
</script>
// B.vue
<script>
import {inject} from 'vue'
export default {
    setup() {   
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>
// C.vue
<script>
import {inject} from 'vue'
export default {
    setup() {   
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>

//在ts中需要类型检查
//新建context.ts
import { InjectionKey } from "vue";
exprot interface UserInfo {
  id: number,
  name: string
}
export const injectKeyUser: InjectionKey<UserInfo> = Symbol()

//parent.vue
import { provide } from 'vue'
import { injectKeyUser } from './context'
export default {
  setup(){
  	provide(injectKeyUser, {
  	   id: 7, 
  	   name:'小燕'
  	})
  }
}

//child.vue
import { inject } from 'vue'
import { injectKeyUser } from './context'
export default {
  setup(){
  	const user = inject(injectKeyUser)
  	//UserInfo | undefined
  	if(user){
  		console.log(user.name) //小燕
  	}
  }
}
// A.vue
<script>
import {provide} from 'vue'
export default {
    setup() {
        const obj= {
            name: '前端印象',
            age: 22
        }
        // 向子组件以及子孙组件传递名为info的数据
        provide('info', obj)
    }
}
</script>
// B.vue
<script>
import {inject} from 'vue'
export default {
    setup() {   
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>
// C.vue
<script>
import {inject} from 'vue'
export default {
    setup() {   
        // 接收A.vue传递过来的数据
        inject('info')  // {name: '前端印象', age: 22}
    }
}
</script>

//在ts中需要类型检查
//新建context.ts
import { InjectionKey } from "vue";
exprot interface UserInfo {
  id: number,
  name: string
}
export const injectKeyUser: InjectionKey<UserInfo> = Symbol()

//parent.vue
import { provide } from 'vue'
import { injectKeyUser } from './context'
export default {
  setup(){
  	provide(injectKeyUser, {
  	   id: 7, 
  	   name:'小燕'
  	})
  }
}

//child.vue
import { inject } from 'vue'
import { injectKeyUser } from './context'
export default {
  setup(){
  	const user = inject(injectKeyUser)
  	//UserInfo | undefined
  	if(user){
  		console.log(user.name) //小燕
  	}
  }
}

getCurrentInstance

  • 获取当前实例,和vue2中的this相同,用于setup函数中(不建议使用) image.png
js
import { getCurrentInstance } from "vue";
const internalInstance  = getCurrentInstance();
console.log(internalInstance);
import { getCurrentInstance } from "vue";
const internalInstance  = getCurrentInstance();
console.log(internalInstance);

vue-router里的hooks

  • useRoute :返回当前路由地址。相当于在模板中使用 $route
  • useRouter:返回 router实例。相当于在模板中使用 $router
  • 必须在setup中使用
js
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
 
console.log(route.params.id)
router.push('/xxx/xxx')
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
 
console.log(route.params.id)
router.push('/xxx/xxx')

vuex 里的 hooks

  • 获取 store 实例
  • 目前vuex是4.x,官方提出了一个 Vuex 5 的全新提案部分灵感是借鉴于Pinia

1642576267(1).jpg

js
import { useStore } from 'vue-router'

const store = useStore()
import { useStore } from 'vue-router'

const store = useStore()

在script setup lang="ts"语法糖中自定义指令

js
//focus.ts
import type { Directive } from 'vue';
const focus:Directive = {
  mounted(el:HTMLDivElement) {
    el.focus()
  }
}
export default focus

//用法
<script setup lang="ts">
import focus from '@/direvtive/focus'
</script>
//focus.ts
import type { Directive } from 'vue';
const focus:Directive = {
  mounted(el:HTMLDivElement) {
    el.focus()
  }
}
export default focus

//用法
<script setup lang="ts">
import focus from '@/direvtive/focus'
</script>

Fragment、Suspense、Teleport新增组件

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中好处: 减少标签层级, 减小内存占用
  • Suspense组件可用于当组件引入使用时,网络不好可能会出现的延迟加载时候,跟React的Suspense组件是一样的效果只不过写的方式不一样。

image.png

  • Teleport组件可以挂载到任意div下,不受父级影响
js
//Fragment用法
<template>
  <h2>测试1</h2>
  <h2>测试2</h2>
</template>

//Suspense用法
/*
1.default插槽 用于成功后显示
2.fallback插槽 用于加载之前和加载失败显示
3.Suspense下的子组件setup可以使用 async
4.子组件返回一个promise
*/
<Suspense>
    <template #default>
      <div :key="1">
        click here
      </div>
    </template>
    <template #fallback>
      <h1>Waiting for server data</h1>
    </template>
</Suspense>

//用法Teleport
<Teleport to="#app">
    /* ... */
</Teleport>
//Fragment用法
<template>
  <h2>测试1</h2>
  <h2>测试2</h2>
</template>

//Suspense用法
/*
1.default插槽 用于成功后显示
2.fallback插槽 用于加载之前和加载失败显示
3.Suspense下的子组件setup可以使用 async
4.子组件返回一个promise
*/
<Suspense>
    <template #default>
      <div :key="1">
        click here
      </div>
    </template>
    <template #fallback>
      <h1>Waiting for server data</h1>
    </template>
</Suspense>

//用法Teleport
<Teleport to="#app">
    /* ... */
</Teleport>

注册store仓库

js
// 创建自己的store

import { InjectionKey, App, inject } from 'vue'

interface MyState {
  id: number
}

export const myStateKey: InjectionKey<MyState> = Symbol()

export function createMyStore() {
  const state = {}

  return {
    install(app: App) {
      app.provide(myStateKey, state)
    },
  }
}

export function useMyStore() {
  return inject(myStateKey)!
}

// 用法
// main.js  const App = createApp(App) app.use(createMyStore())
// 任意页面  const state = useMyStore()
// 创建自己的store

import { InjectionKey, App, inject } from 'vue'

interface MyState {
  id: number
}

export const myStateKey: InjectionKey<MyState> = Symbol()

export function createMyStore() {
  const state = {}

  return {
    install(app: App) {
      app.provide(myStateKey, state)
    },
  }
}

export function useMyStore() {
  return inject(myStateKey)!
}

// 用法
// main.js  const App = createApp(App) app.use(createMyStore())
// 任意页面  const state = useMyStore()

defineExpose

  • 使用 script setup 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例比如你需要 通过ref拿到子组件的方法,则需要通过 defineExpose将方法暴露出来。
js
<script setup lang="ts">
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>
<script setup lang="ts">
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

useSlots和 useAttr

  • 分别可以访问到$slots$attrs
js
<script setup lang="ts">
// 可以访问到$slots和$attrs
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>
<script setup lang="ts">
// 可以访问到$slots和$attrs
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

拓展