Vue3.0 基础

概述

Vue2.0的局限性:

  • 当组件变得庞大复杂起来后,代码可阅读性降低
  • 代码复用有明显的缺陷
  • TypeScript支持非常有限

版本对比

vue2vue3区别

vue3基于 vue2按需加载组件加快项目加载速度,原理是通过把 vue 库里的方法独立封装一个一个的函数,当组件需要使用时单独加载工具函数即可

createApp

调用 createApp 返回一个应用实例。该实例提供了一个应用上下文。应用实例挂载的整个组件树共享相同的上下文,该上下文提供了之前在 Vue 2.x 中“全局”的配置。

由于 createApp 方法返回应用实例本身,因此可以在其后链式调用其它方法

  1. /**
  2. * {
  3. * component: ƒ component(name, component),
  4. * config: Object,
  5. * directive: ƒ directive(name, directive),
  6. * mixin: ƒ mixin(mixin),
  7. * mount: (containerOrSelector) => {…},
  8. * provide: ƒ provide(key, value),
  9. * unmount: ƒ unmount(),
  10. * use: ƒ use(plugin, ...options),
  11. * version: "3.2.23",
  12. * _component: {},
  13. * _container: null,
  14. * _context: {app: {…}, config: {…}, mixins: Array(0), components: {…},
  15. * directives: {…}, …},
  16. * _instance: null,
  17. * _props: null,
  18. * _uid: 0,
  19. * get config: ƒ config(),
  20. * set config: ƒ config(v)
  21. * }
  22. */

component

  1. //如果传入 参数,返回应用实例。
  2. const app = createApp({});
  3. const comp = app.component('my-component', {
  4. name: 'MyComponet',
  5. setup() {}
  6. })
  7. //返回的是应用实例
  8. console.log(comp);

mount

挂载应用实例的根组件

  1. //返回根组件实例
  2. const app = createApp({})
  3. const rootIns = app.mount('#app')
  4. console.log(rootIns);
  5. //Proxy{...}

unmont

卸载应用实例的根组件

  1. const app = createApp({})
  2. app.mount('#app')
  3. // 挂载5秒后,应用将被卸载
  4. setTimeout(() => app.unmount(), 5000)

use

安装 Vue.js 插件。如果插件是一个对象,它必须暴露一个 install 方法。如果它本身是一个函数,它将被视为安装方法。

该安装方法将以应用实例作为第一个参数被调用。传给 use 的其他 options 参数将作为后续参数传入该安装方法。

当在同一个插件上多次调用此方法时,该插件将仅安装一次。

  1. import { createApp } from 'vue'
  2. import MyPlugin from './plugins/MyPlugin'
  3. const app = createApp({})
  4. app.use(MyPlugin)
  5. app.mount('#app')

config

包含应用配置的对象, 在挂载应用之前,你可以修改其 属性

config.errorHandler

  1. app.config.errorHandler = (err, vm, info) => {
  2. // 处理错误
  3. // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  4. }

指定一个处理函数,来处理组件渲染方法和侦听器执行期间抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和应用实例。

globalProperties

添加一个可以在应用的任何组件实例中访问的全局 property。组件的 property在命名冲突具有优先权

这可以代替 Vue 2.xVue.prototype 扩展:

  1. // 之前(Vue 2.x)
  2. Vue.prototype.$http = () => {}
  3. // 之后(Vue 3.x)
  4. const app = createApp({})
  5. app.config.globalProperties.$http = () => {}
  6. //使用
  7. const { ctx } = getCurrentInstance();
  8. console.log(ctx.utils.plus(1, 2));

组合 API

hooksvue3底层提供的钩子实现函数方式(不像 vue2 options API),开发者只需写提供钩子里面的逻辑

基于函数抽离的组合各种方法函数实现高内聚的情况(2.0 有横向拆分,各个组件都有如 data,method,computed)

CompositionAPI

解决问题是vue 2.0中当组件变得庞大复杂起来后,代码可阅读性降低

问题:什么时候使用CompositionAPI?

  • 希望有最理想的 TypeScript支持
  • 当组件的内容变得庞大复杂起来的时候,并且希望通过功能来管理组件
  • 可能会有一些想要在不同的组件里使用的代码(代码复用)
  • 团队倾向新的CompositionAPI
  1. //vue2.0写法:
  2. export default {
  3. data() {
  4. return {
  5. search,
  6. sorting
  7. }
  8. },
  9. methods: {
  10. search,
  11. sorting
  12. },
  13. props: {
  14. search,
  15. sorting
  16. }
  17. }
  1. //vue3.0写法:(可选/不影响2.0使用)
  2. export default {
  3. setup() {
  4. //Composition API语法
  5. search,
  6. sorting
  7. }
  8. }
  1. export default {
  2. setup() {
  3. //Composition functions
  4. return {
  5. ...useSearch(),
  6. ...useSorting()
  7. }
  8. }
  9. }
  10. //搜索
  11. function useSearch() {}
  12. //排序
  13. function useSorting() {}

vue2.0代码复用的三种方式:

  1. mixin提取公共代码到数组管理
  2. Mixin Factories工厂
  3. Scoped Slots作用域插槽方式
  1. //方式一:mixins
  2. //存在优点:
  3. //1.根据不同的功能进行归类
  4. //存在缺点:
  5. //1.容易产生重复定义冲突
  6. //2.复用性不高
  7. const productSearchMixin = {
  8. data() {
  9. search
  10. },
  11. methods: {
  12. search
  13. }
  14. }
  15. const resultSortMixin = {
  16. data() {
  17. sorting
  18. },
  19. methods: {
  20. sorting
  21. }
  22. }
  23. export default {
  24. mixins: [productSearchMixin, resultSortMixin]
  25. }
  1. //方式二:Mixin Factories
  2. //存在优点:
  3. //1.提高可复用性
  4. //存在缺点:
  5. //1.命名空间需要有严格的规范
  6. //2.暴露的属性需要进入到Mixin工厂函数的定义文件里查看
  7. //3.Factories不能动态生成
  8. //组件部分:
  9. import searchMixinFactory from '@mixins/factories/search';
  10. import sortingMixinFactory from '@mixins/factories/sorting';
  11. export default {
  12. mixins: [
  13. searchMixinFactory({
  14. namespace: 'productSearch',
  15. xxx
  16. }),
  17. sortingMixinFactory({
  18. namespace: 'productSorting',
  19. xxx
  20. }),
  21. ]
  22. }
  23. //逻辑部分:
  24. export default function sortingMixinFactory(obj) { }
  1. //方式三:Scoped Slots
  2. //存在优点:
  3. //1.解决Mixins大多数问题
  4. //存在缺点:
  5. //1.配置需要模板完成,理想的状态模板只定义需要渲染的内容
  6. //2.缩进降低代码的可阅读性
  7. //3.暴露的属性只能够在模板里使用
  8. //4.3个组件比1个组件,性能开销上升
  9. //generic-search.vue组件部分:
  10. <script>
  11. export default {
  12. props:['getResults']
  13. }
  14. </script>
  15. <template>
  16. <div>
  17. <slot v-bind="{query, results, run}"></slot>
  18. </div>
  19. </template>
  20. //generic-sorting.vue组件部分:
  21. <script>
  22. export default {
  23. props:['input', 'options']
  24. }
  25. </script>
  26. <template>
  27. <div>
  28. <slot v-bind="{options, index, output}"></slot>
  29. </div>
  30. </template>
  31. //search.vue组件部分:
  32. <template>
  33. <div>
  34. <generic-search
  35. :get-results="getProducts"
  36. :v-slot="productSearch"
  37. >
  38. <generic-sorting
  39. :input="productSearch.results"
  40. :options="resultSortingOptions"
  41. v-slot="resultSorting"
  42. ></generic-sorting>
  43. </generic-search>
  44. </div>
  45. </template>
  46. <script>
  47. export default {}
  48. </script>

问题:如何使用CompositionAPI?

  1. //安装
  2. npm i -S @vue/composition-api
  3. //引用
  4. import VueCompositionApi from '@vue/composition-api';
  5. //注册
  6. Vue.use(VueCompositionApi);

setup

一个组件选项,在组件被创建之前props 被解析之后执行。

它是组合式 API的入口。

setup返回一个对象,对象里的属性将被合并到render函数执行期上下文里,所以视图模板可以使用对象里的数据

当视图模板访问对象属性时,不需要.value写法

setup方法必须返回 view模板里定义的数据和方法

  1. export default {
  2. setup(props, context){
  3. //必须返回视图模板需要的属性和方法
  4. return {}
  5. },
  6. }

setup函数在以下之前执行:

  1. Components/props/data/methods/computed/lifecycle

setup写法:

  1. import { watch } from 'vue';
  2. export default {
  3. setup(props, context){
  4. //监听props里面的属性
  5. watch(()=>{
  6. console.log(props.name);
  7. });
  8. //因为不能访问this,但通过context可访问:
  9. //context.attrs/slots/parent/root/listeners
  10. },
  11. props:{
  12. name: String
  13. }
  14. }

参数

  • 接收的第一个参数为:被解析后的props
  • 接收的第二个参数为:执行期上下文
  1. setup(props){}

注意: 接收的props已经被响应式代理了对象, 且不要解构props否则失去响应式

  1. console.log(props);
  2. //Proxy{...}
  1. console.log(ctx);
  2. /**
  3. * {
  4. * attrs: (...),
  5. * emit: (...), 这里emit代替vue2 this.$emit
  6. * expose: exposed => {…},
  7. * slots: (...),
  8. * get attrs: ƒ attrs(),
  9. * get slots: ƒ slots()
  10. * }
  11. */

参数 2 可以解构使用:

  1. setup(props, {attrs, emit, slots}){...}
  1. setup(props, ctx){
  2. const { attrs, emit, slots } = ctx;
  3. }

在子组件里的setup想要拿到vuex.$store属性和方法时,

  1. //getCurrentInstance:拿状态和方法通过 获取当前实例 的函数方法
  2. //computed函数 计算和保存拿到的状态和方法
  3. import { getCurrentInstance, computed } from 'vue';
  4. export default {
  5. name: 'xxx',
  6. props: {
  7. index: Number
  8. },
  9. setup(props){
  10. //ctx -> store -> state/mutaions
  11. const { ctx } = getCurrentInstance(),
  12. store = ctx.$store;
  13. const changeCityInfo = () => {
  14. //提交给mutations里的方法去操作数据
  15. store.commit('changeCity', props.index);
  16. };
  17. //返回视图模板需要的数据和方法
  18. return {
  19. //拿到state里的数据
  20. curIdx: computed(() => store.state.curIdx),
  21. changeCityInfo
  22. }
  23. }
  24. }

ref

接受一个内部值并返回一个响应式且可变的 ref对象。ref对象仅有一个 .value property,指向该内部值。

  1. /**
  2. * RefImpl{
  3. * dep: undefined,
  4. * __v_isRef: true,
  5. * _rawValue: "张三",
  6. * _shallow: false,
  7. * _value: "张三",
  8. * value: (...)
  9. * }
  10. */
  1. const count = ref(0)
  2. console.log(count.value) // 0

如果将对象分配为 ref值,则它将被 reactive函数处理为深层的响应式对象。

  1. const obj = ref({
  2. a: 1,
  3. b: 2
  4. });
  5. /**
  6. * RefImpl{
  7. * ...,
  8. * value: Proxy{...}
  9. * }
  10. */
  11. //value会做reactive响应式处理

reactive 将解包所有深层的refs同时维持 ref的响应性。

  1. const count = ref(1)
  2. const obj = reactive({ count })
  3. // ref 会被解包
  4. console.log(obj.count === count.value) // true
  5. // 它会更新 `obj.count`
  6. count.value++
  7. console.log(count.value) // 2
  8. console.log(obj.count) // 2
  9. // 它也会更新 `count` ref
  10. obj.count++
  11. console.log(obj.count) // 3
  12. console.log(count.value) // 3
  1. 当将 ref 分配给 reactive property 时,ref 将被自动解包。
  2. const count = ref(1);
  3. const obj = reactive({});
  4. console.log(obj.count === count.value); // false
  5. obj.count = count;
  6. console.log(obj.count); // 1
  7. console.log(obj.count === count.value); // true

Reactive References / refs

响应式引用值作用:

选择性暴露响应式对象数据有利于后期代码维护,也能更好的追踪到模板里的属性定义的位置

ref()

  1. <div>容量:{{ capacity }}</div>
  2. import { ref } from '@vue/composition-api';
  3. export default {
  4. setup(){
  5. //传入原始值并执行会创建ref对象
  6. //ref() => ref对象 => 响应式属性
  7. const capacity = ref(3);
  8. console.log(capacity);
  9. /**
  10. * RefImpl:
  11. * {
  12. * value: 3,
  13. * get value(){}
  14. * set value(){}
  15. * }
  16. */
  17. //必须返回对象才能供模板表达式使用
  18. //不返回会报错:模板使用了但未定义
  19. return { capacity };
  20. }
  21. }

方法定义:

  1. <div>容量:{{ capacity }}</div>
  2. <button @click="increaseCapacity()">增加容量</button>
  3. import { ref } from '@vue/composition-api';
  4. export default {
  5. setup(){
  6. const capacity = ref(3);
  7. function increaseCapacity(){
  8. capacity.value ++;
  9. };
  10. return { capacity, increaseCapacity };
  11. }
  12. }

计算方法:

  1. <p>座位容量:{{ capacity }}</p>
  2. <p>剩余座位容量:{{spacesLeft}}/{{capcity}}</p>
  3. <button @click="increaseCapacity()">增加容量</button>
  4. <h2>参加人员</h2>
  5. <ul>
  6. <li v-for="(name, index) in attending" :key="index">
  7. {{name}}
  8. </li>
  9. </ul>
  10. //引入computed计算函数
  11. import { ref, computed } from '@vue/composition-api';
  12. export default {
  13. setup(){
  14. const capacity = ref(3);
  15. const attending = ref(['小王', '小李', '小张']);
  16. //定义计算函数方法
  17. const spacesLeft = computed(()=>{
  18. return capacity.value - attending.value.length;
  19. });
  20. function increaseCapacity(){
  21. capacity.value ++;
  22. };
  23. //导出spacesLeft计算属性方法
  24. return {
  25. capacity,
  26. attending,
  27. spacesLeft,
  28. increaseCapacity
  29. };
  30. }
  31. }

模板引用:

在使用组合式 API时,响应式引用模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref并从setup返回:

  1. //1.在dom上使用可以拿到dom元素
  2. //2.在组件上使用可以拿到组件实例,可以访问实例里的属性和方法
  3. <div>
  4. <div ref="child">张三</div>
  5. <button @click="changeName">改变名字</button>
  6. </div>
  7. setup(props, ctx) {
  8. //引用模板的固定写法:
  9. const child = ref(null);
  10. const changeName = () => {
  11. // console.log(child.value);
  12. //<div>张三</div>
  13. //修改页面上的文本为
  14. child.value.innerText = "李四";
  15. };
  16. return {
  17. child,
  18. changeName,
  19. };
  20. }

这里我们在渲染上下文中暴露 root,并通过 ref="child",将其绑定到 div作为其 ref

在虚拟 DOM补丁算法中,如果 VNoderef 键对应于渲染上下文中的 ref,则 VNode的相应元素或组件实例将被分配给该 ref的值。这是在虚拟 DOM挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。

作为模板使用的 ref的行为与任何其他 ref一样:它们是响应式的,可以传递到 (或从中返回) 复合函数中。

v-for中的用法:组合式 API模板引用在 v-for 内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理:

  1. <template>
  2. //将divs每一项赋值给el
  3. <li v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
  4. {{ item }}
  5. </li>
  6. </template>
  7. <script>
  8. import { ref, reactive, onBeforeUpdate } from 'vue'
  9. export default {
  10. setup() {
  11. const list = reactive([1, 2, 3])
  12. const divs = ref([])
  13. // 确保在每次更新之前重置ref
  14. onBeforeUpdate(() => {
  15. console.log(divs.value);
  16. //Proxy{0: li, 1: li, 2: li}
  17. console.log(divs.value[0]);
  18. //<li>...<li>
  19. })
  20. return {
  21. list,
  22. divs
  23. }
  24. }
  25. }
  26. </script>

unref

如果参数是一个ref, 则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数。

toRef

可以用来为源响应式对象上的某个 property新创建一个ref ,更多的针对响应式数据

然后,ref可以被传递,它会保持对其源 property的响应式连接。

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const fooRef = toRef(state, 'foo')
  6. //state.foo和fooRef.value相互关联,哪个修改都会同步修改
  7. fooRef.value++
  8. console.log(state.foo) // 2
  9. state.foo++
  10. console.log(fooRef.value) // 3

当你要将 propref传递给复合函数时,toRef 很有用:

  1. //可以自定义composition API
  2. function useDoSth(name) {
  3. return `My name is ${name.value}.`;
  4. }
  5. export default {
  6. setup(props) {
  7. const state = reactive({
  8. name: "张三",
  9. age: 30,
  10. });
  11. const sentence = useDoSth(toRef(state, "name"));
  12. console.log(sentence);
  13. //My name is 张三.
  14. }
  15. }

toRefs

将响应式对象转换为普通对象,其中结果对象的每个 property都是指向原始对象相应 propertyref

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const stateAsRefs = toRefs(state);
  6. // ref 和原始 property 已经“链接”起来了
  7. state.foo++
  8. console.log(stateAsRefs.foo.value) // 2
  9. stateAsRefs.foo.value++
  10. console.log(state.foo) // 3
  1. const state = reactive({
  2. name: "张三",
  3. age: 30,
  4. });
  5. const stateRefs = toRefs(state);
  6. // console.log({ ...stateRefs });
  7. //{name: ObjectRefImpl, age: ObjectRefImpl}
  8. // console.log(stateRefs.name);
  9. //ObjectRefImpl {...}
  10. // console.log(stateRefs.name.value);
  11. //张三

当从组合式函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:

  1. const { name, age } = { ...toRefs(state) };
  2. console.log(name);
  3. console.log(age);

问题:Vue3为什么要使用toRefs?

reactive函数可以将ref定义的属性归并在一起,在模板绑定使用时写法是

  1. <li>姓名:{{reactive函数执行返回的变量名.属性名}}</li>

可以看出在模板里使用时用xxx.属性名的写法比较麻烦,那么如何直接用属性名的写法呢?

可以通过toRefs函数实现, 它可以将多个ref定义的响应式属性/reactive响应式对象通过展开运算符的方式一并return到视图使用

  1. import { reactive, toRefs } from 'vue';
  2. setup(){
  3. const person = reactive({
  4. name: 'lisi',
  5. age: 29
  6. });
  7. return {
  8. ...toRefs(person);
  9. }
  10. }
  11. //视图写法:
  12. <li>{{name}}</li>

reactive

返回对象的响应式副本, 是“深层”影响所有嵌套 property

有一个事件触发或一个视图上或数据上的改变,相对于被绑定方的数据也一起被改变

  1. const state = reactive({
  2. count: 0
  3. });

reactive()

该函数返回的是真正的响应式对象

  1. //reactive()写法:
  2. <p>座位容量:{{ event.capacity }}</p>
  3. <p>剩余座位容量:{{event.spacesLeft}}/{{event.capcity}}</p>
  4. <button @click="increaseCapacity()">增加容量</button>
  5. <h2>参加人员</h2>
  6. <ul>
  7. <li v-for="(name, index) in event.attending" :key="index">
  8. {{name}}
  9. </li>
  10. </ul>
  11. //写法二:toRefs()简写
  12. <p>座位容量:{{ capacity }}</p>
  13. <p>剩余座位容量:{{spacesLeft}}/{{capcity}}</p>
  14. <button @click="increaseCapacity()">增加容量</button>
  15. <h2>参加人员</h2>
  16. <ul>
  17. <li v-for="(name, index) in attending" :key="index">
  18. {{name}}
  19. </li>
  20. </ul>
  21. //引入computed计算函数
  22. import { reactive, computed, toRefs } from '@vue/composition-api';
  23. export default {
  24. setup(){
  25. //reactive()接收一个对象作为参数
  26. const event = reactive({
  27. capacity: 4,
  28. attending: ['小王', '小李', '小张'],
  29. spacesLeft: computed(()=>{
  30. return event.capacity - event.attending.length;
  31. });
  32. });
  33. function increaseCapacity(){
  34. return event.capacity ++;
  35. }
  36. return { event, increaseCapacity };
  37. //写法二:
  38. //toRefs()将响应式对象转换为普通对象
  39. //...平铺开对象
  40. //既可以保持属性响应式,又能进行简写响应式对象平铺
  41. //return { ...toRefs(event), increaseCapacity };
  42. //写法三:
  43. //因为toRefs()方法返回的是一个响应式对象
  44. //所以可以直接返回该对象
  45. //return toRefs();
  46. }
  47. }

readonly

接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

Composition Functions

提取代码,做代码复用的解决方案

解决问题:代码复用有明显的缺陷

  1. //优点:
  2. //1. 代码量减少,能够更容易地把功能从组件内部提取到一个函数里
  3. //2. 因为使用的是函数,使用的是现有的知识
  4. //3. 更灵活,技能感知,自动补全等编辑器里提示的功能利于编写代码
  5. //缺点:
  6. //1.学习low-level API知识来定义Composition Functions
  7. //2. 3.0定义组件的方式变成了2种
  8. //写法:
  9. //其他组件使用:
  10. import useEventSpace from '@/use/event-space';
  11. export default {
  12. setup(props, context){
  13. //执行函数并返回对象
  14. return useEventSpace();
  15. }
  16. }
  17. //定义在组件 src/use/event-space.js
  18. //Composition Function
  19. <script>
  20. import { ref, computed } from '@vue/composition-api';
  21. export default function useEventSpace(){
  22. const capacity = ref(3);
  23. const attending = ref(['小王', '小李', '小张']);
  24. spacesLeft: computed(()=>{
  25. return event.capacity - event.attending.length;
  26. });
  27. function increaseCapacity(){
  28. capacity.value++;
  29. }
  30. return {
  31. capacity,
  32. attending,
  33. spacesLeft,
  34. increaseCapacity
  35. }
  36. }
  37. </script>

生命周期

4.Vue3.0 - 图1

选项式 API的生命周期选项和组合式 API之间的映射

  • beforeCreate -> 使用 setup():

    • 在实例初始化之后、进行数据侦听和事件/侦听器的配置之前同步调用
  • created -> 使用 setup():

    • 在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。
  • beforeMount -> onBeforeMount:

    • 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted -> onMounted:

    • 在实例挂载完成后被调用,这时候传递给 app.mount 的元素已经被新创建的 vm.$el 替换了。如果根实例被挂载到了一个文档内的元素上,当 mounted 被调用时, vm.$el 也会在文档内。 注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等待整个视图都渲染完毕,可以在 mounted 内部使用vm.$nextTick
  • beforeUpdate -> onBeforeUpdate:

    • 在数据发生改变后,DOM被更新之前被调用。这里适合在现有 DOM将要被更新之前访问它,比如移除手动添加的事件监听器。
  • updated -> onUpdated:

    • 在数据更改导致的虚拟 DOM重新渲染和更新完毕之后被调用。当这个钩子被调用时,组件 DOM已经更新,所以你现在可以执行依赖于 DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或侦听器取而代之。注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等待整个视图都渲染完毕,可以在 updated 内部使用vm.$nextTick
  • beforeUnmount -> onBeforeUnmount:

    • 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的
  • unmounted -> onUnmounted:

    • 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载
  • errorCaptured -> onErrorCaptured:

    • 在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播
  • renderTracked -> onRenderTracked:

    • 跟踪虚拟 DOM重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键
  • renderTriggered -> onRenderTriggered:

    • 当虚拟 DOM重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键
  • activated -> onActivated:

    • keep-alive 缓存的组件激活时调用
  • deactivated -> onDeactivated:

    • keep-alive 缓存的组件失活时调用。
  1. //vue3.0改动
  2. //为什么进行改动?
  3. //因为语义化挂载的反义词也有
  4. beforeDestyory -> beforeUnmount
  5. destroyed -> unmounted
  1. //钩子使用
  2. import { onBeforeMount } from '@vue/composition-api';
  3. export default {
  4. setup(){
  5. onBeforeMount(()=>{
  6. console.log('Before Mount!');
  7. })
  8. }
  9. }

新增生命钩子函数有:

  1. onActivated/onDeactivated/onErrorCaptured/onRenderTracked/onRenderTriggered 追踪响应式依赖

computed

接受一个 getter函数,并根据 getter的返回值返回一个不可变的响应式 ref对象

  1. const count = ref(1);
  2. const plusOne = computed(() => count.value + 1);
  3. console.log(plusOne.value); // 2
  4. plusOne.value++;
  5. // 错误 Write operation failed: computed value is readonly

或者,接受一个具有 getset 函数的对象,用来创建可写的 ref对象

  1. const count = ref(1)
  2. const plusOne = computed({
  3. get: () => count.value + 1,
  4. set: val => {
  5. count.value = val - 1
  6. }
  7. })
  8. plusOne.value = 1
  9. console.log(count.value) // 0

问题:为什么在setup返回时需要computed重新计算?

  1. export default {
  2. ...,
  3. setup() {
  4. return {
  5. //注意:在vue2.x中是通过computed里 ...mapState(['xxx'])方法拿到里面的属性
  6. //1.所以这里不能直接访问state.headerTitle
  7. //2.所以需要用computed函数取出state里的属性
  8. headerTitle: computed(() => state.headerTitle),
  9. };
  10. },
  11. };

侦听器

  1. //侦听器写法:
  2. <div>
  3. <input type="text"/>
  4. <div>
  5. 符合关键字的活动的数目: {{results}}
  6. </div>
  7. </div>
  8. import { ref, watch } from '@vue/composition-api';
  9. import eventApi from '@/api/event.js';
  10. export default {
  11. setup(){
  12. const searchInput = ref('');
  13. const results = ref(0);
  14. //侦听searchInput属性,若发生变化执行右侧的箭头函数
  15. watch(searchInput, (newValue, oldValue)=>{
  16. results.value = eventApi.getEventCount(searchInput.value);
  17. });
  18. //多属性数据写法
  19. watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast])=>{
  20. results.value = eventApi.getEventCount(searchInput.value);
  21. });
  22. //setup函数底下的getEventCount()方法只触发一次
  23. //不能实时监听,所以用watch侦听
  24. //results.value = eventApi.getEventCount(searchInput.value);
  25. return{ searchInput, results };
  26. }
  27. }

watch

watchAPI完全等同于组件侦听器 propertywatch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下,它也是惰性的,即只有当被侦听的源发生变化时才执行回调。

  • watchEffect 比较,watch 允许我们:

    • 懒执行副作用;
    • 更具体地说明什么状态应该触发侦听器重新运行;
    • 访问侦听状态变化前后的值。

侦听单个数据源

侦听器数据源可以是返回值的 getter函数,也可以直接是 ref

  1. // 侦听一个 getter
  2. const state = reactive({ count: 0 })
  3. watch(
  4. () => state.count,
  5. (count, prevCount) => {
  6. /* ... */
  7. }
  8. )
  9. // 直接侦听ref
  10. const count = ref(0)
  11. watch(count, (count, prevCount) => {
  12. /* ... */
  13. })

侦听多个数据源

侦听器还可以使用数组同时侦听多个源:

  1. const firstName = ref('')
  2. const lastName = ref('')
  3. watch([firstName, lastName], (newValues, prevValues) => {
  4. console.log(newValues, prevValues)
  5. })
  6. firstName.value = 'John' // logs: ["John", ""] ["", ""]
  7. lastName.value = 'Smith' // logs: ["John", "Smith"] ["John", ""]

尽管如此,如果你在同一个函数里同时改变这些被侦听的来源,侦听器仍只会执行一次:

  1. setup() {
  2. const firstName = ref('')
  3. const lastName = ref('')
  4. watch([firstName, lastName], (newValues, prevValues) => {
  5. console.log(newValues, prevValues)
  6. })
  7. const changeValues = () => {
  8. firstName.value = 'John'
  9. lastName.value = 'Smith'
  10. // 打印 ["John", "Smith"] ["", ""]
  11. }
  12. return { changeValues }
  13. }

通过 watch Componsition API 实现数据监听

  1. //当数据变化时
  2. import { watch } from "vue";
  3. export default {
  4. setup(){
  5. watch(()=>{return xxx;},(value)=>{
  6. //业务需求:操作state里的数据,调用mutatios里面的方法
  7. store.commit("setHeaderTitle", value);
  8. });
  9. //这里的value是前面第一个函数里return的值
  10. }
  11. };

watchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖(数据)变更时重新运行该函数。

  1. const count = ref(0);
  2. watchEffect(() => {
  3. console.log(count.value);
  4. //0 首次立即执行
  5. });
  6. setTimeout(() => {
  7. count.value = 1;
  8. //再次执行watchEffect并打印 1
  9. }, 100);

停止侦听

watchEffect 在组件的setup函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

在一些情况下,也可以显式调用返回值以停止侦听:

  1. const stop = watchEffect(() => {
  2. /* ... */
  3. })
  4. // later
  5. stop()

清除副作用

有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

  • 副作用即将重新执行时
  • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
  1. watchEffect(onInvalidate => {
  2. const token = performAsyncOperation(id.value)
  3. onInvalidate(() => {
  4. // id has changed or watcher is stopped.
  5. // invalidate previously pending async operation
  6. token.cancel()
  7. })
  8. })

我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回它,是因为返回值对于异步错误处理很重要。

在执行数据请求时,副作用函数往往是一个异步函数:

  1. const data = ref(null)
  2. watchEffect(async onInvalidate => {
  3. onInvalidate(() => {
  4. /* ... */
  5. }) // 我们在Promise解析之前注册清除函数
  6. data.value = await fetchData(props.id)
  7. })

provide/inject

使用一对 provideinject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

在此处 provide一些组件的实例 property,这将是不起作用的:

  1. app.component('todo-list', {
  2. ...,
  3. provide: {
  4. todoLength: this.todos.length
  5. // 将会导致错误 `Cannot read property 'length' of undefined`
  6. },
  7. })

要访问组件实例 property,我们需要将 provide 转换为返回对象的函数:

  1. app.component('todo-list', {
  2. ...,
  3. provide() {
  4. return {
  5. todoLength: this.todos.length
  6. }
  7. }
  8. })

响应式处理

默认情况下,provide/inject 绑定并不是响应式的。我们可以通过传递一个 ref propertyreactive 对象给 provide 来改变这种行为

  1. app.component('todo-list', {
  2. // ...
  3. provide() {
  4. return {
  5. todoLength: Vue.computed(() => this.todos.length)
  6. }
  7. }
  8. })
  9. app.component('todo-list-statistics', {
  10. inject: ['todoLength'],
  11. created() {
  12. console.log(`Injected property: ${this.todoLength.value}`)
  13. // > 注入的 property: 5
  14. }
  15. })

setup写法

  1. //provide
  2. export default {
  3. setup() {
  4. provide('location', 'North Pole')
  5. provide('geolocation', {
  6. longitude: 90,
  7. latitude: 135
  8. })
  9. }
  10. }
  1. //inject
  2. export default {
  3. setup() {
  4. const userLocation = inject('location', 'The Universe')
  5. const userGeolocation = inject('geolocation')
  6. return {
  7. userLocation,
  8. userGeolocation
  9. }
  10. }
  11. }

响应式处理

  1. //添加响应性
  2. export default {
  3. setup() {
  4. const location = ref('North Pole')
  5. const geolocation = reactive({
  6. longitude: 90,
  7. latitude: 135
  8. })
  9. provide('location', location)
  10. provide('geolocation', geolocation)
  11. }
  12. }