Vue
5️⃣Composition API
00 分钟
2022-8-22
2024-8-1

一、前言

(一)Option API 与 Composition API

  • Vue2中,我们编写组件的方式是Options API
    • Options API的一大特点就是在对应的属性中编写对应的功能模块;
    • 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;
弊端:
  • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
  • 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散
  • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
Vue2 Option API
Vue2 Option API
 
Vue3 Composition API
Vue3 Composition API

二、Mixin

(一)基本使用

如果组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取。在Vue2和Vue3中都支持的一种方式是使用Mixin来完成(Mixin本质是「对象」)
Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能,一个Mixin对象可以包含任何组件选项。当组件使用Mixin对象时,所有Mixin对象的选项将被混合进入该组件本身的选项中。
 

(二)合并规则

  • 情况1️⃣:如果是data函数的返回值对象
    • 返回值对象默认情况下会进行合并;
    • 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据;
  • 情况2️⃣:如果是生命周期钩子函数
    • 生命周期的钩子函数会被合并到数组中,都会被调用
  • 情况3️⃣:值为对象的选项,例如methods、components 和directives,将被合并为同一个对象。
    • 比如都有methods选项,并且都定义了方法,那么它们都会生效;
    • 但是如果对象的key相同,那么会取组件对象的键值对;
 

(三)全局混入

即全部组件都包含mixin的属性
 

三、Extends

作为补充,但是完全可以使用Mixin替代。只继承BasePage中的 export对象
 

四、setup()

(一)基本使用

为了开始使用Composition API,我们可以通过 setup 函数使用Composition API,但需要注意setup函数 没有绑定this
 
  • 我们先来研究一个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 发出事件);
    •  

(二)返回值

setup() 返回值:
  • setup() 的返回值可以在模板template中被使用;
  • 也就是说我们可以通过 setup() 的返回值来 替代data 选项;( setup() 优先级比 data 高)
 
setup() 不可以使用 this
  • this 并没有指向当前组件实例(表明已经实例化了);
  • setup() 被调用之前,datacomputedmethods 等都没有被解析;
  • 所以无法在 setup() 中获取 this
 

五、reactive API

响应式通过Porxy对象数据劫持
 
Reactive API 判断(偏向底层API)
  • isProxy
    • 检查对象是否是由 reactivereadonly 创建的proxy。(此处也表明reactive和readonly的底层原理)
  • isReactive
    • 检查对象是否是由 reactive 创建的响应式代理:
      如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回true;
      ⭐例如 const info = readonly(reactive({name: "alan"})) // true
  • isReadonly
    • 检查对象是否是由 readonly 创建的只读代理。
  • toRaw
    • 返回reactive 或readonly 代理的原始对象(建议保留对原始对象的持久引用。请谨慎使用)。
      例如:const info = reactive({name: "why"}); info.toRAW // 返回{name: "why"}原始对象
  • shallowReactive
    • 创建一个响应式代理,它跟踪其自身property 的响应性,但不执行嵌套对象的深层响应式转换(深层还是原生对象)。
  • shallowReadonly
    • 创建一个proxy,使其自身的property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
💡
全部都通过 import { toRaw } from 'vue'; 导入;shallowXxx 将默认的深层操作转化为浅层
 

六、ref API

reactive API对 传入的类型是有限制的,它要求我们必须传入的是 一个对象 或者 数组类型;如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
模板中引入 ref 的值时,Vue会自动帮助我们进行 解包操作,所以我们并不需要在模板中通过ref.value 的方式来使用;
但是在 setup 函数内部,它依然是一个 ref 引用, 所以对其进行操作时,我们依然需要使用ref.value 的方式;【注意上述 ref 都是浅解包,如果外层加入对象则不会解包;但如果使用Reactive API,即最外层包裹的是一个 reactive 可响应式对象,则 可以自动解包(内部对Reactive进行判断)】
 
Ref API
  • unref 如果我们想要 获取一个ref引用中的value,那么也可以通过unref方法: 如果参数是一个ref,则返回内部值,否则返回参数本身; 这是 val = isRef(val) ? val.value : val 的语法糖函数;
  • isRef 判断值 是否是一个ref对象
  • shallowRef 创建一个 浅层的ref对象
  • triggerRef 手动触发和shallowRef 相关联的副作用:
第三、四点案例
 

七、readonly API

通过 reactive或者ref 可以获取到一个响应式的对象,但是某些情况下,我们 传入给其他地方(组件)的这个响应式对象希望另外一个地方(组件)被使用,但是 不能被修改,这个时候Vue3为我们提供了 readonly 的方法;
readonly 会返回原生对象的 只读代理(也就是它依然是一个Proxy,并且对proxy的set方法进行劫持,并且不能对其进行修改);
⭐ 通常传入子组件时需要传入响应式的对象,通常使用第二种方法
 

八、toRefs / toRef API

如果我们使用ES6的 解构 语法,对 reactive 返回的对象进行解构 获取值,那么之后无论是 修改结构后的变量,还是修改 reactive 返回的state对象,数据都不再是响应式的。实现响应式可以通过 toRefs 或者 toRef
⭐ 传入的参数是 reactive 对象
 

九、customRef API

创建一个 自定义的ref,并对其 依赖项跟踪和更新触发 进行显示控制:它需要一个 工厂函数,该函数接受 tracktrigger 函数作为参数;并且应该返回一个带有 getset 的对象;
案例:双向绑定的属性进行debounce(节流)的操作
 

十、computed API

计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理;
在前面的Options API中,我们是使用 computed 选项来完成的(Vue2);
在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性,其返回一个 ref 对象;
 

十一、watch API

总结
在前面的Options API中,我们可以通过 watch 选项来侦听 data 或者 props 的数据变化,当数据变化时执行某一些操作;
在Composition API中,我们可以使用 watchEffectwatch 来完成响应式数据的侦听: watchEffect 用于自动收集响应式数据的依赖; watch 需要手动指定侦听的数据源;

(一)watchEffect API

基本使用

(二)停止监听

(三)清除副作用

比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
解决方法:在我们给 watchEffect 传入的函数被回调时,其实可以获取到一个参数:onInvalidate 当 副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;我们可以在传入的回调函数中,执行一些清除工作;

(四)setup中使用ref

在Vue2中我们可以使用 ref 作为属性绑定在标签上,然后在 method 等地方通过 this.$ref.xxx 进行访问该元素,但是在 setupthis 并没有指向,所以通过其他方法实现。

(五)watchEffect 执行时机

上述代码会打印两次值
如果我们希望在第一次的时候就打印出来对应的元素呢?
这个时候我们需要改变副作用函数的执行时机,增加第二个参数 flush: 它的默认值是 pre,它会在元素挂载或者更新之前执行;所以我们会先打印出来一个空的,当依赖的title发生改变时,就会再次执行一次,打印出元素;我们可以设置为 postflush 选项还接受 sync,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。

(六)watch API

watch API完全等同于组件 watch 选项的Property:watch 需要侦听特定的数据源,并在回调函数中执行副作用;默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
watchEffect 的比较,watch 允许我们:
懒执行副作用(第一次不会直接执行); 更具体的说明当哪些状态发生变化时,触发侦听器的执行; 访问侦听状态变化前后的值;

监听单个数据源

watch侦听函数的数据源有两种类型:1️⃣一个 getter 函数:但是该 getter 函数必须引用可响应式的对象(比如 reactive 或者 ref);2️⃣直接写入一个可响应式的对象,reactive 或者 ref(比较常用的是 ref);
💡
相关源码补充:package/runtime-core/apiWatch.ts
传入的source会经过 isRef()isReactive()isArray()isFunction() 判断

监听多个数据源

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

(七)侦听响应式对象

 

十二、生命周期 API

生命周期函数
生命周期函数
如果有需要在挂载之前进行执行的操作可以直接放在 setup 里面,即 setup 围绕 beforeCreatecreated 生命周期钩子运行的
 

十三、Provide × Inject

上述案例子组件修改父组件的数据是不合规的,可以传入 provide 时候用 readonly 进行修饰
 

十四、composition API 案例

(一)封装到index

把Hook进行包装

(二)简单的逻辑抽离

将部分逻辑抽取到外部 hook 中,简化主体App的逻辑

(三)修改标题案例

(四)监听界面滚动和鼠标位置

(五)本地储存

 

十五、顶层编写方式

需要注意组件传值需要用到 defineProps,具体代码如下:
更多适用情况见官网
 
 
notion image

十六、h函数

Vue 推荐在绝大数情况下使用模板来创建你的HTML,然后一些特殊的场景,你真的需要JavaScript的完全编程的能力,这个时候我们可以使用 渲染函数,它比模板更接近编译器;
💡
template中代码转化为真实DOM流程
notion image
其接受三个参数:1️⃣tag,必须传值,一个HTML标签名、组件、一句组件,E.g. “div”;2️⃣props,与attribute、prop和事件相对应的对象,我们会在模板中使用;3️⃣children,子VNodes,使用`h()`构建或使用字符串获取“文本VNode”或有插槽的对象,例子如下:
💡 如果没有props,那么通常可以将children作为第二个参数传入;✅如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入;
 
1️⃣ 基本使用
2️⃣ 利用h函数实现加减点击操作
3️⃣ 传入组件并使用插槽
 

十七、jsx

如果我们希望在项目中使用jsx,那么我们需要添加对jsx的支持:jsx我们通常会通过Babel来进行转换(React编写的jsx就是通过babel转换的);对于Vue来说,我们只需要在Babel中配置对应的插件即可(目前脚手架已支持可以不用额外配置)
💡其本质是通过Babel转成h函数
插件安装:npm install @vue/babel-plugin-jsx -D
 
引用其他组件案例
 

十八、自定义指令

基础

在Vue的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model 等等,除了使用这些指令之外,Vue也允许我们来自定义自己的指令。
注意:在Vue中,代码的复用和抽象主要还是通过「组件」;通常在某些情况下,假如需要对DOM元素进行底层操作,这个时候就会用到「自定义指令」;
自定义指令分为两种: 1️⃣ 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用; 2️⃣ 自定义全局指令:app的 directive 方法,可以在任意组件中被使用;
比如我们来做一个非常简单的案例:当某个元素挂载完成后可以自定获取焦点 实现方式一:如果我们使用默认的实现方式; 实现方式二:自定义一个 v-focus 的局部指令; 实现方式三:自定义一个 v-focus 的全局指令;

实现方式一:聚焦的默认实现

通过原生的方式搭配生命周期回调函数实现
 

实现方式二:局部自定义指令

 

实现方式三:自定义全局指令

自定义一个全局的 v-focus 指令可以让我们在任何地方直接使用
 

指令的生命周期

一个指令定义的对象,Vue3 提供了如下的几个钩子函数:
 
案例代码:

Demo-时间戳转化展示

在开发中,大多数情况下从服务器获取到的都是时间戳。我们需要将时间戳转换成具体格式化的时间来展示;

十九、Teleport与插件

Teleport基础

之前组件化开发 中,我们封装一个组件A,在另外一个组件B中使用:那么组件A中template的元素,会被挂载到组件B中template的某个位置;最终我们的应用程序会形成 一棵DOM树结构
新情况:但是某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置:比如移动到body元素上,或者我们有其他的div#app之外的元素上。个时候我们就可以通过 teleport 来完成;
Teleport是一个Vue提供的内置组件,类似于react的Portals。teleport翻译过来是心灵传输、远距离运输的意思;它有 两个 属性: 1️⃣ to:指定将其中的内容移动到的目标元素,可以使用选择器; 2️⃣ disabled:是否禁用 teleport 的功能;
 
案例代码
 

Vue插件

通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式: 1️⃣ 对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行; 2️⃣ 函数类型:一个function,这个函数会在安装插件时自动执行
 
类型1️⃣:plugins_objects
在组件setup中使用:
 
类型2️⃣:plugins_function
代码自动执行
 
 
notion image

上一篇
Animation
下一篇
浏览器原理 × V8引擎

评论
Loading...