前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Vue3基础小结 从建项目到Teleport的完整上手路径

首页2021-02-16 16:12:12Front-End
VueVue3

用 Vue 2 写了两三年,第一次开 Vue 3 的项目,最先卡住的往往不是组合式 API,而是一堆细碎的变化。脚手架到底该用哪个,生命周期钩子改了几个名字,mixin 还能不能继续用,模态框那个被父级 overflow 裁掉的老问题在 Vue 3 里有没有官方解法。这篇是我把 Vue 3 基础这块重新过一遍的笔记,按「建项目 → 父子传值 → 生命周期 → 逻辑复用 → Teleport」这条线走完,每个点都说清楚它解决什么、什么时候用、边界在哪。读完你至少能独立起一个 Vue 3 项目,并且知道哪些 Vue 2 的肌肉记忆需要改掉。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • Vue CLI 和 Vite 两套脚手架怎么选,各自的命令和适用场景
  • 父组件给子组件传值、props 验证的七种写法、单向数据流为什么不能破
  • $refs 和 $parent 这两条「越级」通道的正确用法和代价
  • Vue 2 到 Vue 3 生命周期钩子的完整对照表,以及 setup 为什么吃掉了前两个钩子
  • keep-alive 缓存组件状态、this.$nextTick 拿渲染后的 DOM
  • mixin 的三条合并规则,以及它为什么在 Vue 3 里被劝退
  • 用 Teleport 实现一个模态对话框组件,顺手解决层级和裁切问题

# 一、先把项目跑起来 Vue CLI 和 Vite 怎么选

动手之前电脑上得先有 Node.js,装稳定版就行。Vue 官方那会儿同时提供了两个脚手架,Vue CLI 和 Vite,两者不是替代关系,是两个时代。

Vue CLI 基于 webpack,生态成熟、插件多、配置项全,老项目和需要复杂构建定制的项目还在用它。安装是全局装一个命令行工具:

yarn global add @vue/cli
# OR
npm install -g @vue/cli
# OR
cnpm install -g @vue/cli
@前端进阶之旅: 代码已经复制到剪贴板

如果电脑上面没有安装过 cnpm,可以通过下面命令安装:

npm install -g cnpm --registry=https://registry.npm.taobao.org
@前端进阶之旅: 代码已经复制到剪贴板

这条命令在 2021 年是标准写法,现在淘宝源域名已经迁移到 registry.npmmirror.com,老域名的证书早就过期了,照抄会直接报错。现在更常见的做法是不装 cnpm,直接给 npm 配镜像源,或者用 pnpm 自带的 registry 配置。

如果电脑上面没有安装过 yarn,可以通过下面命令安装:

npm i -g yarn
@前端进阶之旅: 代码已经复制到剪贴板

装完就能创建项目了,这一步会有一个交互式的选项列表,问你要不要 Router、Vuex、TypeScript、ESLint:

vue create hello-vue3

yarn serve
@前端进阶之旅: 代码已经复制到剪贴板

另一条路是 Vite。它开发阶段不打包,直接用浏览器原生的 ES Module 按需编译,改一行代码热更新几乎是瞬时的,项目越大差距越明显。用 npm 创建:

npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
@前端进阶之旅: 代码已经复制到剪贴板

用 yarn 创建:

yarn create vite-app <project-name>
cd <project-name>
yarn
yarn dev
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意,vite-app 这个包名是 Vite 1.x 时期的产物,后来官方模板改名了,现在通行的是 npm create vite@latest。如果你要的是一个开箱带 Router 和 Pinia 的 Vue 项目,Vue 官方现在推荐的入口是 npm create vue@latest,它内部就是 Vite 驱动的。Vue CLI 目前处于维护状态,新项目基本不用再考虑它了,具体版本策略以官方文档为准。

先说结论,除非你要接手的是一个已经跑在 webpack 上的老仓库,否则新项目直接上 Vite。

# 目录结构

创建完的项目长这样,src 下面是源码,public 放不参与打包的静态资源,入口在 main.js:

Vue3 项目创建完成后的目录结构,src 下按 components / assets 划分

# 开发工具以及插件配置

编辑器这块我用 VS Code。这里有个特别容易踩的点,Vue 2 时代大家装的是 Vetur,但 Vetur 不认 Vue 3 的类型推导,装了反而会满屏飘红。Vue 3 项目要装的是 Volar,它后来改名叫 Vue - Official,两个插件同时开启会互相打架,装新的记得把旧的禁用掉。

VS Code 中 Vue 开发相关插件的配置界面

# 二、父组件给子组件传值 props验证与单向数据流

# 父子组件是怎么串起来的

组件树的关系图先放在这,父组件通过标签属性把数据往下传,子组件通过事件把消息往上抛,这是最基础的一组通道:

父组件与子组件的层级关系示意图,数据向下流事件向上抛

# 父组件调用子组件的时候传值

父组件这边,把要传的数据写在 data 里,在标签上用 :title 绑上去:

<template>
    <v-header :title="title"></v-header>
</template>

<script>
import Header from './Header'
export default {
    data() {
        return {
            title: "首页组件title"
        }
    },  
    components: {
        "v-header": Header
    }
}
</script>
@前端进阶之旅: 代码已经复制到剪贴板

# 子组件接收父组件传值

子组件用 props 声明自己要接收哪些字段,声明完就能像自己的数据一样在模板里用:

<template>
    <header>{{title}}</header>
</template>

<script>
export default {
    props: ["title"]
}
</script>
@前端进阶之旅: 代码已经复制到剪贴板

数组形式的 props 写起来快,但它什么都不校验。传错类型、少传必填项,全都要等到页面白屏了才发现。

# Props 验证

我们可以为组件的 prop 指定验证要求。如果有一个要求没被满足,Vue 会在浏览器控制台里警告你。这在开发一个会被别人用到的组件时尤其有帮助,相当于给组件写了一份可执行的接口文档。

下面这段把常见的七种写法都列全了,从最简单的类型检查到自定义验证函数:

props: {
  // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
  propA: Number,
  // 多个可能的类型
  propB: [String, Number],
  // 必填的字符串
  propC: {
    type: String,
    required: true
  },
  // 带有默认值的数字
  propD: {
    type: Number,
    default: 100
  },
  // 带有默认值的对象
  propE: {
    type: Object,
    // 对象或数组默认值必须从一个工厂函数获取
    default: function() {
      return { message: 'hello' }
    }
  },
  // 自定义验证函数
  propF: {
    validator: function(value) {
      // 这个值必须匹配下列字符串中的一个
      return ['success', 'warning', 'danger'].indexOf(value) !== -1
    }
  },
  // 具有默认值的函数
  propG: {
    type: Function,
    // 和对象、数组的默认值不同,这里的函数不是工厂函数,它本身就是默认值
    default: function() {
      return 'Default function'
    }
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

propE 和 propG 这两条最容易记混。对象和数组的 default 必须写成工厂函数,因为引用类型如果直接写字面量,所有组件实例会共享同一个对象,一个改了大家全跟着变。而 Function 类型的 default 就是那个函数本身,不需要再包一层。

# 单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样能防止子组件意外改掉父组件的状态,避免数据流向变得难以追踪。

另外,每次父级组件发生变更时,子组件中所有的 prop 都会刷新为最新的值。所以你不应该在子组件内部直接改 prop,改了 Vue 会在控制台发警告,而且下一次父组件更新时你的修改会被覆盖掉。

那真的需要在子组件里改怎么办?两种做法。一种是把 prop 当初始值,在 data 里复制一份自己维护;另一种是用 computed 基于 prop 派生出新值。要是父组件也需要知道这个变化,那就别绕了,老老实实 $emit 一个事件让父组件自己改。

# 父组件主动获取子组件的数据和执行子组件方法

props 和事件是常规通道,但有时候你就是想在父组件里直接调子组件的一个方法,比如让表单子组件执行一次校验。这时候用 ref。

调用子组件的时候定义一个 ref:

 <v-header ref="header"></v-header>
@前端进阶之旅: 代码已经复制到剪贴板

父组件主动获取子组件数据:

this.$refs.header.属性	
@前端进阶之旅: 代码已经复制到剪贴板

父组件主动执行子组件方法:

this.$refs.header.方法		
@前端进阶之旅: 代码已经复制到剪贴板

# 子组件主动获取父组件的数据和执行父组件方法

反过来,子组件也能通过 $parent 摸到父组件实例。

子组件主动获取父组件的数据:

 this.$parent.数据	
@前端进阶之旅: 代码已经复制到剪贴板

子组件主动执行父组件方法:

this.$parent.方法
@前端进阶之旅: 代码已经复制到剪贴板

这两条通道我自己的感受是能少用就少用。$refs 还算可控,父组件本来就知道自己挂了哪些子组件;但 $parent 相当于让子组件对它的使用环境做了假设,一旦这个组件被挪到别的地方复用,this.$parent.xxx 立刻就是 undefined。真要跨层级通信,后面 provide/inject 或者状态管理才是正路,这块我在 Vue组件化实践详解 里把九种方式的取舍都排了一遍。

还有个 Vue 3 的变化得提一句。用 script setup 写的组件默认是封闭的,父组件通过 ref 拿到的实例上什么都没有,你得在子组件里用 defineExpose 显式暴露想给外面用的属性和方法。这个设计我一开始觉得麻烦,用久了反而觉得舒服,组件的对外接口一眼就能看全。

# 三、生命周期函数 从Vue2到Vue3的钩子对照

# 生命周期函数

先看这张图,一个组件从创建到销毁会经过创建、挂载、更新、卸载四个阶段,每个阶段前后各埋一个钩子:

Vue 组件生命周期流程图,涵盖创建、挂载、更新、销毁四个阶段的钩子调用顺序

Vue 3 里这些钩子在组合式 API 中都换了名字,规律是加 on 前缀改成驼峰:

Vue2 Vue3
beforeCreate ❌setup(替代)
created ❌setup(替代)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured
- 🎉onRenderTracked
- 🎉onRenderTriggered

表里有三处值得单独拎出来说。

fe
  • 一、先把项目跑起来 Vue CLI 和 Vite 怎么选
    • 目录结构
    • 开发工具以及插件配置
  • 二、父组件给子组件传值 props验证与单向数据流
    • 父子组件是怎么串起来的
    • 父组件调用子组件的时候传值
    • 子组件接收父组件传值
    • Props 验证
    • 单向数据流
    • 父组件主动获取子组件的数据和执行子组件方法
    • 子组件主动获取父组件的数据和执行父组件方法
  • 三、生命周期函数 从Vue2到Vue3的钩子对照
    • 生命周期函数
  • 四、动态组件keep-alive与nextTick
    • 动态组件 keep-alive
    • this.$nextTick
  • 五、Mixin实现组件功能的复用
    • 新建 mixin/base.js
    • 使用 mixin
    • 关于 Mixin 的选项合并
    • 全局配置 Mixin
  • 六、使用Teleport自定义一个模态对话框组件
    • Teleport 解决的是什么
    • 用 Teleport 实现一个模态对话框组件
  • 总结
  • 参考

← Vue3之Composition API详解 从setup到组合式函数异步编程入门之RxJs 从宝石图到常用操作符 →