【Vue3】保姆级毫无废话的进阶到实战教程 - 02

上一篇文章主要引入 Vue3 中的一些 Big Changes,这篇来深入探讨三个东西:

  1. <script setup> 的真正优势
  2. 对比 Vue 2 中的 Mixins,Vue 3 中的 Composable 好在哪里?
  3. 你没见过的隐藏高级技能 - defineModel

<script setup> 的真正优势

这个实际上在官方是有说明的:sfc-script-setup,但是却没有做更进一步的解释了。

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和自定义事件。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

更简洁的代码

setup 刚出来那会儿,作为一个长时间写 React 的人对下面这种每一个变量和方法都要手动 return 出去的写法,真的觉得非常的不适应,这哪是高级语言该有的东西!心中有说不出的万马奔腾感:

好在后面出了 <script setup> 这个语法糖,减少了模板代码,做了简化,在上一篇文章中我已经着重解释过了,这里就不废话了,贴一张对比图,直观明了:

小技巧 :推荐大家一个提升协作效率的工具,VS Code 插件:CodeSnap,直接在编辑器中生成代码美化的图片。

你只需要要将需要生成的代码选中,然后右键选择 CodeSnap,点击上方的 Logo,即可生成图片,实在方便!

更好的运行性能

回过头来,就一直想知道一个问题:setup() 写法为什么要 return?

查了一些资料,结论是是因为 SFC 会将 <template><script> 中的内容编译成组件的渲染函数。而 <script><script setup> 在性能上的差异,就在于渲染函数从 <script> 中取得 <template> 所需变量的方式不同。

<script> + setup()

官方文档中有提到,在开发过程中 ,基于开发工具检查模板热重载 的原因,使用 <script setup> 开发的组件,还是会被编译成回传组件,而不是直接回传给渲染函数。

也就是说,想看到 <script setup><script> 差别,要看正式编译打包后的产物( npm run build)。

直接编译出来的产物实在很难读,最好在 vite.config.ts 中加一个编译配置:

产物中的函数名可以保持原来的命名,便于阅读和理解。

然后 pnpm run build 一下,来看看这种写法的编译结果:

可以看到,setup()函数和渲染函数会分开声明,各自形成自己的闭包。所以,声明在 setup() 内的变量需要 return 出去,才能传给渲染函数,在渲染 template 的时候才能取到所有变量。

<script setup>

这种语法糖的写法不需要经过中间代理,运行时性能比较好。

组件的渲染函数会存在在 setup()scope 内,也就是将渲染函数写在 setup 的闭包内,最后才回传出去。所以,渲染函数可以通过闭包拿到外层 setup 函数( <script setup>) 内,所有 top-level 变量(包含:变量、函数和 imports),也不用担心泄漏过多变量或逻辑。

看看这种写法的编译结果:

渲染函数可以通过必报直接拿到外层的变量和方法等等。你搞懂了吗!

与普通的 <script> 一起使用

来自:官方文档

<script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有这些需要的情况下或许会被使用到:

  • 声明无法在 <script setup> 中声明的选项,例如 inheritAttrs 或插件的自定义选项 (在 3.3+ 中可以通过 defineOptions 替代)。
  • 声明模块的具名导出 (named exports)。
  • 运行只需要在模块作用域执行一次的副作用,或是创建单例对象。

在同一组件中将 <script setup><script> 结合使用的支持仅限于上述情况。具体来说:

  • 不要 为已经可以用 <script setup> 定义的选项使用单独的 <script> 部分,如 propsemits
  • <script setup> 中创建的变量不会作为属性添加到组件实例中,这使得它们无法从选项式 API 中访问。我们强烈反对以这种方式混合 API。

如果你发现自己处于以上任一不被支持的场景中,那么你应该考虑切换到一个显式的 setup() 函数,而不是使用 <script setup>

Composable(组合式函数)

在 Vue 2 中组件之间可复用逻辑通常会用 Mixins。但现在有了 Composable(官方翻译为:组合式函数),可以以更简洁的方式实现代码的复用。

大概写过 React 的人看了 Vue 3 的 Composition API,难免直呼"内行"!这可不就是 React HooksComposable 不就是 React Custom Hooks


这下好了,面试官又有的可问了:同学,我看你骨骼惊奇,居然 React 和 Vue 双修成仙,那你讲讲 Vue3 Composition API 和 React Hooks 的区别呗?

实际上,但它俩还是有区别的,一言两语根本讲不完,放这篇文章肯定是不合适的,后续专门写一篇文章来讲这个问题!

先来讲讲为什么会出现 Composable

Mixins 的缺点

一个新事物的出现必然是为了改善旧事物的不足的,过去在 Vue 中实现逻辑复用主要是用到 Mixins

先来看个例子,比如你接手了一个旧项目,然后你写了一段可复用的逻辑,将其封装成了一个 Mixins,但是你没注意到之前的同学也封装了一个 Mixins ,里面有个同名的方法,这两个 Mixins 长这样:

然后你自信满满,信手拈来,引入到了组件中:

想想,调用 getData 方法会发生什么?答案是:数组中的第二个 mixin ( itemsMixin ) 会覆盖 userMixin 中定义的 getData 方法。

除了这种方法的互相覆盖,还有变量的覆盖,跟注入组件中的变量的冲突等等之类的问题。

来说说 Mixins 的一些缺点:

  1. 命名冲突 :当多个 Mixins 中具有同名属性或方法时,会造成命名冲突,导致出现不可预期的结果。当然也有一些办法来解决这个问题,比如在 mixins 中使用特殊前缀或命名空间来避免冲突。

  2. 紧耦合,依赖关系难以追踪 :在 mixins 和组件之间遇到隐式依赖关系的情况并不少见。这使得在不破坏现有代码的情况下重构组件或 mixins 变得极其困难。随着新需求的出现,mixins 还可以与其他 mixins 紧密耦合。解决这个问题,可以使用更明确的依赖注入方式,例如 provide/inject

  3. 难以理解和调试 :对于新开发人员来说,理解包含大量 mixins 的代码库非常困难。属性的来源并不明显,尤其是在存在全局注册的 mixins 的情况下。随着应用程序的增长,隔离错误也成为一场噩梦。

  4. 复用逻辑难以维护 :使用 mixins 可以实现逻辑复用,但是在应用复杂逻辑时,如果多个 mixins 互相依赖或冲突,会使得逻辑变得难以维护和理解。解决这个问题,可以采用更清晰和结构化的代码组织方式,例如使用高阶组件插件

Composable

在了解了 mixins 的缺点之后,我们用最新的 Composable 来改写一下上面的这个例子:

然后你可以在组件中这么使用:

这不仅可以控制对外暴露哪些数据,还可以你在使用的时候通过解构来重命名你的变量和方法,很好地解决了 Mixins 的那些问题。

很多初学者可能会觉得这个心智模型有点难,其实就很简单,比如封装一个常用的 API 请求的 Composable

怎么用呢?

你只需要记住:只要可复用的逻辑,都应该封装成公共方法或者 Composable,如果这段逻辑中使用了 Composition API(ref、reactive...),那你就封装成以 "use" 开头的 Composable,否则,就将其封装成普通的公共方法。

你不能说它跟 React Hooks 一模一样(比如 refuseState,还是不一样的),只能说是基本相同,哈哈。

反正就是借鉴呗 ~

推荐两个高质量的第三方 composable 库

VueUse

类似 React 中的 ahooks,提供了一系列高质量、常用的自定义 Hooks。

VueUse 使用 TypeScript 编写,提供 200 多个可组合函数,适用于 Vue 2 和 3。

shell 复制代码
npm i @vueuse/core

vue-composable

vue-composable 是一个库,提供即用型通用组合 API 函数。你可以使用 Yarn 或 npm 安装它:

shell 复制代码
npm install @vue/composition-api vue-composable

它提供的部分功能类别包括事件、日期、格式、断点、存储、i18n 和 Web。

隐藏高级技能 - defineModel

VueJS 3.3 版本 中,推出了一个新的语法糖 - defineModel,可以让我们以一种非常优雅的方法来支持 v-model 的双向绑定,但需要注意的是,当前这个宏还是默认未开启状态

是的,截至目前(2023-09-17),连官方网站都还没挂上去:

推荐阅读:pull/8018

下面,我们就来讲讲这是个什么东西!

在旧版本的 Vue 中,创建具有双向绑定功能的 Vue 元素需要两个步骤:

  1. 首先,组件必须声明一个属性以接受来自父组件的数据。
  2. 随后,它需要发出一个具有相应名称的更新事件,以便在数据更新时通知父组件。

这一过程不仅出现了模板代码,还增加了组件开发的复杂性,比起 React,真的是相当别扭。

不过,随着 VueJS 3.3 版本 的发布,现在可以利用 defineModel 宏。这个宏大大简化了双向绑定的过程。

需要注意的是:编译时,宏会声明一个具有相同名称的道具和一个相应的事件 update:propName。这是一个实验性功能,引入了一个名为 defineModel 的新编译器脚本选项,默认情况下是禁用的。 vuejs/rfcs#503

defineModel 宏会自动处理这些任务,而不是手动声明 props 和发出更新事件。让我们来看一个例子,看看以前的双向绑定是如何实现的,而 defineModel 宏又是如何轻松实现相同的功能。

旧实现

ParentComponent.vue

ChildComponent.vue

测试:

当父组件使用属性 :model-value="modelValue" 将 prop modelValue 传递给子组件,而子组件使用 emit("update:modelValue", value) 函数向父组件发送更新事件时,就建立了双向绑定。

新实现

需要注意的是,defineModel 目前还是默认关闭的,要使用它,需要在 vite.config.ts 中打开:

ParentComponent.vue

ChildComponent.vue

测试:

使用 defineModel 创建一个数值模型,该模型将在父组件和子组件之间共享。如你所见,新方法更加简洁明了。宏自动注册道具并返回一个可直接更改的引用,而不是显式声明 props 并发出事件,从而提供了一种连续的双向数据绑定体验。

使用 defineModel() 实现多个双向绑定

假设我们有一个父组件,需要使用多个双向绑定与子组件通信。我们将使用三个 props:countcountername。父组件将向子组件发送这些 props 的更新值,而子组件则可以修改并发回更新值。

ParentComponent.vue

ChildComponent.vue

测试:

通过利用响应式变量和 props,我们实现了父组件(ParentComponent)和子组件(ChildComponent)之间的高效双向通信。这一强大的功能确保了组件之间无缝的数据同步,从而实现了连贯、同步的用户体验。让我们深入了解这种方法的机制,见证它的实际效果。

附:Vue 3 迁移指南


相关推荐
IT女孩儿21 分钟前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡1 小时前
commitlint校验git提交信息
前端
天天进步20152 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz2 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇2 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒2 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员2 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐2 小时前
前端图像处理(一)
前端
程序猿阿伟2 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒2 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript