Vue3 知识点

214 阅读6分钟

结构分析

  • main.js
  1. 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
  2. 创建应用实例对象——app(类似于之前的Vue2中的vm,但是比vm更‘轻’)
  • template标签最外层可以不用加根标签

常用Composition API

setup

  • Vue3中的一个新的配置项,值是一个函数,组件中所用到的数据,方法等,都要配置在setup中
  • setup函数的两种返回值: 1.若返回一个对象,则对象中的属性,方法,在模板中均可直接使用
<template>
  <div>姓名{{ name }}</div>
  <div>年龄{{ age }}</div>
  <button @click="sayHello">打招呼</button>
</template>

<script>
export default {
  name: "App",
  //此处暂时不考虑响应式
  setup() {
    let name = "genji";
    let age = 19;

    function sayHello() {
      console.log(`我叫${name},年龄${age}`);
    }
    return {
      name,
      age,
      sayHello,
    };
  },
};
</script>

2.若返回一个渲染函数:则可以自定义渲染内容

import { h } from "vue";  //需要手动引入渲染函数
...
return () => h("h1", "守望先锋");

注意:setup不能是一个async函数,因为加了async后,返回值不再是return的对象,而是promise

ref函数

定义一个响应式的数据

<template>
  <div>姓名{{ name }}</div>
  <div>年龄{{ age }}</div>
  <button @click="change">修改人名</button>
  <div>工作{{ job.type }}</div>
  <div>薪水{{ job.salary }}</div>
</template>

<script>
import { ref } from "vue";
export default {
  name: "App",
  setup() {
    //将数据生成引用对象
    const name = ref("genji");
    const age = ref(19);
    const job = ref({
      type: "前端",
      salary: "20k",
    });

    function change() {
      name.value = "Mcree";
      job.value.type = "后端";
    }
    return {
      name,
      age,
      change,
      job,
    };
  },
};
</script>

reactive函数

定义一个对象类型的响应式数据(基本类型不要用它,用ref函数) ,接收一个对象或数组,返回一个代理对象(Proxy的实例对象),内部基于Proxy实现,通过代理对象操作源对象内部数据进行操作

<script>
import { ref, reactive } from "vue";
export default {
  name: "App",
  setup() {
    //将数据生成引用对象
    const name = ref("genji");
    const age = ref(19);
    const job = reactive({
      type: "前端",
      salary: "20k",
    });

    function change() {
      name.value = "Mcree";
      job.type = "后端";
    }
    return {
      name,
      age,
      change,
      job,
    };
  },
};
</script>

Vue3实现响应式原理

  1. 通过Proxy代理,拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,属性的删除等
  2. 通过Reflect反射,对被代理对象的属性进行操作
const person = {name:'genji',age:18}

//模拟Vue3中实现响应式
const p = new Proxy(person,{
  get(target,propName){
    console.log('有人读取了某个属性');
    return Reflect.get(target,propName)
  },
  set(target,propName,value){
    console.log('有人修改或者新增了某个属性');
    return Reflect.set(target,propName,value)
  },
  deleteProperty(target,propName){
    console.log('有人删除了某个属性');
    return Reflect.deleteProperty(target,propName)
  }
})

reactive和ref对比

  • 定义数据角度
  1. ref用来定义基本类型数据
  2. reactive用来定义对象或数组类型数据
  3. ref也可以用来定义对象或数组类型数据,它内部会自动通过reactive转为代理对象
  • 原理角度
  1. ref仍然是通过Object.defineProperty()的get与set来实现响应式(数据劫持)
  2. reactive通过Proxy来实现响应式,并通过Reflect操作源对象内部的数据
  • 从使用角度对比
  1. ref定义的数据,操作数据需要用.value,读取数据时模板中直接读取,不需要.value
  2. reactive定义的数据,操作数据与读取数据,均不需要.value

setup的两个注意点

  • setup执行的时机 在beforeCreate之前执行一次,this是undefined

  • setup的参数

  1. props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
  2. context:上下文对象
    1. attrs:值为对象,包含:组件外部传递过来,但没有在props中配置声明的属性,相当于 this.$attrs
    2. slots:收到的插槽内容,相当于this.$slots ,如果想用具名插槽,要用v-slot,不要直接slot:' '
    3. emit:分发自定义事件的函数,相当于this.$emit

computed计算属性

  • 与Vue2中 computed配置功能一致
<template>
  第一个名字:<input v-model="person.firstName" />
  <br />
  第二个名字:<input type="text" v-model="person.lastName" />
  <br />
  <span>全名:{{ person.fullName }}</span>
  全名: <input type="text" v-model="person.fullName" />
</template>

<script>
import { reactive, computed } from "vue";
export default {
  name: "Demo",
  setup() {
    let person = reactive({
      firstName: "genji",
      lastName: "源氏",
    });
    //简略写法
    person.fullName = computed(() => {
      return `${person.firstName}-${person.lastName}`;
    });
    //完整写法
    person.fullName = computed({
      get() {
        return `${person.firstName}-${person.lastName}`;
      },
      set(value) {
        const arr = value.split("-");
        person.firstName = arr[0];
        person.lastName = arr[1];
      },
    });
    return {
      person,
    };
  },
};
</script>

watch

  • 监视简单变量时,可以获得oldVal,监视复杂变量时,无法获取oldVal
<template>
  <div>总数为:{{ sum }}</div>
  <button @click="sum++"></button>
  <div>{{ msg }}</div>
  <button @click="msg += '!'">加!</button>
  <div>{{ person.name }}</div>
  <button @click="person.name += '~'">加~</button>
  <div>{{ person.age }}</div>
  <button @click="person.age++">加1</button>
</template>

<script>
import { ref, watch, reactive } from "vue";
export default {
  name: "Demo",
  setup() {
    let sum = ref(0);
    let msg = ref("守望先锋");
    let person = reactive({
      name: "genji",
      age: 18,
      job: {
        salary: 20,
      },
    });

    //情况一:监视ref定义的一个响应式数据
    watch(sum, (newVal, oldVal) => console.log("sum变化了", newVal, oldVal), { immediate: true });
    //情况二:监听多个ref定义的响应式数据,可以写多个watch,也可以写在一个watch里,newVal和oldVal为数组
    watch([sum, msg], (newVal, oldVal) => console.log("sum或msg变化了", newVal, oldVal));
    /**
     * 情况三:监视reactive所定义的一个响应式数据的全部属性
     * 1、无法获取正确的oldVal
     * 2、强制开启了深度监视(deep无效)
     */
    watch(person, (newVal, oldVal) => console.log("person变化了", newVal, oldVal));
    //情况四:监视reactive所定义的一个响应式数据中的某个属性,需要用函数返回需要监听的属性
    watch(
      () => person.name,
      (newVal, oldVal) => console.log("person的name变化了", newVal, oldVal)
    );
    //情况五:监视reactive所定义的一个响应式数据中的多个属性
    watch([() => person.name, () => person.age], (newVal, oldVal) => console.log("person的name或者age变化了", newVal, oldVal));
    //特殊情况,监视reactive所定义的响应式数据中的一个深层次属性,即一个对象属性,需要配置deep
    watch(
      () => person.job,
      (newVal, oldVal) => console.log("person的job变化了", newVal, oldVal),
      { deep: true }
    );

    return {
      sum,
      msg,
      person,
    };
  },
};
</script>

watchEffect函数

  • 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
  • 有点像computed
    1. 但computed更注重计算出来的值,所以必须有返回值
    2. watchEffect更注重过程,不用写返回值
watchEffect(() => {
      const x = sum.value;  //回调中用到了sum,即监视sum属性
      console.log(123);
    });

Vue3生命周期

  • Vue3中可以继续使用2中的生命周期钩子,但是最后两个被更名:
    1. beforeDestroy --> beforeUnmount 2.destroyed --> unmounted
  • Vue3也提供了组合式API形式的生命周期钩子,除了beforeCreate,created都可以写在setup中
...
import {onBeforeMount,onMounted,...} from 'Vue'
export default {
    name:'',
    setup(){
        onBeforeMount(()=>console.log(onBeforeMount))
        onMounted(()=>console.log(onBeforeMount))
    }
}

自定义hook

  • 本质是一个函数,把setup函数中使用的composition API进行了封装,类似于mixin 创建/src/hooks文件夹,以use开头,简历自定义hook js文件,
//src/hooks/usePoint.js
import {reactive,...} from 'vue'


function usePoint(){
    ...
    相关数据,
    相关方法
    相关生命周期钩子
    return{...}
}

export default usePoint

在需要用的文件中引入

import usePoint from '...'
...
setup(){
    const point = usePoint()
}

toRef,toRefs

  • 创建一个ref对象,其value值指向另一个对象中的某个属性
  • toRefs和toRef功能一致,但可以批量创建多个ref对象
<template>
  <div>{{ name }}</div>
  <button @click="name += '~'">加~</button>
  <div>{{ age }}</div>
  <button @click="age++">加1</button>
  <div>{{ salary }}</div>
</template>

<script>
import { reactive, toRef } from "vue";
export default {
  name: "Demo",
  setup() {
    let person = reactive({
      name: "genji",
      age: 18,
      job: {
        salary: 20,
      },
    });

    return {
      name: toRef(person, "name"),
      age: toRef(person, "age"),
      salary: toRef(person.job, "salary"),
    };
  },
};
</script>
  • toRefs
<template>
  <div>{{ name }}</div>
  <button @click="name += '~'">加~</button>
  <div>{{ age }}</div>
  <button @click="age++">加1</button>
  <div>{{ job.salary }}</div>
</template>

...

return {
      //   name: toRef(person, "name"),
      //   age: toRef(person, "age"),
      //   salary: toRef(person.job, "salary"),
      ...toRefs(person),
    };

shallowReactive 和 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理

readonly 和 shallowReadonly

  • readonly:让一个响应式数据变为只读的(深只读)
  • shallowReadonly:浅只读 应用场景:不希望数据被修改时,例:从别处引用了响应式数据
...
let person = reactive({
      name: "genji",
      age: 18,
      job: {
        salary: 20,
      },
    });
    person = readonly(person);

toRaw 和 markRaw

toRaw:

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

markRaw:

  • 标记一个对象,使其永远会不会再成为响应式对象
  • 场景:有些值不应被设置为星影视的,滴入复杂的第三方库等,当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
<script>
import { reactive, toRaw, markRaw, toRefs } from "vue";
export default {
  name: "Demo",
  setup() {
    let person = reactive({
      name: "genji",
      age: 18,
      job: {
        salary: 20,
      },
    });

    function showRawPerson(){
        const p = toRaw(person)
        console.log(p);
    }

    function addCar(){
        const car  = {name:'benz',price:40}
        person.car = markRaw(car)
    }


    return {
      ...toRefs(person),
      showRawPerson,
      addCar
    };
  },
};
</script>

customRef

  • 创建一个自定义的ref,并对其依赖想跟踪和更新触发进行显示控制
<script>
import { ref, customRef } from "vue";
export default {
  name: "Demo",
  setup() {
    function myRef(value) {
      let timer;
      return customRef((track, trigger) => {
        return {
          get() {
            track(); //通知Vue追踪value(返回值)的变化
            return value;
          },
          set(newValue) {
            clearTimeout(timer);
            timer = setTimeout(() => {
              value = newValue;
              trigger(); //通知Vue重新解析模板
            }, 500);
          },
        };
      });
    }

    // let text = ref("genji"); //使用Vue提供的ref
    let text = myRef("genji"); //使用自定的ref

    return { text };
  },
};
</script>

provide 和 inject

  • 实现祖孙组件间通信
  • 父组件通过provide来提供数据,后代组件通过inject来使用数据
//祖组件
<template>
  <div class="app">
    <div>{{name}}</div>
    <div>{{price}}</div>
    <Demo />
  </div>
</template>

<script>
import { reactive, toRefs } from '@vue/reactivity';
import Demo from "./components/Demo.vue";
import { provide } from '@vue/runtime-core';
export default {
  name: "App",
  components: { Demo },
  setup(){
    let car = reactive({
      name:'benz',
      price:40
    })

    provide('car',car)   //传递数据给后代组件

    return {...toRefs(car)}
  }
};
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>
//后代组件
<template>
  <div class="child">child
    <div>{{name}}</div>
    <div>{{price}}</div>
  </div>
</template>

<script>
import { inject, toRefs } from '@vue/runtime-core';
export default {
  name: "child",
  setup(){
    let car =  inject('car')   //接收祖组件传递的数据

    return{...toRefs(car)}
  }
};
</script>

<style>
.child {
  background-color: skyblue;
  padding: 10px;
}
</style>

响应式数据的判断

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

Fragment

Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中

减少标签层级,减少内存占用

Teleport

  • 将组件html结构移动到指定位置
<teleport to='移动位置'>  //可以直接html,body;或者标签选择器#id等
    ....
</teleport>

Suspense

  • 等待异步组件时渲染一些额外内容,让应有有更好的用户体验
// 父组件
<template>
  <div class="app">
    <Suspense>
      <template v-slot:default>  //default插槽包裹要展示的组件
        <Demo />
      </template>
      <template v-slot:fallback>  //fallback插槽包裹异步组件未渲染时展示的内容
        <div>加载中</div>
      </template>
    </Suspense>
    
  </div>
</template>

<script>
import {defineAsyncComponent} from 'vue'
const Demo = defineAsyncComponent(()=>import('./components/Demo.vue'))
// import Demo from "./components/Demo.vue";
export default {
  name: "App",
  components: { Demo },
};
</script>

<style>
.app {
  background-color: gray;
  padding: 10px;
}
</style>
//子组件      子组件异步引入,且使用了suspense的时候,setup可以返回promise
<template>
  <div class="demo">demo
  </div>
</template>

<script>
import { ref } from '@vue/reactivity';
export default {
  name: "demo",
  setup(){
    let sum = ref(0)
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
        resolve({sum})
      },2000)
    })
  }
  // async setup(){
  //   let sum = ref(0)
  //   let p = new Promise((resolve,reject)=>{
  //     setTimeout(()=>{
  //       resolve({sum})
  //     },2000)
  //   })
  //   return await p
  // }
};
</script>

<style>
.demo {
  background-color: pink;
  padding: 10px;
}
</style>

全局API转移

  • Vue.config.xxx ———— app.config.xxx
  • Vue.config.productionTip ———— 移除
  • Vue.component ———— app.component
  • Vue.directive ———— app.directive
  • Vue.mixin ———— app.mixin
  • Vue.use ———— app.use
  • Vue.prototype ———— app.config.globalProperties

其他改变

  • data选项应始终被声明为一个函数
  • 过渡类名的变更
.v-enter => .v-enter-from
.v-leave => .v-leave-from
  • 移除keyCode作为v-on的修饰符(@keyup.13),同时也不再支持config.keyCodes
  • 移除v-on.native
//父组件
<Child @close='handleClose' @click='handleClick' />

//子组件中声明自定义事件
export default{
    emits:['close']   // 生命过的为自定义事件,未声明的(click)为原生事件
}
- 移除过滤器 filter