• Vue3详解


    目录

    1、核心·-响应式

    2、getCurrentInstance

    3、5个内置组件

    3-1、 Transition组件

    基于 CSS 的过渡效果#

    CSS 的 animation#

    同时使用 transition 和 animation#

    深层级过渡与显式过渡时长#

    性能考量#

    JavaScript 钩子#

    可复用过渡效果#

    出现时过渡#

     过渡模式

    3-2、TransitionGroup组件

    和  的区别#

    3-3、 KeepAlive组件

    基本使用#

    在 DOM 模板中使用时,它应该被写为 。

    包含/排除#

    最大缓存实例数#

    缓存实例的生命周期#

    3-4、Teleport 组件

    3-5、Suspense组件

    4、响应式-进阶

    5、由于少了 export,没法传参,也不方便暴露接口,所以就增加了三个工具方法

    defineProps

    defineEmits

    defineExpose

    语法

    defineProps :

    1、适用于父组件向子组件传递属性

    2、带默认值的defineProps

    defineEmits

    defineExpose

    子组件:detail.vue

    父组件:main.vue

    6、其他的Composition API

    6.1.shallowReactive与shallowRef

    2.readonly与shallowReadonly

    3.toRaw与markRaw

    4.customRef

    7.provide与inject

    8、TypeScript 与组合式 API

    9、Vuex与组合式API


    组合式Api要求我们要将响应式的变量声明在setup函数中,setup函数是一个专门用于组合式Api的钩子函数。而且我们需要借助reactive函数来创建响应式的对象。

    setup函数的两种返回值:

    若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点关注)
    若返回一个渲染函数:则可以自定义渲染内容。

    setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性

    1、核心·-响应式

    setup的两个注意点
    setup执行的时机:在beforeCreate之前执行一次,this是undefined
    setup的参数

    1. props:值为对象,包含: 组件外部传递过来,且组件内部声明接收了属性
    2. context:上下文对象
    3. attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于 this.$attrs
    4. slots:收到插槽的内容,相当于$slots
    5. emit: 分发自定义事件的函数,相当于this.$emit
    1. //父组件
    2. <template>
    3.   <img alt="Vue logo" src="./assets/logo.png" />
    4.   <HelloWorld msg="传递吧" @hello="hello">
    5.     <template v-slot:cacao>
    6.       <span>是插槽吗span>
    7.     template>
    8.     <template v-slot:qwe>
    9.       <span>meiyouspan>
    10.     template>
    11.   HelloWorld>
    12. template>
    13. //子组件
    14. export default {
    15.     name: 'test3',
    16.     props: ['msg'],
    17.     emits:['hello'],
    18.     //这里setup接收两个参数,一个是props,一个是上下文context
    19.     setup(props,context){
    20.         /**
    21.          * props就是父组件传来的值,但是他是Porxy类型的对象
    22.          * >Proxy:{msg:'传递吧'}
    23.          * 可以当作我们自定义的reactive定义的数据
    24.          */
    25.         /**
    26.          * context是一个对象 包含以下内容:
    27.          * 1.emit触发自定义事件的 
    28.          * 2.attrs 相当于vue2里面的 $attrs 包含:组件外部传递过来,但没有在props配置中声明的属性
    29.          * 3.slots 相当于vue2里面的 $slots
    30.          * 3.expose 是一个回调函数
    31.          */
    32.         console.log(context.slots);
    33.         let person = reactive({
    34.             name: '张三',
    35.             age: 17,
    36.         })
    37.         
    38.         function changeInfo(){
    39.             context.emit('hello', 666)
    40.         }
    41.         //返回对象
    42.         return {
    43.             person,
    44.             changeInfo
    45.         }
    46.         //返回渲染函数(了解) 这个h是个函数
    47.         //return () => h('name','age')
    48.     }
    49. }
    1. <script>
    2. import {reactive} from 'vue'
    3. export default {
    4. setup() {
    5. const datas = reactive({ num: 0 })
    6. function add1(){
    7. datas.num++
    8. }
    9. return {
    10. datas,
    11. add1
    12. }
    13. }
    14. }
    15. script>
    16. <style>
    17. style>
    如上,即为组合式Api的典型写法。
    
    在组合式Api中一定记得将需要暴露到模板的对象return出去(单独使用setup的时候)。
    更多声明响应式对象的方法
    前面我们已经了解了data方法(选项式Api中使用)、reactive(组合式Api中使用)两种不同的声明响应式对象的方式,那还有其他方式吗?
    
    ref
    
    因为reactive方法仅仅对,对象,数组、Map、Set类型生效,对于string、number、boolean类型是无效的。
    
    鉴于这种情况,vue3引入了ref方法来帮助开发者创建任意类型的响应式对象。
    
    toRef
    
    当我们想要将reactive创建的响应式对象的某个属性单独传递出来时,我们可以使用toRef实现
    
    1. <script setup>
    2. import {reactive,toRef} from 'vue'
    3. const datas = reactive({name: 'phyger',age: 18, num: 0})
    4. // 通过toRef将datas中的num转换为了ref对象
    5. const tms = toRef(datas,'num')
    6. const add1=()=>{
    7. // 在此对tms的值进行修改
    8. tms.value++
    9. }
    10. script>
    toRefs
    类似toRef,toRefs可以将reactive声明的响应式变量的所有元素都转换为ref对象。
    
    1. <script setup>
    2. import {reactive,toRefs} from 'vue'
    3. const datas = reactive({name: 'phyger',age: 18, num: 0})
    4. // 通过toRef将datas中的num转换为了ref对象
    5. const tms = toRefs(datas)
    6. const add1=()=>{
    7. // 在此对tms的值进行修改
    8. tms.num.value++
    9. }
    10. script>
    toRefs的返回值是一个字典,格式为:
    
    {name: ObjectRefImpl, age: ObjectRefImpl, num: ObjectRefImpl}
    如上,我们已经将reactive定义的对象元素全部转换为了ref对象,而且属性值的变化会同步更新到reactive的对象中。
    
    isRef
    vue3为我们提供了判断时否为ref对象的方法,即isRef。
    
    结果:
    简化在setup中的return
    前面我们有说,在组合式Api中,我们必须将模板需要的属性暴露出去。通常大多数的属性都是需要暴露出去的,为了方便,vue3支持在单文件组件(SFC)的场景下,使用
  • 请注意:

  • onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用。

  • 这两个钩子不仅适用于  缓存的根组件,也适用于缓存树中的后代组件。

3-4、Teleport 组件

3-5、Suspense组件

4、响应式-进阶

  1. <script lang="ts" setup>
  2. import { useDebouncedRef } from "./debounceRef";
  3. import {
  4. shallowRef,
  5. reactive,
  6. ref,
  7. watch,
  8. shallowReactive,
  9. getCurrentInstance,
  10. triggerRef,
  11. watchEffect,
  12. isReactive,
  13. effectScope,
  14. computed,
  15. } from "vue-demi";
  16. //shallowRef
  17. const text = useDebouncedRef("hello", 1000);
  18. const data = shallowRef({ count: 1 });
  19. //不会触发响应式-修改数据
  20. const clickBtnNoChange = () => {
  21. data.value.count++;
  22. console.log(data.value.count++, " data.value.count++===========");
  23. };
  24. //会修改数据源
  25. const clickBtnChange = () => {
  26. const random = Math.floor(Math.random() * 10 - 1);
  27. data.value = { count: random };
  28. };
  29. const state = shallowRef({ count: 1 });
  30. state.value = { count: 2 };
  31. const shallow = shallowRef({
  32. greet: "Hello world",
  33. });
  34. // 触发该副作用第一次应该会打印 "Hello, world"
  35. watchEffect(() => {
  36. console.log(shallow.value.greet);
  37. });
  38. // 这次变更不应触发副作用,因为这个 ref 是浅层的
  39. shallow.value.greet = "hello universe";
  40. // 打印 "hello, universe"
  41. triggerRef(shallow);
  42. // shallowReactive
  43. const stateReactive = shallowReactive({
  44. foo: 1,
  45. nested: {
  46. bar: 2,
  47. },
  48. });
  49. //不会触发响应式-修改数据
  50. const clickBtnNoChange1 = () => {
  51. //但下层嵌套对象不会被转化为响应式
  52. isReactive(stateReactive.nested); //false
  53. //不是响应式的
  54. stateReactive.nested.bar++;
  55. };
  56. //会修改数据源
  57. const clickBtnChange1 = () => {
  58. stateReactive.foo++;
  59. console.log(stateReactive.foo++, "stateReactive.foo++===========");
  60. };
  61. //更改状态自身的属性是响应式的
  62. stateReactive.foo++;
  63. //但下层嵌套对象不会被转化为响应式
  64. isReactive(stateReactive.nested); //false
  65. //不是响应式的
  66. stateReactive.nested.bar++;
  67. // effectScope()
  68. const disposables=[]
  69. const counter=ref(1)
  70. const scope=effectScope()
  71. scope.run(()=>{
  72. const doubled=computed(()=>counter.value*2)
  73. console.log(doubled,'doubled===============')
  74. watch(doubled,()=> {
  75. console.log(doubled.value)
  76. }
  77. )
  78. watchEffect(()=>console.log("Count:",doubled.value))
  79. })
  80. //处理掉当前作用域内的所有 effect
  81. scope.stop()
  82. console.log(scope.stop(),'scope.stop()=============')
  83. disposables.push(()=>scope.stop())
  84. console.log(disposables,'disposables=============')
  85. script>
  1. import {customRef} from 'vue'
  2. export function useDebouncedRef(value:any,delay=200){
  3. let timeout:any
  4. return customRef((track,trigger)=>{
  5. return {
  6. get(){
  7. track()
  8. return value
  9. },
  10. set(newValue){
  11. clearTimeout(timeout)
  12. timeout=setTimeout(()=>{
  13. value=newValue
  14. trigger()
  15. },delay)
  16. }
  17. }
  18. })
  19. }

5、由于少了 export,没法传参,也不方便暴露接口,所以就增加了三个工具方法

  • defineProps

  • defineEmits

  • defineExpose

注意,这三个工具方法只是帮助 Vue 编译器构建组件,它们不会出现在最终代码里,我们也不能预期它们会像普通函数那样工作。比如下面这段代码,就得不到常见的结果:

  1. const props = defineProps({
  2. userMenu: {
  3. type: Array,
  4. default() {
  5. return []
  6. }
  7. }
  8. })
  9. console.log(props) // 该对象中的 userName 总是有值
  10. console.log(props.userMenu) // 该对象始终是一个空数据
  1. 因为 VueMVVM 框架,它的视图会在数据变化后自动渲染,于是通常情况下,
  2. props 里的值什么时候被填充并不重要,Vue 开发团队也不想追求 defineProps 工作的一般化。
  3. 所以使用目前版本,上面这段代码,访问到的 props 是 reactive 对象,数据被填充后就能看到
  4. userName 里有值;
  5. 而 props.userMenu 在访问时还没有被填充,所以得到的是 default() 返回的默认值,一直是空的。
  6. 同时大家还要知道,console.log() 输出的是对象的指针,而非快照。
  7. 所以里面的值只跟你展开时有关,跟运行时关系不大

语法

在 script setup 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits ,它们具备完整的类型推断并且在 script setup 中是直接可用的

defineProps :

1、适用于父组件向子组件传递属性

子组件

  1. // 定义Props
  2. const props = defineProps<{
  3. result: number,
  4. name: string
  5. }>()

父组件 

<Detail name="结果" :result="1">Detail>

2、带默认值的defineProps

适用于带默认值的Props,经测试不能与defineProps在同一组件同时定义。

子组件 

  1. interface IProps {
  2. labels?: string[]
  3. result: number,
  4. name:string
  5. }
  6. // 定义带默认值的Props
  7. const defaultProps = withDefaults(defineProps<IProps>(), {
  8. name: 'hello',
  9. result:0,
  10. labels: () => ['one', 'two']
  11. })

父组件

  1. <Detail name="结果">Detail>         

defineEmits

适用于父组件向子组件传递方法

子组件 

  1. <hr />
  2. <button @click="btnReset">重置button>
  3. <hr />
  4. // 定义Emits
  5. const emits = defineEmits<{
  6. (e: 'add', id: number): void
  7. (e: 'reset', value: number): void
  8. }>()
  9. const btnAdd = () => {
  10. emits('add',2)
  11. }
  12. const btnReset = () => {
  13. emits("reset",0)
  14. }

夫组件

  1. <Detail @add="add" @reset="reset">Detail>
  2. const result = ref(0);
  3. const add = (num:number)=>{
  4. result.value+=num
  5. }
  6. const reset = (num:number)=>{
  7. result.value = num
  8. }

defineExpose

适用于子组件向父组件暴露方法和属性,父组件通过子组件示例进行调用。

子组件 

  1. // 定义
  2. Expose const exposeStr = ref("") defineExpose({ exposeStr })

父组件 

  1. <Detail ref="detail">Detail>
  2. // 必须跟组件ref保持一致
  3. const detail = ref()
  4. setTimeout (() => {
  5. detail.value.exposeStr = "exposeStr"
  6. },1000)

完整代码 

子组件:detail.vue

  1. <template>
  2. <div>
  3. <hr />
  4. {{defaultProps.name}} - {{defaultProps.result}} - {{defaultProps.labels}}
  5. <hr />
  6. <button @click="btnAdd">添加button>
  7. <hr />
  8. <button @click="btnReset">重置button>
  9. <hr />
  10. {{exposeStr}}
  11. div>
  12. template>
  13. <script setup lang="ts">
  14. import { ref,defineProps ,defineEmits,defineExpose} from "vue"
  15. // 定义Props
  16. // const props = defineProps<{
  17. // result: number,
  18. // name: string
  19. // }>()
  20. interface IProps {
  21. labels?: string[]
  22. result: number,
  23. name:string
  24. }
  25. // 定义带默认值的Props
  26. const defaultProps = withDefaults(defineProps<IProps>(), {
  27. name: 'hello',
  28. result:0,
  29. labels: () => ['one', 'two']
  30. })
  31. // 定义Emits
  32. const emits = defineEmits<{
  33. (e: 'add', id: number): void
  34. (e: 'reset', value: number): void
  35. }>()
  36. const btnAdd = () => {
  37. emits('add',2)
  38. }
  39. const btnReset = () => {
  40. emits("reset",0)
  41. }
  42. // 定义Expose
  43. const exposeStr = ref("")
  44. defineExpose({
  45. exposeStr
  46. })
  47. script>
  48. <style lang="scss" scoped>style>

父组件:main.vue

  1. <template>
  2. <div>
  3. <Detail ref="detail" name="结果" :result="result" @add="add" @reset="reset">Detail>
  4. div>
  5. template>
  6. <script setup lang="ts">
  7. import Detail from './detail.vue'
  8. import {ref} from 'vue'
  9. const result = ref(0);
  10. const add = (num:number)=>{
  11. result.value+=num
  12. }
  13. const reset = (num:number)=>{
  14. result.value = num
  15. }
  16. // 必须跟子组件ref名保持一致
  17. const detail = ref()
  18. setTimeout (() => {
  19. // 调用子组件属性并修改值
  20. detail.value.exposeStr = "exposeStr"
  21. },1000)
  22. script>

6、其他的Composition API

6.1.shallowReactive与shallowRef

shallowReactive:只处理对象最外层属性的响应式(浅响应式)只考虑第一层数据的响应式。
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理,传递基本数据类型的话跟ref没有任何区别,ref是可以进行对象的响应式处理的
我们正常的ref创建的数据,里面的.value是一个proxy,而shallowRef创建的数据 .value里面是一个object数据类型,所以不会响应式数据

什么时候使用?:
如果有一个对象数据,结构比较深,但变化时只是外层属性变化 ===> shallowReactive
如果有一个对象数据,后续功能不会修改对象中的属性,而是生新的对象来替换 ===> shallowRef

2.readonly与shallowReadonly

readonly:让一个响应式的数据变成只读的(深只读)
shallowReadonly: 让一个响应式数据变成只读的(浅只读)
应用场景:不希望数据被修改的时候

3.toRaw与markRaw

toRaw
作用:将一个由reactive生成的响应式对象转换为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象
使用场景:

  1. 1.有些值不应被设置成响应式的,例如复杂的第三方类库等
  2. 2.当渲染具有不可变数据的大列表时候,跳过响应式转换可以提高性能
  1. import {reactive,toRaw,markRaw} from 'vue'
  2. setup(){
  3.     let person = reactive({
  4.         name: '张三',
  5.     })
  6.     function showRawPerson(){
  7.         const p = toRaw(person)
  8.         p.age++
  9.         console.log(p)
  10.     }
  11.     function addCar(){
  12.         let car = {name: '奔驰'}
  13.         person.car = markRaw(car) //一旦这么做时候,他就永远不能当成响应式数据去做了
  14.     }
  15. }


4.customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
实现防抖效果: