从Vue 2到Vue 3组织代码的思维转变

到了Vue 3,当我们看完组合式 API相关文档,蠢蠢欲动立马上手把业务代码挪到setup内时,竟发现无法在setup()内部通过 this 获取当前组件实例了(this 是 undefined)!

其实想要变相获取“this”可以用 getCurrentInstance ,比如像这样访问全局属性:

真正属于组件的内容是ctxproxyproxy就是对ctx包装了一层 Proxy。目前看来属实没用,就不展开来说了。

getCurrentInstance 只能setup生命周期钩子中调用。不仅使用起来麻烦,且只适用于开发环境。官方都告知我们日常开发中不要用:

貌似到头来this的问题还是无解。Vue 3 Composition API 的设计初衷也是为了减少对组件实例的依赖,避免this指向的困扰。包括一些全局方法也可以提取到 composables 组合函数中,无需再通过当前组件的原型链获得。我们动不动就通过this获取组件实例的思想该转变了

怎么个直接使用法?我们先看一眼简易版 选项式 API组合式 API 的对比。再用例子来捋:

API 对比

1. 读写数据

Vue 2的做法,或者说Option API更准确 (后面就简写成Vue 2)

data中定义,this.xxx获取
<template>模版中不需要用this

Vue 3 的 Composition API (后面就简写成 Vue 3)

ref reactive定义。
通过ref方法定义的属性在 setup 函数内需要通过.value去访问它的值 (template 模版内不用), reactive则不用。我们可以简单地把 ref(obj) 理解为 reactive({ value: obj })
详细请看 ➡️ 【Vue 3 之:弄清 ref reactive toRef toRefs】

如何选择 refreactive?建议:

  1. 基础类型值(StringNumberBoolean等) 或单值对象(类似{ count: 3 }这样只有一个属性值的对象) 使用 ref
  2. 引用类型值(ObjectArray)使用 reactive
  3. 对于 ref 对象可以使用 unref 语法糖来免去.value访问的困扰

2. 定义/调用方法

Vue 2:methods

继续上面的例子,我们把请求列表数据的操作提取成一个方法。(略去重复的代码)

Vue 3

3. 获取模版DOM元素/或组件实例的引用

Vue 2

直接整上 element-plus Form 表单 的的例子,<el-form>是我们写的DemoForm组件的一个子组件,通过为它添加一个refattribute,就能使用使用this.$refs[ref值]直接访问子组件以及它的所有属性/方法了。
这边只放相对实际的场景用例,给 DOM 元素添加 ref 引用的例子看这里:【模板引用】

Vue 3

我们来修改成 Composition API 版本:
别忘了 ref 创建的变量要用.value获取值
详细文档请阅:【在组合式 API 中使用 template refs】

4. 父组件向子组件传值

文档:Props
使用 DOM 模板时,camelCase (驼峰命名法) 的 props 需要使用等价的 kebab-case (短横线分隔命名) 命名。

说明:第 4-8 点 都会结合同一个 Tabs 标签页的例子,循序渐进梳理这几个属性或方法。依然用了element-plus 的组件(TabsTabPane)。

Vue 2

父组件:📃src/views/design/indexV2.vue

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

转化成 Composition API 后的子组件:(一贯省略重复代码)
📃src/views/design/components/DesignTabsV3.vue

5. 计算属性computed

比如我们现在希望在点击 tab 标签的时候能获得 tab 的序号(index):

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

这里就比较不一样了,props 的值是不允许在子组件直接修改的。子组件的activeName状态初始值为传入的activeTab,如果activeTabtabOptions一样通过 toRefstoRef 包装,那么activeName的修改是和activeTab深度响应的,这样会报错;
如果要同步修改父组件的值,可以通过emit事件(后面会说)

组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

这里想额外提一嘴computed可以包在reactive内使用,在组件数据比较简单的情况下甚至可以直接用reactive包起组件全部数据,就好像 Option Api 的 data 选项那样。这样做的目的是在setup()访问不用再带上.value
但这同时会带来一个问题,在模版需要使用state.xx去渲染,而且如果我们 return 的时候把state解构,包含的状态会失去响应性,那就得不偿失了。需要用toRefs包裹再传递,即可维持其响应性。

6. 侦听器watch

获取这个tabIndex有什么用呢,目的是在当前标签变化时根据index刷新对应TabPane的子组件的数据。但是如果在标签点击事件触发数据刷新也不合适,因为我们不希望重复点击相同tab时也去刷新。那么这个时候watch或者watchEffect就登场了。

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

这个写法可能不是很直观,可以看下 ➡️ 组合式 API 模板引用在 v-for 中的用法
组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

watchwatchEffect 的功能是等效的,都是侦听其依赖,并在依赖值变更时重新运行定义的函数。两者区别:

watch

  • 必须在第一个参数明确指定跟踪的依赖
    侦听器数据源只能是getter/effect函数、refreactive对象,或者包含这些类型(的数据)的数组
    换句话说,只要侦听数据不是refreactive对象,就必须传入一个箭头函数
    打个比方,若要侦听reactive对象的某个属性(例:const state = reactive({ count: 0 })count),便不能像侦听单个ref或整个reactive对象那样直接传一个变量,而是必须在第一个参数传入一个回调函数,如() => state.count
  • 第二个参数是依赖值变更时执行的回调,函数内能访问被侦听状态的当前值和前一个值;
  • 组件初始化时不会执行回调。如果需要可在第三个参数(Object)中设置immediate: true
  • 如果要对多层嵌套状态深度侦听,在第三个参数中设置deep: true

watchEffect

  • 无需手动传入依赖项;
  • 只有一个参数,即侦听数据变更的回调函数,会自动跟踪所有函数中用到的变量;
  • 组件初始化时即会执行一次。

7. 子组件向父组件通信(触发父组件方法)

Vue 2

父组件:📃src/views/design/indexV2.vue

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

Vue 3

子组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

8. 使用 Vue Router

Vue 2

📃src/views/design/components/DesignTabsV2.vue

Vue 3

📃src/views/design/components/DesignTabsV3.vue

9. 获取 Vuex 对象

Vue 2

Vue 3

参考链接


从 Vue 2 到 Vue 3 组织代码的思维转变(一)- 获取组件实例

发布者

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注