Vue3筑基—下篇

58 阅读8分钟

一、vue3的生命周期

vue3.0可以继续使用vue2.x中的生命周期钩子,但是有两个被更名

  • beforeDestory改名为beforeUmount
  • destoryed改名为unmounted

vue3.0也提供了Composition API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

  • berforeCreate ====> setup()
  • created ========> setup()
  • beforeMount =====> onBeforeMount()
  • mounted ========> onMounted
  • beforeUpdate =====> onBeforeUpdate
  • updated =========> onUpdated
  • beforeUnmount ====> onBeforeUnmount
  • unmounted =======> onUnmounted

组合式API生命周期在前,配置项生命周期再后,但是一般要么用组合式的要么用配置式,不建议混合使用。

<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
  </div>
</template>

<script>
import { ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  // emits:['hello'],
  setup() {
    console.log("setup");
    let sum = ref(0)
    //通过组合式API形式使用生命周期
    onBeforeMount(() => {
      console.log("onBeforeMount-----");
    })
    onMounted(() => {
      console.log("onMounted-----");
    })
    onBeforeUpdate(() => {
      console.log("onBeforeUpdate-----");
    })
    onUpdated(() => {
      console.log("onUpdated-----");
    })
    onBeforeUnmount(() => {
      console.log("onBeforeUnmount-----");
    })
    onUnmounted(() => {
      console.log("onUnmounted-----");
    })
    return {
      sum,
    };
  },
  //#region
  //通过配置项形式使用生命周期
  beforeCreate() {
    console.log("beforeCreate");
  },
  created() {
    console.log("created");
  },
  beforeMount() {
    console.log("beforeMount");
  },
  mounted() {
    console.log("mounted");
  },
  beforeUpdate() {
    console.log("beforeUpdate");
  },
  updated() {
    console.log("updated");
  },
  beforeUnmount() {
    console.log("beforeUnmount");
  },
  unmounted() {
    console.log("unmounted");
  },
  //#endregion
};
</script>

二、自定义hook函数

什么式hook?本质是一个函数,把setup函数中使用的Composition API进行了封装。 类似于vue2.x中的mixin。自定义hook的优势:复用代码,让setup中的逻辑更清晰易懂。

image.png

import { reactive, onMounted, onBeforeUnmount } from "vue";

export default function usePoint() {
  // 创建一个响应式对象来存储x和y坐标
  let point = reactive({
    x: 0,
    y: 0,
  });

  // 当发生点击事件时,更新x和y坐标的函数
  function onSavePoint(event) {
    point.x = event.pageX;
    point.y = event.pageY;
  }

  // 当组件挂载时,添加点击事件监听器
  onMounted(() => {
    window.addEventListener("click", onSavePoint);
  });

  // 当组件即将卸载时,移除点击事件监听器
  onBeforeUnmount(() => {
    window.removeEventListener("click", onSavePoint);
  });

  // 返回响应式的point对象
  return point;
}

<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
    <h2>点击的位置:x:{{ point.x }},y:{{ point.y }}</h2>
  </div>
</template>

<script>
import usePoint from "../hooks/usePoint"
import { ref} from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  setup() {
    console.log("setup");
    let sum = ref(0)
    let point = usePoint()
    return {
      sum,
      point
    };
  },
 };
</script>

三、toRef和toRefs

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
  • 语法:const name = toRef(person,'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部使用
  • 扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
    <h3>姓名:{{ name }} 年龄:{{ age }} 薪资:{{ salary }}</h3>
  </div>
  <button @click="name += '~';age++;salary++ " >修改person</button>
  <hr>
  {{ human }}
  <h3>
    代号:{{ idName }} 爱好:{{ hobby }} 工作类型:{{ job.type.jobName }}
  </h3>
  <button @click="idName += '~';hobby += '~';job.type.jobName += '~' " >修改human</button>
</template>

<script>
import { ref,reactive,toRef,toRefs } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  setup() {
    console.log("setup");
    let sum = ref(0)
    let person = reactive({
      name:'张三',
      age:18,
      job:{
        salary:20000
      }
    })
    let human = reactive({
      idName:'李四',
      hobby:'篮球、足球',
      job:{
        type:{
          jobName:'前端工程师',
        }
      }
    })

    return {
      human,
      sum,
      name: toRef(person,'name'),
      age: toRef(person,'age'),
      salary:toRef(person.job,'salary'),
      ...toRefs(human)
    };
  },
 };
</script>

四、其他Composition API

1、shallowReactive与shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
  • 什么时候用
    如果有一个对象数据,结构比较深,但变化时张三外层属性变化===>shallowReactive
    如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换===>shallowRef
<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <h2>x:{{ x.y }}</h2>
    <!-- 可以修改 -->
    <button @click="sum++" >增加数据</button>
    <!-- 无法修改 -->
    <button @click="x.y++" >修改x</button>
  </div>
  <hr>
  {{ human }}
  <h3>
    代号:{{ idName }} 爱好:{{ hobby }} 工作类型:{{ work.type.jobName }}
  </h3>
  <!-- 离谱,这里的jobName也会跟着改变 -->
  <button @click="idName += '~';hobby += '~';work.type.jobName ++" >修改human</button> 
  <!-- 但是这里的时正常的jobName无法修改,符合reactiveRef的用法 -->
  <button @click="work.type.jobName ++" >  修改work </button>
</template>

<script>
import { toRefs ,shallowReactive,shallowRef} from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  setup() {
    let sum = shallowRef(0)

    let x = shallowRef({
      y:1
    })

    let human = shallowReactive({
      idName:'李四',
      hobby:'篮球、足球',
      work:{
        type:{
          jobName: 300,
        }
      }
    })

    return {
      x,
      ...toRefs(human),
      sum,
    };
  },
 };
</script>

2、readonly和shallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读,对象深度内容也无法修改)
  • shallowReadonly:让一个响应式数据变为只读的(浅只读,对象深度内容可修改)
  • 应用场景:不希望数据被修改时
<template>
  <div>
    <h2>求和:{{ sum }}</h2>
    <button @click="sum++" >增加数据</button>
  </div>
  <hr>
  <h3>
    代号:{{ idName }} 爱好:{{ hobby }} 工作类型:{{ work.type.jobName }}
  </h3>
  <button @click="idName += '~';hobby += '~';work.type.jobName ++" >修改human</button> 
  <button @click="work.type.jobName ++" >  修改work </button>
</template>

<script>
import { toRefs ,ref,reactive,readonly,shallowReadonly} from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  setup() {
    let sum = ref(0)

    let human = reactive({
      idName:'李四',
      hobby:'篮球、足球',
      work:{
        type:{
          jobName: 300,
        }
      }
    })
    sum = readonly(sum)//无法修改
    // human = readonly(human) //无法修改
    human = shallowReadonly(human)//jobName还是改变,不支持嵌套内容修改
    return {
      ...toRefs(human),
      sum,
    };
  },
 };
</script>

3、toRaw和markRaw

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

markRaw
作用:标记一个对象,使其永远不会再成为响应式对象。
应用场景:1、有些值不应该设置为响应式,例如复杂的第三方库等。 2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

<template>
  <h3> 代号:{{ idName }} </h3>
  <h3>爱好:{{ hobby }} </h3>
  <h3>工作类型:{{ work.type.jobName }}</h3>
  <h3>车数据{{ human.car }}</h3>
  <button @click="idName += '~';hobby += '~';work.type.jobName ++" >修改human</button> 
  <button @click="getOriginHuman" > 获取最原始human </button>
  <button @click="add" >添加车</button>
  <button @click="changePrice"  v-if="human.car" >改车钱</button>
</template>

<script>
import { toRefs ,reactive,toRaw,markRaw} from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Demo",
  setup() {

    let human = reactive({
      idName:'李四',
      hobby:'唱跳rap',
      work:{
        type:{
          jobName: 300,
        }
      }
    })
    function getOriginHuman(){
      human = toRaw(human)
      console.log(human);
    }
    function add(){
      let car = {
        name:'奔驰',
        price: 10000
      }
      human.car = markRaw(car)//添加之后car属性不再时响应式
    }
    function changePrice(){
      human.car.price ++
      console.log(human.car.price);//修改的只是普通值,不是响应式,也不参与页面更新
    }
    return {
      human,
      ...toRefs(human),
      getOriginHuman,
      add,
      changePrice
    };
  },
 };
</script>

4、customRef,重点理解示例

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


<template>
  <div>
    <input type="text" v-model="content" >
    {{ content }}
  </div>
</template>

<script>
import { customRef } from 'vue';
export default {
  name: 'App',
  setup(){
    
    function myRef(value,delay){
      let timer
      return customRef((track,trigger)=>{
      return {
        get(){
          console.log(`有人读取了数据,${value}`);
          track()//通知vue追踪value的变化(提前和get商量一下,让其认为这个value有用并执行get)
          return value

        },
        set(newValue){
          console.log(`有人修改了数据,${newValue}`);
          clearTimeout(timer)
          //每次修改数据都会清除上一次的定时器,对应一个变量timer防止快速添加数据时,显示内容会有bug,防抖动
          timer = setTimeout(() => {
            value = newValue
            trigger() //通知vue重新解析模板
          }, delay);
         
        }
      }
    })
    }
    let content = myRef('hello',1000)//自定义Ref
    return {
      content
    }
  }
}
</script>

5、provide与inject

  • 作用:实现祖与后代组件间通信,子组件也可以注入但是一般用props传递数据
  • 套路:父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据
  • 具体写法:

1、祖组件中:

setup(){
    .....
    let car = reactive({name:'奔驰',price:'200000'})
    provide('car',car)//两个参数,(命名,传入数据)
}

2、孙组件中:

setup(){
    ....
    const car = inject('car')
    return {car}
}

6、响应式数据的判断

  • isRef:检查一个值是否为ref对象
  • isReactive:检查一个对象是否由reactive创建的响应式代理
  • isReadOnly:检查一个对象是否由readonly创建的只读代理
  • isProxy:检查一个对象是否由reactive或者readonly方法创建的代理

<template>
  <div class="app" >
    <h1>测试判断</h1>
  </div>
</template>

<script>
import { reactive,ref,readonly, isReactive,isReadonly,isRef, isProxy } from 'vue'
export default {
  name: 'App',
  setup(){
    let sum = ref(0)
    let person = reactive({
      name:'张三',
      age:18,
    })
    let car = readonly({
      name:'奔驰',
      price:40
    })
     
    let person2 = readonly(person)

    console.log(isRef(sum)); //true
    console.log(isReactive(person));//true
    console.log(isReadonly(car));//true
    console.log(isReadonly(person2));//true
    console.log(isProxy(person));//true
    console.log(isProxy(person2));//true
    console.log(isProxy(sum));//false

    return {person,sum,car}
  }
}
</script>
<style>

</style>

7、Composition API的优势

  1. option API存在的问题:使用传统Option API中,新增或修改一个需求,就需要分别在data,method,computed里修改
  2. Composition API优势:我们可以更加优雅的组件我们的代码,函数。让相关功能的代码更加有序的组织在一起。

五、新的组件

1、Fragment

  • 在Vue2中:组件必须有一个根标签
  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存使用

2、Teleport(适用弹窗场景)

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

<teleport to='body(移动位置,只相对于body;不止是body,也可以使用选择器)' >
    <div v-if="show" class="mask">
        <div class="dialog" >
            <h3>我是一个弹窗</h3>
            <button @click="isShow=false" >关闭弹窗</button>
        </div>
    </div>
</teleport>

3、Suspense

等待异步组件渲染一些额外内容,让应用有更好的用户体验。(从用户角度想是有点像骨架屏,等待的组件可能数据量大,短时间内无法显示页面,但为了更好的用户体验,先默认显示出一个指定的加载状态,组件加载出来了,这个加载状态也就消失)

  • 使用步骤:
    异步引入组件
    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('./components/Child.Vue'))

使用Suspense包裹组件,并配置好default与fallback


父组件

    
<template>
  <h1>父组件</h1>
  <Suspense>
    <template v-slot:default>
      <Child/>
    </template>
    <template v-slot:fallback >
      <h4>加载中...</h4>
    </template>
  </Suspense>
</template>

<script>
// import Child from "./components/Child.vue";//静态引入
import { defineAsyncComponent } from 'vue';
const Child = defineAsyncComponent(() => import('./components/Child.vue'))//动态引入即异步引入(Child这个组件比App出现的晚)
export default {
  name: 'App',
  components:{
    Child
  },
  setup(){
  
  }
}
</script>

子组件

<template>
  <div>
    <h1>我是孩子节点{{ sum }}</h1>
  </div>
</template>
<script>
import { ref } from "vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "Child",
  //配合父组件Suspense
  async setup() {
    let sum = ref(111);
    let p = new Promise((resolve) => {
      setTimeout(() => {
        resolve({ sum });
      }, 500);
    });
    return await p;
  },
};
</script>

六、其他改变

  • 将全局的API,即Vue.XXX调整到应用实例(app)上
2.x全局API(Vue)3.x实例API(app)
Vue.config.xxxapp.config.xxx
Vue.config.productionTip(生产提示)移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
  • data选项应始终声明为一个函数
  • 过度类名的更改
  • 移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes
  • 移除v-on.native修饰符
  • 移除过滤器(filter)
  • .....剩下的找官方文档