Vue3 – Composition API

news2024/11/16 3:41:15

1、认识CompositionAPI

1.1、Options API的弊端

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

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

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

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

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

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

1.2、大组件的逻辑分散

  • 如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
  • 这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
  • 也有人把Vue Composition API简称为VCA。 

1.3、Composition API 

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

  • 为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方
  • 在Vue组件中,这个位置就是 setup 函数

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

  • 只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;
  • 比如methods、computed、watch、data、生命周期等等;

2、Setup函数的基本使用

2.1、setup函数的参数

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

  • 第一个参数:props
  • 第二个参数:context

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

  • 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
  • 并且在template中依然是可以正常去使用props中的属性,比如message;
  • 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取;
  • 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;

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

  • attrs:所有的非prop的attribute;
  • slots:父组件传递过来的插槽;
  • emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);

2.2、setup函数的返回值

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

  • setup的返回值可以在模板template中被使用
  • 也就是说我们可以通过setup的返回值来替代data选项;

甚至是我们可以返回一个执行函数代替在methods中定义的方法

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

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

2.3、示例

useCounter.js

import { ref } from 'vue'

export default function useCounter() {
  let counter = ref(100)
  const increment = () => {
    counter.value++
    console.log(counter.value)
  }
  const decrement = () => {
    counter.value--
  }

  return { counter, increment, decrement }
}

App.vue

<template>
  <div class="app">
    <!-- template中ref对象自动解包 -->
    <h2>当前计数: {{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

<script>
import { ref } from 'vue'
import useCounter from './hooks/useCounter'

export default {
  setup() {
    // 1.定义counter的内容
    // 默认定义的数据都不是响应式数据
    // let counter = ref(100)  // ref函数让定义的数据变成响应式数据
    // const increment = () => {
    //   counter.value++  // 获取对象里面的值,进行改变
    //   console.log(counter.value)
    // }
    // const decrement = () => {
    //   counter.value--
    // }

    // const { counter, increment, decrement } = useCounter()

    return {
     /* counter,
      increment,
      decrement*/

      ...useCounter()
    }
  }
}
</script>

<style>
</style>

3、Setup中数据的响应式

3.1、Reactive API

如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数

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

  • 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
  • 数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的; 

3.2、Ref API

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

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

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

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

这里有两个注意事项:

  • 模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用
  • 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;

3.3、Ref自动解包

  • 模板中的解包是浅层的解包,如果我们的代码是下面的方式:
  • 如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包

3.4、示例 

showInfo.vue 

<template>
  <div>
    <h2>ShowInfo: {{ name }} - {{ age }}</h2>
  </div>
</template>

<script>
  export default {
    props: {
      name: {
        type: String,
        default: ""
      },
      age: {
        type: Number,
        default: 0
      }
    }
  }
</script>

<style scoped>
</style>

App.vue

<template>
  <div>
    <h2>message: {{ message }}</h2>
    <button @click="changeMessage">修改message</button>
    <hr>
    <h2>账号: {{ account.username }}</h2>
    <h2>密码: {{ account.password }}</h2>
    <button @click="changeAccount">修改账号</button>
    <hr>
    <!-- 默认情况下在template中使用ref时, vue会自动对其进行解包(取出其中value) -->
    <h2>当前计数: {{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="counter++">+1</button>

    <hr>
    <!-- 使用的时候不需要写.value -->
    <h2>当前计数: {{ info.counter }}</h2>
    <!-- 修改的时候需要写.value -->
    <button @click="info.counter.value++">+1</button>
  </div>
</template>

<script>
  import { reactive, ref } from 'vue'

  export default {
    setup() {
      // 1.定义普通的数据: 可以正常的被使用
      // 缺点: 数据不是响应式的
      let message = "Hello World"
      function changeMessage() {
        message = "你好啊,李银河!"
        console.log(message)
      }

      // 2.定义响应式数据
      // 2.1.reactive函数: 定义复杂类型的数据
      const account = reactive({
        username: "coderwhy",
        password: "123456"
      })
      function changeAccount() {
        account.username = "kobe"
      }

      // 2.2.ref函数: 定义简单类型的数据(也可以定义复杂类型的数据)
      // counter定义响应式数据
      const counter = ref(0)
      function increment() {
        counter.value++
      }

      // 3.ref是浅层解包
      const info = {
        counter
      }

      return {
        message,
        changeMessage,
        account,
        changeAccount,
        counter,
        increment,
        info
      }
    }
  }
</script>

<style scoped>
</style>

App2.vue

<template>
  <div>
    <form>
      账号: <input type="text" v-model="account.username">
      密码: <input type="password" v-model="account.password">
    </form>
    
    <form>
      账号: <input type="text" v-model="username">
      密码: <input type="password" v-model="password">
    </form>

    <hr>

    <show-info :name="name" :age="age"></show-info>
  </div>
</template>

<script>
  import { onMounted, reactive, ref } from 'vue'
  import ShowInfo from './ShowInfo.vue'

  export default {
    components: {
      ShowInfo
    },
    data() {
      return {
        message: "Hello World"
      }
    },
    setup() {
      // 定义响应式数据: reactive/ref
      // 强调: ref也可以定义复杂的数据
      const info = ref({})
      console.log(info.value)

      // 1.reactive的应用场景
      // 1.1.条件一: reactive应用于本地的数据
      // 1.2.条件二: 多个数据之间是有关系/联系(聚合的数据, 组织在一起会有特定的作用)
      const account = reactive({
        username: "coderwhy",
        password: "1234567"
      })

      const username = ref("coderwhy")
      const password = ref("123456")

      // 2.ref的应用场景: 其他的场景基本都用ref(computed)
      // 2.1.定义本地的一些简单数据
      const message = ref("Hello World")
      const counter = ref(0)
      const name = ref("why")
      const age = ref(18)

      // 2.定义从网络中获取的数据也是使用ref
      // const musics = reactive([])
      const musics = ref([])
      onMounted(() => {
        const serverMusics = ["海阔天空", "小苹果", "野狼"]
        musics.value = serverMusics
      })


      return {
        account,
        username,
        password,
        name,
        age
      }
    }
  }
</script>

<style scoped>
</style>

4、readonly函数

4.1、认识readonly

        我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?

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

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

  • 类型一:普通对象
  • 类型二:reactive返回的对象
  • 类型三:ref的对象

4.2、readonly的使用

  • 在readonly的使用过程中,有如下规则:
    • readonly返回的对象都是不允许修改的;
    • 但是经过readonly处理的原来的对象是允许被修改的;
    • 比如 const info = readonly(obj),info对象是不允许被修改的
    • obj被修改时,readonly返回的info对象也会被修改;
    • 但是我们不能去修改readonly返回的对象info
  • 其实本质上就是readonly返回的对象的setter方法被劫持了而已;

4.3、readonly的应用 

那么这个readonly有什么用呢?

  • 在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly了;

4.4、示例 

showInfo.vue

<template>
  <div>
    <h2>ShowInfo: {{ info }}</h2>
    <!-- 代码没有错误, 但是违背规范(单项数据流) -->
    <button @click="info.name = 'kobe'">ShowInfo按钮</button>
    <!-- 正确的做法: 符合单项数据流-->
    <button @click="showInfobtnClick">ShowInfo按钮</button>
    <hr>
    <!-- 使用readonly的数据 -->
    <h2>ShowInfo: {{ roInfo }}</h2>
    <!-- 代码就会无效(报警告) -->
    <!-- <button @click="roInfo.name = 'james'">ShowInfo按钮</button> -->
    <!-- 正确的做法 -->
    <button @click="roInfoBtnClick">roInfo按钮</button>
  </div>
</template>

<script>
  export default {
    props: {
      // reactive数据
      info: {
        type: Object,
        default: () => ({})
      },
      // readonly数据
      roInfo: {
        type: Object,
        default: () => ({})
      }
    },
    emits: ["changeInfoName", "changeRoInfoName"],
    setup(props, context) {
      function showInfobtnClick() {
        // setup函数没有this,所以不能使用this.$emit()这种方式
        context.emit("changeInfoName", "kobe")
      }

      function roInfoBtnClick() {
        context.emit("changeRoInfoName", "james")
      }

      return {
        showInfobtnClick,
        roInfoBtnClick
      }
    }
  }
</script>

<style scoped>
</style>

 App.vue

<template>
  <h2>App: {{ info }}</h2>
  <show-info :info="info" 
             :roInfo="roInfo" 
             @changeInfoName="changeInfoName"
             @changeRoInfoName="changeRoInfoName">
  </show-info>
</template>

<script>
  import { reactive, readonly } from 'vue'
  import ShowInfo from './ShowInfo.vue'

  export default {
    components: {
      ShowInfo
    },
    setup() {
      // 本地定义多个数据, 都需要传递给子组件
      // name/age/height
      const info = reactive({
        name: "why",
        age: 18,
        height: 1.88
      })

      function changeInfoName(payload) {
        info.name = payload
      }

      // 使用readOnly包裹info
      const roInfo = readonly(info)
      function changeRoInfoName(payload) {
        info.name = payload
      }

      return {
        info,
        changeInfoName,
        roInfo,
        changeRoInfoName
      }
    }
  }
</script>

<style scoped>
</style>

5、Reactive知识点补充

5.1、Reactive判断的API

isProxy

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

isReactive

  • 检查对象是否是由 reactive创建的响应式代理:
  • 如果该代理是 readonly 建的但包裹了由 reactive 创建的另一个代理,它也会返回 true;

isReadonly

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

toRaw

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

shallowReactive

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

shallowReadonly

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

5.2、toRefs

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

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

  • Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref
  • 那么我们再次进行结构出来的 name 和 age 本身都是 ref的;

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

5.3、toRef

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

5.4、示例 

App.vue

<template>
  <div>
    <h2>info: {{ name }} - {{ age }} - {{ height }}</h2>
    <button @click="age++">修改age</button>
    <button @click="height = 1.89">修改height</button>
  </div>
</template>

<script>
  import { reactive, toRefs, toRef } from 'vue'
  
  export default {
    setup() {

        const info = reactive({
          name: "why",
          age: 18,
          height: 1.88
        })

        // reactive被解构后会变成普通的值, 失去响应式
        const { name, age } = toRefs(info)
        const height = toRef(info, "height")

        return {
          name,
          age,
          height
        }

    }
  }

</script>

<style scoped>
</style>

6、Ref知识点补充

unref

  • 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法
    • 如果参数是一个 ref,则返回内部值,否则返回参数本身
    • 这是 val = isRef(val) ? val.value : val 的语法糖函数;

isRef

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

shallowRef

  • 创建一个浅层的ref对象

triggerRef

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

7、setup中禁用this

官方关于this有这样一段描述:

  • 表达的含义是this并没有指向当前组件实例
  • 并且在setup被调用之前,data、computed、methods等都没有被解析;
  • 所以无法在setup中获取this

代码是按照如下顺序执行的:

  • 调用 createComponentInstance 创建组件实例;
  • 调用 setupComponent 初始化component内部的操作;
  • 调用 setupStatefulComponent 初始化有状态的组件;
  • 在 setupStatefulComponent 取出了 setup 函数;
  • 通过callWithErrorHandling 的函数执行 setup;

从上面的代码我们可以看出, 组件的instance肯定是在执行 setup 函数之前就创建出来的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【快速简单登录认证】SpringBoot使用Sa-Token-Quick-Login插件快速登录认证

一、解决的问题 Sa-Token-Quick-Login 可以为一个系统快速的、零代码 注入一个登录页面 试想一下&#xff0c;假如我们开发了一个非常简单的小系统&#xff0c;比如说&#xff1a;服务器性能监控页面&#xff0c; 我们将它部署在服务器上&#xff0c;通过访问这个页面&#xf…

学习字符串函数和内存函数必看

字符串函数 1.strlen函数 strlen库函数 #include<stdio.h> #include<string.h> int main() {char arr[] "abc";char arr1[] { a,b,c };int len strlen(arr);int len1 strlen(arr1);//没有\0就无法停止printf("%d\n",len);printf("%…

VUE中的provide和inject用法

一、Vue中 常见的组件通信方式可分为三类 父子通信 父向子传递数据是通过 props&#xff0c;子向父是通过 events&#xff08;$emit&#xff09;&#xff1b; 通过父链 / 子链也可以通信&#xff08;$parent / $children&#xff09;&#xff1b; ref 也可以访问组件实例&…

XLSReadWriteII 写了一个DBGrdiEh创建EXCEL表的函数

XLSReadWriteII 写了一个DBGrdiEh创建EXCEL表的函数 自己通过XLSReadWriteII&#xff0c;写了一个由DBGridEh数据集&#xff0c;通过参数调用&#xff0c;创建EXCEL表格的函数&#xff0c;通过调用的参数设置&#xff0c;可以较为方便地&#xff0c;创建指定数据集的常用EXCEL表…

自动化和Selenium

作者&#xff1a;~小明学编程 文章专栏&#xff1a;测试开发 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 什么是自动化&#xff0c;我们为什么需要自动化的测试&#xff1f; 为什么选择selenium来作为我们的web自动化测试的工具&#xff1f; 定位元素…

【C进阶】找单身狗

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;C语言进阶 ⭐代码仓库&#xff1a;C Advanced 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff0c;你们…

Spring事务(事务的实现、隔离级别、传播机制)

目录 一、事务的定义和意义 二、事务的实现 1、MySQL事务的回顾 2、Spring声明式事务&#xff08;利⽤注解⾃动开启和提交事务&#xff09; 前置知识及概念 实例分析&#xff1a;事务的回滚 &#x1f514;特殊情况&#xff08;无自动回滚&#xff09; &#x1f514;事务…

VueJs中如何使用provide与inject

前言在vue2.0里面provide与inject是以选项式(配置)API的方式在组件中进行使用的,解决的是跨组件(祖孙)间通信的一种方式也就是父子组件间的通信,父组件上通过自定义属性,而子组件间通过props这种方式接收,如果想要一层一层的传递,这种方式就会比较麻烦,不灵活provide与inject就…

Linux下的进程信号

目录 信号背景&#xff1a; 信号产生前 Core Dump 信号产生中 信号产生后 其他概念 不可重入函数 volatile关键字 SIGCHLD 17号信号 信号背景&#xff1a; 在生活中处处都存在的信号&#xff0c;比如信号灯&#xff0c;要想处理信号&#xff0c;我们就必须具备两种…

day26-XML/枚举/注解

1.xml 1.1概述【理解】 万维网联盟(W3C) 万维网联盟(W3C)创建于1994年&#xff0c;又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。 建立者&#xff1a; Tim Berners-Lee (蒂姆伯纳斯李)。 是Web技术领域最具权威和影响力的国际中立性技术标准机构。 到目前为…

AWS - IAM

AWS IAM 自用笔记。 Terms IAM - Identity and Access Management, a global service it gives: Centralized control of AWS accountShared access to AWS accountGranular permissionsIdentity Federation, i.e., Facebook, LinkedIn etc. it allows: Multi-Factor Authe…

20230121解决AIO-3568J开发板无法刷机以及串口异常的问题

20230121解决AIO-3568J开发板无法刷机以及串口异常的问题 2023/1/21 22:31 一片比较旧的AIO-3568J开发板&#xff0c;症状&#xff1a; 1、无法刷机&启动。【Loader模式 & MASKROM模式】 2、串口输出乱码&#xff01; &#xff08;一&#xff09;刷机问题的解决&#x…

一起自学SLAM算法:7.3 估计理论

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 不管是用贝叶斯网络还是因子图&#xff0c;一旦SLAM问题用概率图模型得到表示后&#xff0c;接下来就是利用可观测量&#xff08;和&#xff09;推理不可观测量&#xff08;和&#xff09;&#xff0c;也就是说S…

初识C语言(上)

写在前面 我们正式开始接触到C语言,这是我在学习过C语言后重新写的博客,我把之前的稍微优化了一下,希望能用更加朴素的语言和大家分享,我希望给大家带来一个可以看的懂,理论和实践并行的内容.当然里面也会存在一些错误和不恰当的地方,还请诸位指正. 为何学习C语言 我想从两个…

mac 疑难问题汇总

macos 更改zsh到bash查看当前系统有哪些bash命令行&#xff1a;cat /etc/shells切换成bash命令行&#xff1a;chsh -s /bin/bashmac触摸屏轻点设置Mac通过crontab设置定时任务报错Operation not permitted1、系统偏好设置->安全性和隐私->完全磁盘访问权限2、解除锁定允许…

【双向链表】java代码实现

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#x1f353;&#x1f353;&#x1f353;&#xff0c;上一篇文章我和大家一起去探索了单链表的知识世界&#xff0c;今天我们去接触双向链表&#xff0c;什么&#xff1f;没听错&#xff0c;就是双向链表&#xff0c;比单链表更…

UE INI文件操作 INI File Operation [ Read / Write ] 插件说明

在 Windows 平台上的 INI 文件读写操作 1. Write INI String 输入&#xff1a; Directory&#xff1a; 选择保存目录 Project Directory &#xff1a; 当前项目目录Project Content Directory&#xff1a;当前项目 Content 目录Project Config Directory&#xff1a;当前项目…

JVM内存区域的划分

根据 JVM 规范&#xff0c;JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。 程序计数器&#xff08;线程私有&#xff09;&#xff1a; 是当前线程所执行的字节码的行号指示器&#xff0c;每条线程都要有一个独立的程序计数器&#xff0c;这类内存也称为…

Java基础08:面向对象进阶(上)

Java基础08&#xff1a;面向对象进阶&#xff08;上&#xff09;一、static1. 静态变量2. 静态方法3. 工具类4. static注意事项5. 重新认识main方法二、继承1. 继承的概念2. 继承的特点3. 继承中访问成员变量4. 继承中访问成员方法&#xff08;方法重写&#xff09;5. 继承中访…

给大家准备了程序员专属红包封面,审核通过后我哭了,太心酸了(┬_┬)

大家好&#xff0c;我是小悟 今天就是除夕了&#xff0c;也就是大年三十&#xff0c;小伙伴们应该都回家了吧&#xff0c;小悟祝大家新年快乐&#xff0c;身体健康&#xff0c;万事如意&#xff0c;兔飞猛进哦。 春节临近&#xff0c;收到微信定制红包封面并送了四千个名额的…