• Vue3中Compositions API的使用(一)


    Options API的弊端

    在Vue2中,我们编写组件的方式是Options API

    • Options API的一大特点就是在对应的属性中编写对应的功能模块;
    • 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;

    但是这种代码有一个很大的弊端

    • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
    • 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
    • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);

    下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分

    • 这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
    • 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;

    在这里插入图片描述


    Composition API

    Composition API能帮助我们, 将同一个逻辑关注点相关的代码收集在一起

    • 也有人把Vue Composition API简称为VCA

    那么既然知道Composition API想要帮助我们做什么事情,接下来看一下到底是怎么做呢?

    为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方

    在Vue组件中,这个位置就是 setup 函数

    setup其实就是组件的另外一个选项

    只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;

    比如methods、computed、watch、data、生命周期等等;

    接下来我们一起学习这个函数的使用

    函数的参数

    函数的返回值


    setup函数的参数

    我们先来研究一个setup函数的参数,它主要有两个参数

    第一个参数:props

    第二个参数:context

    props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取

    对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;

    并且在template中依然是可以正常去使用props中的属性,比如message;

    如果我们在setup函数中想要使用props,那么不可以通过 this 去获取(后面我会讲到为什么);

    因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;

    另外一个参数是context, context是一个对象,我们也称之为是一个SetupContext,它里面包含三个属性

    attrs:所有的非prop的attribute;

    slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);

    emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);


    setup函数返回值

    setup既然是一个函数,那么它也可以有返回值,它的返回值用来做什么呢?

    setup的返回值可以在模板template中被使用;

    也就是说我们可以通过setup的返回值来替代data选项;

    甚至是我们可以返回一个执行函数来代替在methods中定义的方法, 接下来我们使用setup函数完成一个计数器, 对setup进行一个初体验 :

    <template>
      <div class="app">
        <h2>当前计数: {{ counter }}h2>
        <button @click="increment">+button>
        <button @click="decrement">-button>
      div>
    template>
    
    <script>
    
      export default {
        // 使用setup函数
        setup() {
    			// setup函数定义普通的数据, 缺点数据不是响应式的
          let counter = 100
          // setup函数定义函数(方法)
          const increment = () => {
            counter++
          }
          // setup函数定义函数(方法)
          const decrement = () => {
            counter--
          }
    
          // 外部要使用的数据需要通过return导出
          return {
            counter,
            increment,
            decrement
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    但是,如果我们将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?

    答案是不可以, 我们发下上面计数器值其实是改变了的, 但是并没有渲染到界面上;

    这是因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;

    setup响应式函数

    如果想为在setup中定义的数据提供响应式的特性,那么我们有如下两种常见的方案

    1.Reactive API

    方式一: 我们可以使用reactive的函数, 为在setup中定义的数据提供响应式的特性 :

    reactive函数常用于定义复杂类型的数据

    如下定义完成后, account这个数据就是定义成响应式的了

    <template>
      <div class="app">
        <h2>账号: {{ account.userName }}h2>
        <h2>密码: {{ account.passWord }}h2>
      div>
    template>
    
    <script>
      // 引入reactive函数
      import { reactive } from 'vue'
    
      export default {
        setup() {
          // 使用reactive函数定义数据
          // reactive函数要求 传入一个复杂数据类型, 数组对象都可以
          const account = reactive({
            userName: "chenyq",
            passWord: "123456"
          })
    
          // 将定义的数据account返回
          return {
            account
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    那么这是什么原因呢?为什么就可以变成响应式的呢?

    这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;

    当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);

    事实上,我们编写的data选项,也是在内部交给了reactive函数将其变成响应式对象的;


    2.Ref API

    方式二: 使用ref函数

    reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型

    如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;

    这个时候Vue3给我们提供了另外一个API:ref API

    ref函数可以定义一个简单类型的数据, 也可以定义一个复杂类型的数据

    ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;

    它内部的值是在ref的 value 属性中被维护的;

    这里有两个注意事项

    在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;

    但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;

    // 引入ref函数
    import { ref } from 'vue'
    
    export default {
      setup() {
        // 使用ref定义数据, 此时message已经是响应式的了
        let message = ref("hello ref")
    
        // 返回定义的数据
        return {
          message
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    例如我们给添加一个按钮测试 :

    <template>
      <div class="app">
    		
        <h2>{{ message }}h2>
        <button @click="btnClick">按钮button>
      div>
      
    template>
    
    <script>
      // 引入reactive函数
      import { reactive } from 'vue'
      
      import { ref } from 'vue'
    
      export default {
        setup() {
          // 使用ref定义数据
          let message = ref("hello ref")
    
          const btnClick = () => {
    				// setup中需要通过message.vue的方式拿到ref里面的值
            message.value = "你好 ref"
          }
    
          // 将定义的数据account返回
          return {
            message,
            btnClick
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    setup其他的函数

    1.readonly函数

    我们先来看如这样一个案例:

    定义一个父组件App.vue, 再定义一个子组件ShowInfo

    通过reactive获取到一个响应式的对象info, 在父组件中展示info对象, 并将对象info传出

    <template>
      <div class="app">
        <h2>app: {{ info }}h2>
        <show-info :info="info" />
      div>
    template>
    
    <script>
    import { reactive } from 'vue'
    import ShowInfo from './ShowInfo.vue'
    
      export default {
        components: {
          ShowInfo
        },
        setup() {
          const info = reactive({
            name: "chenyq",
            age: 18,
            height: 1.88
          })
    
          return {
            info
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    子组件ShowInfo对接收父组件传递的info对象并展示, 在子组件定义一个button按钮, 点击button使info对象的name属性修改

    <template>
      <div class="info">
        <h2>ShowInfo: {{info}}h2>
        <button @click="info.name = 'kaisa'">按钮button>
      div>
    template>
    
    <script>
      export default {
        props: {
          info: {
            type: Object,
            default: () => ({})
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们发现, 由于组件传递对象时, 是传递的对象的引用, 因此当子组件内容修改时, 父组件中的内容同样会被修改

    在这里插入图片描述

    上面这种做法是可行的, 但是它好用吗? 符合规范吗

    这种做法是不符合规范的, 在Vue中有单向数据流的规范, 指的是子组件中只能拿到数据, 不能修改

    如果确实需要修改, 按照规范我们在子组件中将事件传递到父组件, 由父组件来修改数据

    setup(props, context) {
      // 发送自定义 事件让父组件修改数据
      function showInfoClick() {
        context.emit("changeInfo", "kaisa")
      }
    
      return {
        showInfoClick
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们知道单项数据流规范是不允许子组件修改的, 但是在公司实际开发中, 其他开发者如果不知道这个规范, 就有可能在子组件中修改数据, 那么我们如何避免这种情况呢?

    Vue3为我们提供了readonly的方法, 可以避免他人子组件修改父组件的数据

    readonly会返回原始对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);

    在开发中常见的readonly方法会传入三个类型的参数

    类型一:普通对象

    类型二:reactive返回的对象

    类型三:ref的对象


    在readonly的使用过程中,有如下规则

    readonly返回的对象都是不允许修改的, 但是经过readonly处理的原来的对象是允许被修改的

    比如 const info = readonly(obj),info对象是不允许被修改的;

    但是可以通过修改obj来修改info, obj被修改时,readonly返回的info对象也会被修改;

    但是我们不能去修改readonly返回的对象info;

    setup() {
    	const info = reactive({
    	  name: "chenyq",
    	  age: 18,
    	  height: 1.88
    	})
      
    	// 为info包裹一个readonly, 子组件就无法修改
    	const newInfo = readonly(info)
    	console.log(newInfo)
    
    	return {
    	  info,
    	  changeInfo,
    	  newInfo
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.reactive的其他函数

    isProxy

    检查对象是否是由 reactive 或 readonly创建的 proxy。

    isReactive

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

    如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;

    isReadonly

    检查对象是否是由 readonly 创建的只读代理。

    toRaw

    返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。

    shallowReactive

    创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。

    shallowReadonly

    创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。

    3.toRefs函数

    如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么解构之后无论是修改结构后的变量,还是修改reactive返回的state对象,数据都不再是响应式

    setup() {
      const info = reactive({
        name: "chenyq",
        age: 18,
        height: 1.88
      })
    
      const { name, age, height } = info
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    那么有没有办法让我们解构出来的属性是响应式的呢?

    Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref

    那么我们再次进行结构出来的 name 和 age 本身都是 ref的;

    setup() {
      const info = reactive({
        name: "chenyq",
        age: 18,
        height: 1.88
      })
    
      // 当我这样来做的时候, 会分别返回三个ref对象, 它们是响应式的
    	const { name, age, height } = toRefs(info)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化


    toRef函数

    如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法

    setup() {
      const info = reactive({
        name: "chenyq",
        age: 18,
        height: 1.88
      })
    
     	// 单独解构某一属性且具有响应式
    	const height = toRef(info, "height")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.ref的其他函数

    unref

    如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:

    如果参数是一个 ref,则返回内部值,否则返回参数本身;

    这是 val = isRef(val) ? val.value : val 的语法糖函数;

    isRef

    判断值是否是一个ref对象。

    shallowRef

    创建一个浅层的ref对象;

    triggerRef

    手动触发和 shallowRef 相关联的副作用:

  • 相关阅读:
    每天空闲时间学习SpringBoot核心讲解,这不是轻松拿捏
    Docker和Docker compose的安装使用指南
    >Web 3.0顶级干货教学:浅析区块链与货币关系
    物料搬运装置及控制系统设计(CAD+PLC)
    A-Level Business Studies 真题及解析(1)
    jQuery小结五
    Redis数据类型
    扩展函数和运算符重载
    IE惯导数据紧组合处理过程与方法
    2022极端高温!人工智能如何预测森林火灾?| 万物AI
  • 原文地址:https://blog.csdn.net/m0_71485750/article/details/125911956