首页 > 其他分享 >Vue3学习笔记(二)——组合式API(Composition API)

Vue3学习笔记(二)——组合式API(Composition API)

时间:2022-10-19 15:14:43浏览次数:82  
标签:target 对象 value API key Vue3 Composition 属性

一、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语,涵盖了以下方面的 API:

  • 响应式 API:例如 ref() 和 reactive(),使我们可以直接创建响应式状态、计算属性和侦听器。

  • 生命周期钩子:例如 onMounted() 和 onUnmounted(),使我们可以在组件各个生命周期阶段添加逻辑。

  • 依赖注入:例如 provide() 和 inject(),使我们可以在使用响应式 API 时,利用 Vue 的依赖注入系统。

组合式 API 是 Vue 3 及 Vue 2.7 的内置功能。对于更老的 Vue 2 版本,可以使用官方维护的插件 @vue/composition-api。在 Vue 3 中,组合式 API 基本上都会配合 <script setup> 语法在单文件组件中使用。下面是一个使用组合式 API 的组件示例:

<script setup>
import { ref, onMounted } from 'vue'

// 响应式状态
const count = ref(0)

// 更改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`计数器初始值为 ${count.value}。`)
})
</script>

<template>
  <button @click="increment">点击了:{{ count }} 次</button>
</template>

虽然这套 API 的风格是基于函数的组合,但组合式 API 并不是函数式编程。组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的,而函数式编程通常强调数据不可变。

如果你对如何通过组合式 API 使用 Vue 感兴趣,可以通过页面左侧边栏上方的开关将 API 偏好切换到组合式 API,然后重新从头阅读指引。

1.1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。

  2. setup是所有Composition API(组合API)“ 表演的舞台 ”

  3. 组件中所用到的:数据、方法等等,均要配置在setup中。

  4. setup函数的两种返回值:

    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)

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

  5. 注意点:

    1. 尽量不要与Vue2.x配置混用

      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。

      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。

      • 如果有重名, setup优先。

    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

1.2.ref函数

  • 作用: 定义一个响应式的数据

  • 语法: const xxx = ref(initValue)

    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)

    • JS中操作数据: xxx.value

    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>

    • ref对应的接口是interface Ref<T>
  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。

    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。

    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

    • isRef 判断是不是一个ref对象

    • import { ref, Ref,isRef } from 'vue'
      let message: Ref<string | number> = ref("我是message")
      let notRef:number = 123
      const changeMsg = () => {
        message.value = "change msg"
        console.log(isRef(message)); //true
        console.log(isRef(notRef)); //false
        
      }

1.3.reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • reactive定义的响应式数据是“深层次的”。

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

1.4.Vue3.0中的响应式原理

1.4.1.vue2.x的响应式

使用 Object 构造函数上的 defineProperty() 实现。

1、vue2存在的问题

新增属性、删除属性,界面不会更新。

直接通过下标修改数组,界面不会自动更新。

注意:如果要解决上述问题

  • ① 使用 vue 实例对象上的 $set 进行添加或修改,$delete 进行删除。
  • ② 使用 Vue.set 进行添加或修改,Vue.delete 进行删除。
  • ③ 使用 vue 实例对象上的 $nextTick 进行页面更新。
  • ④ 使用数组的一些方法对数组进行操作(如 splice() )。

2、实现响应式的原理

对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。

数组类型:通过重写更新数组的方法来实现拦截(对数组的变更方法进行了包裹)。

(1)Object.defineProperty() 的基本使用

Object.defineProperty("对象", "属性", {
    value: 0, // 属性值
    enumerable: true, // 属性是否可被枚举,默认 false
    writable: true, // 属性是否可被修改,默认 false
    configurable: true, // 属性是否可被删除,默认 false
    get() {}, // 获取属性值时调用,此函数需返回属性的属性值
    set(value) {}, // 修改属性值时调用,value为修改后的值
})

(2)实现代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./js/vue3.js"></script>
    <script>
      function updateView(target, key, newValue) {
        console.log("视图更新了!", target, key, newValue);
      }

      function observer(target) {
        //如果被观察的对象不是对象类,或为null,则直接返回原对象
        if (typeof target !== "object" || target === null) {
          return target;
        }
        //遍历出对象的所有的key
        for (let key in target) {
          //定义属性,覆盖原有属性
          defineReactive(target, key, target[key]);
        }
      }

      //在目标对象上定义属性,目标对象,属性名,值
      function defineReactive(target, key, value) {
        //如果被定义的值是对象类型则继续观察
        if (typeof value === "object") {
          observer(value);
        } else {
          //value在这里起到了闭包保留值的作用
          Object.defineProperty(target, key, {
            get() {
              return value;
            },
            set(newValue) {
              if (newValue !== value) {
                updateView(target, key, newValue);
                value = newValue;
              }
            },
          });
        }
      }

      let person = {
        name: "张果",
        address: {
          province: "广东",
          city: "珠海",
        },
      };
      observer(person);
      person.name = "张三";
      person.address.city = "深圳";
      console.log(person);
    </script>
  </body>
</html>

1.4.2.Vue3.0的响应式

(1)、反射 Reflect

Reflect是ES6中新增加的一个对象,并非构造器,该对象中含有多个可完成"元编程(对编程语言进行编程)"功能的静态函数,能方便的对对象进行操作,也可以结合Proxy实现拦截功能

https://www.cnblogs.com/best/p/16291079.html#_lab2_4_0

(2)、代理Proxy

浅层代理:

      //定义被代理的对象
      var shape = { width: 100 };

      //定义代理代理
      let proxy = new Proxy(shape, {
        get: function (target, key, receiver) {
          console.log("get");
          //输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
          console.log(target, key, receiver);
          //使用get方法从目标对象中获取值,把取得的值加100
          return Reflect.get(target, key, receiver) + 100;
        },

        set: function (target, key, value, receiver) {
          console.log("set");
          //输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
          console.log(target, key, value, receiver);

          //在目标对象上设置属性值,设置值时将值加100
          return Reflect.set(target, key, value + 100, receiver);
        },
        deleteProperty: function (target, key, receiver) {
          console.log("delete");
          console.log(target, key);
          return Reflect.deleteProperty(target, key, receiver);
        },
      });

      proxy.width = 101;
      console.log(proxy.width);
      delete proxy.width;

深层代理 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      //定义被代理的对象
      var shape = { width: 100, location: { x: 100, y: 200 } };

      function reactive(target) {
        return createReactiveObject(target);
      }

      function isObject(target) {
        return typeof target === "object" && target !== null;
      }

      function createReactiveObject(target) {
        if (!isObject(target)) return target;
        return new Proxy(target, {
          get: function (target, key, receiver) {
            console.log("get");
            //输出被代理的目标对象,属性名称,receiver为getter调用时的this值(当前对象)
            console.log(target, key, receiver);
            //使用get方法从目标对象中获取值
            let result = Reflect.get(target, key, receiver);
            //递归多层代理,相比于vue2的优势是,vue2默认递归,而vue3中,只要不使用就不会递归。
            return isObject(result) ? reactive(result) : result;
          },

          set: function (target, key, value, receiver) {
            console.log("set");
            //输出被代理的目标对象,属性名称,值,receiver为getter调用时的this值(当前对象)
            console.log(target, key, value, receiver);

            //在目标对象上设置属性值
            return Reflect.set(target, key, value, receiver);
          },
          deleteProperty: function (target, key, receiver) {
            console.log("delete");
            console.log(target, key);
            return Reflect.deleteProperty(target, key, receiver);
          },
        });
      }

      let proxyShape = reactive(shape);
      proxyShape.location.x = 500;
    </script>
  </body>
</html>

 

 

 

总结:

  • vue2使用Object.defineProperty()实现响应式原理,而vue3使用Proxy()实现。
  • 虽然vue2,vue3面对对象嵌套,都需要递归,但vue2是对对象的所有属性进行递归,vue3是按需递归,如果没有使用到内部对象的属性,就不需要递归,性能更好。
  • vue2中,对象不存在的属性是不能被拦截的。而vue3可以。
  • vue2对数组的实现是重写数组的所有方法,并改变,vue2中,数组的原型来实现,而Proxy则可以轻松实现。而且vue2中改变数组的长度是无效的,无法做到响应式,但vue3可以。

1.5.reactive对比ref

  • 从定义数据角度对比:

    • ref用来定义:基本类型数据

    • reactive用来定义:对象(或数组)类型数据

    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象

  • 从原理角度对比:

    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。

    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。

  • 从使用角度对比:

    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value

    • reactive定义的数据:操作数据与读取数据:均不需要.value

1.6.setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。

  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。

    • context:上下文对象

      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs

      • slots: 收到的插槽内容, 相当于 this.$slots

      • emit: 分发自定义事件的函数, 相当于 this.$emit

1.7.计算属性与监视

1.7.1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'

    setup(){
       ...
    //计算属性——简写
       let fullName = computed(()=>{
           return person.firstName + '-' + person.lastName
      })
       //计算属性——完整
       let fullName = computed({
           get(){
               return person.firstName + '-' + person.lastName
          },
           set(value){
               const nameArr = value.split('-')
               person.firstName = nameArr[0]
               person.lastName = nameArr[1]
          }
      })
    }

1.7.2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    },{immediate:true})

    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    console.log('sum或msg变化了',newValue,oldValue)
    })

    /* 情况三:监视reactive定义的响应式数据
    若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
    */
    watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效

    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})

    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})

    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
       console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

1.7.3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。

    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
       const x1 = sum.value
       const x2 = person.age
       console.log('watchEffect配置的回调执行了')
    })

1.8.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

二、其它 Composition API

2.1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。

    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

2.2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。

  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。

  • 应用场景: 不希望数据被修改时。

2.3.toRaw 与 markRaw

  • toRaw:

    • 作用:将一个由reactive生成的响应式对象转为普通对象

    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

  • markRaw:

    • 作用:标记一个对象,使其永远不会再成为响应式对象。

    • 应用场景:

      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。

      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

2.4.customRef

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

  • 实现防抖效果:

    <template>
    <input type="text" v-model="keyword">
    <h3>{{keyword}}</h3>
    </template>

    <script>
    import {ref,customRef} from 'vue'
    export default {
    name:'Demo',
    setup(){
    // let keyword = ref('hello') //使用Vue准备好的内置ref
    //自定义一个myRef
    function myRef(value,delay){
    let timer
    //通过customRef去实现自定义
    return customRef((track,trigger)=>{
    return{
    get(){
    track() //告诉Vue这个value值是需要被“追踪”的
    return value
    },
    set(newValue){
    clearTimeout(timer)
    timer = setTimeout(()=>{
    value = newValue
    trigger() //告诉Vue去更新界面
    },delay)
    }
    }
    })
    }
    let keyword = myRef('hello',500) //使用程序员自定义的ref
    return {
    keyword
    }
    }
    }
    </script>

2.5.provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      ......
         let car = reactive({name:'奔驰',price:'40万'})
         provide('car',car)
         ......
      }
    2. 后代组件中:

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

2.6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象

  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

三、Composition API 的优势

3.1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

3.2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。​

标签:target,对象,value,API,key,Vue3,Composition,属性
From: https://www.cnblogs.com/best/p/16798724.html

相关文章

  • ​Vue3插件机制剖析 + 手写实现Vuex
    使用插件这里以vuex使用为例,我们通过​​app.use(store)​​引入vuex插件:<divid="app"><h3@click="add">{{counter}}</h3></div><scriptsrc="http://unpkg.com/vue@nex......
  • 使用Kong网关API接口配置
    一、Upstream1、创建Upstream:curl-i-XPOSTIPAddress:8001/upstreams-d'name=upstream-test'-d'slots=1000'2、创建Targets目标地址并且关联Upstream:curl-i-XPO......
  • VUE3.0 中如何使用SVG图标
    1.文件下新建SvgIcon文件夹以及子文件index.js,index.vue,svg文件夹(用于存放svg图片) 2.编写index.vue组件<template><svg:class="svgClass"aria-hidden="true">......
  • 从0搭建vue3组件库:Shake抖动组件
    先看下效果其实就是个抖动效果组件,实现起来也非常简单。之所以做这样一个组件是为了后面写Form表单的时候会用到它做一个规则校验,比如下面一个简单的登录页面,当点击登录......
  • 用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能。
    Vue3的父子组件传值、绑定表单数据、UI库的二次封装、防抖等,想来大家都很熟悉了,本篇介绍一种使用Typescript的方式进行统一的封装的方法。基础使用方法Vue3对于表单的绑......
  • API安全测试checklist
    开发安全的API所需要核对的清单以下是当你在设计,测试以及发布你的API的时候所需要核对的重要安全措施。身份认证不要使用BasicAuth,请使用标准的认证协议(如JW......
  • vue3+vite+ts自动引入api和组件
    安装cnpminstallunplugin-auto-importunplugin-vue-components-d配置//自动导入compositionapi和生成全局typescript说明importAutoImportfrom'unplugin-au......
  • 基于apiview接口实现 -------查、更新
    https://www.bilibili.com/video/BV1z5411D7BQ?p=11&vd_source=caabcbd2a759a67e2a3de8acbaaf08eaviews.pyclassBookView(APIView):defget(self,request):......
  • JDBC API 详解
    JDBCAPI详解DriverManager驱动管理类作用:1.注册驱动获取数据库连接 Connection数据库连接对象作用:获取执行SQL对象普通执行SQL对象Statement creatStat......
  • Github上把敏感文件或者API秘钥上传公共库并开放为public权限导致秘钥泄露
    Github上把敏感文件或者API秘钥上传公共库并开放为public权限导致秘钥泄露背景我们在开发项目时,可能会使用到一些加密文件,例如私钥,或者使用到一些云服务商提供的ak(Acce......