Vue3的CompositionAPI

448 阅读6分钟

mixin 和 extends

mixin 是代码的共用,vuex 是数据的共用,extends 是拓展,拿其他的组件来用

mixin 混入

(可以参考本人文章 Vue2 里面的介绍)[juejin.cn/post/703925…]

重点:组件使用 mixin 和内部自定义的属性或函数冲突时的优先级

  • 基本类型变量以实例为主
  • 引用类型:键值替换
  • 生命钩子:队列处理,混入先出
  • 函数方法:重载

extends 继承

引入其他组件,能够直接使用组件内部的方法和属性

<template>
  <div>
    HOme
    <!-- 继承data内的属性,以及其他的方法 -->
    <h2>extends: {{message}}</h2>
    <button @click="foo">baseFoo</button>
  </div>
</template><script>
import BasePage from "./BasePage.vue"
  export default {
    extends: BasePage
  }
</script>

optionsAPI 和 compositionAPI 的区别

optionsAPI

  • 不同的类型放在不同的 option 里面,例如:data、computed、methods 等
  • 当一个 option 中代码过多,来回查看时,阅读性很差;data 属性归在一处,methods 归在一处,查询需要来回滚动

compositionAPI

  • 不用书写过多属性,需要时将对应 api 导入进来即可(watch、computed 等)
  • 代码能够在同一处(setup 函数中)书写,方便代码维护,增强可读性

setup 函数

函数自带两个属性,以及函数返回一个对象

setup(props, context) {
  return {}
}
​

函数的参数

  • @param {*} props: 父组件传递过来的属性(proxy 对象)

  • @param {*} context: 包含三个属性(因此 context 可以解构成 { attrs, slots, emit })

    • attrs(非 props 的属性)
    • slots(父组件传递的插槽)
    • emit(发出事件)

函数的返回值

return {}; 返回的对象能够替换data的属性,因此data这以optionsAPI就可以省略。

setup中的this

在setup函数中打印this,输出为undefined

console.log(this); // setup中没有绑定this: 输出undefined

vue官方解释: 在setup中避免使用this,在setup调用钱,data、computed等都还没有被解析;所以无法在setup中获取到this。

在生命周期中执行,setup在所有之前

image-20220210111746297.png

compositionAPI使用

reactive

reactive 能够在setup中使用该api返回一个proxy响应式对象

const info = reactive({
   name: "it is reactive"
})
console.log(info.name) // 使用值按正常对象操作

ref

ref 能够让基本数据类型成为ref响应式对象(reference)

ref对象的值通过 ref.value 进行获取

//  使用简单数据类型,可使用 ref API(reference:引用)
let counter_ref = ref(100); // ref对象引用
counter_ref.value++; // 获取引用的值,需要拿到对象内部的值value
console.log(counter_ref.value);

在template模板里面,会存在解包操作,因此页面上并不需要使用 .value获取, 可以直接使用

注意:搭配标签内部的ref能够获取到对应元素

<template>
  <div>
    <h2 ref="title">喝水水</h2>
  </div>
</template>
<script>
import { ref } from "vue"
export default {
  setup() {
    // 给ref传入null值,能够获取到页面上ref指定的元素或组件
    const title = ref(null)
    
    return {
      title
    }
  }
};
</script>

readonly

readonly 返回一个响应式对象的只读副本(readONLY修饰的对象仍然是 proxy对象;只是劫持了set方法)

在对于一些父组件传来只读的对象可以进行该操作,不会对父组件的属性产生影响

import { reactive, ref, readonly } from "vue";
    const info2 = reactive({
      name: "it is readonly"
    })
​
    // 得到一个info2(ref 或 reactive)的只读副本
    // 在对于一些父组件传来只读的对象可以进行该操作
    const readOnlyInfo2 = readonly(info2)
    
    const increment = () => {
      // info2.name = "changed" // 修改数据源本体,正常调用set方法
      // 当对只读副本进行修改时,会爆出警告不能修改只读数据
      readOnlyInfo2.name = "Is it readonly?"
    };

computed 和 watch

computed 计算属性

使用vue3的compositionAPI的方式引入,有两种用法

  • 传入一个回调函数:默认将回调函数放在计算属性的get函数上,使用的时候自动调用get函数
  • 传入get和set属性:能分别给计算属性进行拦截,对于获取和赋值时进行操作
import { ref, computed } from "vue";
  setup() {
    const firstName = ref("kobe");
    const lastName = ref("Bryant");
​
    // computed 返回一个 ref对象
    // 1.用法一:传入一个getter函数
    // const fullName = computed(() => firstName.value + " " + lastName.value);
​
    // 2.用法二:传入一个对象,对象包含getter、setter
    const fullName = computed({
      get: () => firstName.value + " " + lastName.value,
      set(newValue) {
        const names = newValue.split(" ");
        firstName.value = names[0]
        lastName.value = names[1]
      }
    })
​
    const changeName = () => {
      // firstName.value = "james"
      // lastName.value = "jack"
      fullName.value = "bluse li"
    }
​
    return {
      fullName,
      changeName
    };
  },

watch 侦听器

在vue3中使用时,需传入侦听对象以及对应的回调函数,可以侦听单个或多个对象

import { ref, watch, reactive } from "vue";
  • 单个对象——两个参数:

    • (getter 函数:必须引用一个响应式对象) / (响应式对象 reactive、ref)

    • 回调函数:监听到变化执行操作,两个参数(新,旧)

      • 回调函数返回值根据第一个参数的不同而不同
      • ref对象返回的是 value,reactive返回的是proxy对象
        const info = reactive({
          name: "kobe",
          age: 18,
        });
        const name = ref("code");
        watch(
          // name, // ref
          // () => info.name, // getter
          // info, // reactive
          () => {
            // 结构返回后,回调函数返回是普通对象,而不是proxy对象
            // 结构出来的并不会深度侦听,需要添加对应的参数 deep
            return { ...info };
          },
          (newValue, oldValue) => {
            console.log(newValue, oldValue);
          }, {
            deep: true,
            immediate: true // 立即执行
          }
        );
    
  • 侦听多个数据——两个参数

    • getter函数
    • 回调函数
        watch(
          // [info, name],
          () => [({...info}), name],
          // (newValue, oldValue) => {
          //   // 多个数据源
          //   console.log(newValue, oldValue);
          // }
          // 解构
          ([newInfo, newName], [oldInfo, oldName]) => {
            console.log(newInfo, oldInfo);
            console.log(newName, oldName);
          }
        )
    

watchEffect

watchEffect也能够进行侦听——自动收集响应式的依赖

当内部使用的响应式对象发生了改变,就会执行一次(其他的改变不会有影响)

首次声明时也会执行

示例:侦听上面ref绑定的title元素

    // 当内部使用的响应式对象发生了改变,就会执行一次(其他的改变不会有影响)
    // 默认立即执行
    watchEffect(() => {
      console.log(title.value);
    }, {
      // 等到dom挂载后再执行
      // 不写该参数的话,上面的打印会执行会打印两次;
      // 第一次是null值,页面还未挂载获取不到节点
      flush: "post"
    })
常用于清除副作用
    //  返回一个函数,调用时可以停止侦听
    const stop = watchEffect((onInvalidate) => {
      // 根据name来发送网络请求是如果出错,数据发生异常 —— 副作用
      onInvalidate(() => {
        // 用这个函数清除掉副作用
        console.log("onInvalidate");
      })
​
      console.log("name:", name.value);
      console.log("Age:", age.value);
    });
    
    
    stop() // 调用回调函数,随时可以停止侦听

生命周期

compositionAPI直接使用on + 周期调用钩子函数

import { onMounted, onUpdated, onUnmounted, ref } from "vue"
  export default {
    setup() {
      // 不推荐在beforeCreate和created里面进行操作
      // setup在二者之前前执行
      const counter = ref(0)
      function increment () {
        counter.value ++
      }
      onMounted(() => {
        console.log("App onMounted");
      })
​
      // 生命周期函数可以多次使用,按顺序执行
      // 队列,先入先出
      onMounted(() => {
        console.log("App onMounted2");
      })
​
      onUpdated(() => {
        console.log("App onUpdated");
      })
​
      onUnmounted(() => {
        console.log("App onUnMounted");
      })
​
      return {
        increment,
        counter
      }
    }
  }

Provide 和 Inject

父组件给后代组件传递数据,能够动态的显示父组件中的数据(传输的数据建议使用readonly只读复制个副本)

父组件

<template>
  <div>
    App
    <button @click="increment">increment</button>
    <home></home>
  </div>
</template><script>
import Home from "./Home.vue"
import { provide, ref, readonly } from "vue"
  export default {
    components: {
      Home
    },
    setup() {
      const name = "刘德华"
      const counter = ref(0)
​
      const increment = () => counter.value ++
      // 子组件中不建议去修改父组件传来的值
      // 用readonly包裹住
      provide("name", readonly(name))
      provide("counter", readonly(counter))
​
      return {
        increment
      }
    }
  }
</script>

子组件

<template>
  <div>
    Home___{{name}}
    <h2>APP里面的counter:{{counter}}</h2>
  <button @click="increment">home ++</button>
  </div>
</template><script>
import { inject } from "vue"
  export default {
    setup() {
      
      const name = inject("name")
      const counter = inject("counter")
​
      const increment = () => counter.value ++
      return {
        name,
        counter,
        increment
      }
    }
  }
</script>

nextTick

更新DOM元素后触发nextTick内部的回调函数:将nextTick加入到vue所处的微任务队列的最后面执行

若使用onUpdated生命周期函数的话,无论什么更新都会触发,会影响性能

示例:给标签添加内容,改变其高度

<template>
  <div>
    <h2 style="width: 100px" ref="title">{{ message }}</h2>
    <button @click="changeMsg">changeMes</button>
  </div>
</template><script>
import { ref, nextTick } from "vue";
export default {
  setup() {
    const message = ref("哈哈哈哈哈哈哈");
    const title = ref(null);
    const changeMsg = () => {
      message.value += message.value;
      // 输出的还是原来的高度
      console.log(title.value.offsetHeight);
      // 使用nextTick 在标签添加内容后输出变化之后的正确高度
      nextTick(() => {
        console.log(title.value.offsetHeight);
      });
    };
​
    return {
      message,
      changeMsg,
      title,
    };
  },
};
</script>

Vue3使用compositionAPI的应用示例

示例代码