七、Vue3 Composition API (一)
Lyk 2022/8/26 VueSetup函数界面响应式数据reactive函数readonly函数关于reactive的其他APItoRefs 函数ref函数关于ref的其他APISetup禁用this
# 1、Options API的弊端
- 在Vue2中,我们编写组件的方式是Options API:
- Options API的一大特点就是在对应的属性中编写对应的功能模块;
- 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;
- 但是这种代码有一个很大的弊端:
- 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;
- 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;
- 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);
- 下面我们来看一个非常大的组件,其中的逻辑功能按照颜色进行了划分:
- 这种碎片化的代码使用理解和维护这个复杂的组件变得异常困难,并且隐藏了潜在的逻辑问题;
- 并且当我们处理单个逻辑关注点时,需要不断的跳到相应的代码块中;
# 2、大组件的逻辑分散
- 如果我们能将同一个逻辑关注点相关的代码收集在一起会更好。
- 这就是Composition API想要做的事情,以及可以帮助我们完成的事情。
- 也有人把Vue Composition API简称为VCA。
# 3、认识Composition API
- 那么既然知道Composition API想要帮助我们做什么事情,接下来看一下到底是怎么做呢?
- 为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方;
- 在Vue组件中,这个位置就是 setup 函数;
- setup其实就是组件的另外一个选项:
- 只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;
- 比如methods、computed、watch、data、生命周期等等;
- 接下来我们一起学习这个函数的使用:
- 函数的参数
- 函数的返回值
# 4、setup函数
# 4.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发出事件);
# 4.2.setup函数的返回值
- setup既然是一个函数,那么它也可以有返回值(return {}),它的返回值用来做什么呢?
- setup的返回值可以在模板template中被使用;
- 也就是说我们可以通过setup的返回值来替代data选项;
- 甚至是我们可以返回一个执行函数来代替在methods中定义的方法:
- 但是,如果我们将 counter 在 increment 或者 decrement进行操作时,是否可以实现界面的响应式呢?
- 答案是不可以;
- 这是因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作;
- 要实现响应式,我们需要借助vue提供的reactive函数 或 ref函数
# 5、reactive API
- 如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数:
- 那么这是什么原因呢?为什么就可以变成响应式的呢?
- 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集;
- 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
- 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
# 6、ref API
# 6.1.ref API
- reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:
- 如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;
- 这个时候Vue3给我们提供了另外一个API:ref API
- ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
- 它内部的值是在ref的 value 属性中被维护的;
- 这里有两个注意事项:
- 在模板中引入ref的值时,Vue会自动帮助我们进行浅层解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用;
- 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式
# 6.2.ref自动解包
- 模板中的解包是浅层的解包,如果我们的代码是下面的方式:
- 如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包
# 7、readonly API
# 7.1.认识readonly
- 我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?
- Vue3为我们提供了readonly的方法;
- readonly会返回原始对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改);
- 在开发中常见的readonly方法会传入三个类型的参数:
- 类型一:普通对象;
- 类型二:reactive返回的对象;
- 类型三:ref的对象;
# 7.2.readonly的使用
- 在readonly的使用过程中,有如下规则:
- readonly返回的对象都是不允许修改的;
- 但是经过readonly处理的原来的对象是允许被修改的;
- 比如 const info1 = readonly(info),info1对象是不允许被修改的;
- 当info被修改时,readonly返回的info1对象也会被修改;
- 但是我们不能去修改readonly返回的对象info1;否则会警告【注意:这里info1对象不管是浅层还是深层都是不可修改的,否则会报警告】;
- 其实本质上就是readonly返回的对象的setter方法被劫持了而已;
# 7.3.readonly的应用
- 那么这个readonly有什么用呢?
- 在我们传递给其他组件数据时,往往希望其他组件使用我们传递的内容,但是不允许它们修改时,就可以使用readonly了;
# 8、关于reactive的其他API
# 8.1.Reactive判断的API
- isProxy
- 检查对象是否是由 reactive 或 readonly创建的 proxy。
- isReactive
- 检查对象是否是由 reactive创建的响应式代理:
- 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
- isReadonly
- 检查对象是否是由 readonly 创建的只读代理。
- toRaw
- 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
- shallowReactive
- 创建一个响应式代理,它跟踪其自身 property(第一层) 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原始对象)。【只处理对象最外层属性的响应式(浅响应式)】
- shallowReadonly
- 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)
# 8.2.toRefs
- 如果我们使用ES6的解构语法,通过对reactive函数处理后,返回的对象进行解构获取值,那么之后无论是修改解构后的变量,还是修改infoReactive;解构出来的变量都不再是响应式的【这里指如下图的:age属性】:
- 那么有没有办法让我们解构出来的属性是响应式的呢?
- Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
- 那么我们再次进行解构出来的 name 本身都是 ref的;
- 这种做法相当于已经在infoReactive.name和ref.value(name.value)之间建立了 链接,任何一个修改都会引起另外一个变化;
# 8.3.toRef
- 如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
# 9、关于ref其他的API
- unref
- 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:
- 如果参数是一个 ref,则返回内部值,否则返回参数本身;
- 这是 val = isRef(val) ? val.value : val 的语法糖函数;
- isRef
- 判断值是否是一个ref对象。
- shallowRef
- 创建一个浅层的ref对象;【只处理基本数据类型的响应式, 不进行对象的响应式处理。】
- triggerRef
- 手动触发和 shallowRef 相关联的副作用:
# 10、setup不可以使用this
# 10.1.组件实例是在setup函数执行之前创建出来的
- 在阅读源码的过程中,代码是按照如下顺序执行的:
- 调用 createComponentInstance 创建组件实例;
- 调用 setupComponent 初始化component内部的操作;
- 调用 setupStatefulComponent 初始化有状态的组件;
- 在 setupStatefulComponent 取出了 setup 函数;
- 通过callWithErrorHandling 的函数执行 setup;
- 从上面的代码我们可以看出, 组件的instance(实例)肯定是在执行 setup 函数之前就创建出来的。
- 提出问题:
- 我们在vue2中使用options api时,可以使用this;拿到组件实例
- 但是当我们使用vue3中的composition api时,我们在setup中却无法使用this。this拿到的是undefined (严格模式下的独立调用,绑定的this是undefined;非严格模式下则是window)
- 原因是:我们在options api中对其中的一些属性进行绑定,但是在composition api中的setup函数的执行并没有绑定实例对象,下面我们看一下源码进行理解。
# 10.2.Options API 为什么this可拿到组件实例
- 下图为
options api
中的methods
选项和生命周期函数的部分源码,我们可以看到当执行函数时,会使用bind
进行绑定;所以使用Options API ,可以用this拿到组件实例
# 10.3.composition API 的setup函数中this不是组件实例
- 下面我们将从
composition api
的setup
函数的内部实现原理进行解析。 - 下图为
setup
执行的代码,此时我们在上图的最后一个箭头执行可以看出,并没有绑定任何东西,所以此时在setup中不可以使用this
(this拿不到组件实例)。【严格模式下,独立函数调用,this为undefined】