loading

超极速的Vue3上手指北🔥

笔者之前是 Vue2+React 开发者,因项目需要直接上手 Vue3,所以快速学习一下,中间会对比一些和 React 相关的区别

# 一、前言

笔者之前是 Vue2+React 开发者,因项目需要直接上手 Vue3,所以快速学习一下,中间会对比一些和 React 相关的区别。阅读前提:已经上手了Vue2的开发,本文主要聊的问题:

  • Vue3 的全新特性
  • Vue2 和 Vue3 的一些区别
  • Vue2 开发者如何快速上手 Vue3
  • Vue3 和 React 的简单比对
  • 使用 Vue3 编写组件库
  • 后续关于 Vue3 的一些学习、源码、面试题等

【俊劫的学习基地】 更多前端学习资源等你来拿 (opens new window)(有摸鱼群 🐳),

点击关注公众号 (opens new window),一起学习打卡!兄弟姐妹帮小弟点个赞

强烈推荐小满 ZS 的 B 站教程:Vue3 + vite + Ts + pinia + 实战 + 源码 +全栈 (opens new window)

# 二、Vue2 vs Vue3

# 2.1 简单点说

  • Vue2 只支持单节点,Vue3 template 支持多节点,类似 react fragments
  • 变化基本都在 script 中(Option api -> Composition api)不会再看见满屏的 this 了!!!
  • style 支持v-bind
  • Proxy 代替 defineProperty
    • defineProperty 无法实现对数组对象的深层监听,Proxy是浏览器最新 api,功能更加强大。
    • 不再支持 IE,Vue2 想享受 Vue3 带来的部分更新,可考虑升级Vue2.7版本
  • TypeScript 的支持
    • Vue2 采用的是 Facebook 的Flow,没法完美支持TypeScript(所以项目初期技术选型很重要)
    • Vue3 TypeScript完全重写,提供和 React 一样的 TS 支持
  • 全新生态
    • 基本还是 vue 周边伴随 Vue3 升级那一套,但是状态管理推荐,由原来的 Vuex 变为Pina
    • 全新的vite支持,包括vitest等,官方提供周边工具更多了
  • 其它优化
    • 性能更好,体积更小就不用说了
    • 事件监听缓存,类似@click 绑定的函数,无需多次创建,放在了 cacheHandler 缓存中
    • SSR:Vue 3.0 会将静态标签直接转化为文本,相比 React 先将 JSX 转化为虚拟 DOM,再将虚拟 DOM 转化为 HTML,这一点 Vue3 的速度要快很多
    • Use Hooks 放弃过去的 mixins,使用 Hooks 解决过去 mixins 的一些缺点

# 2.2 源码

了解的不多,后续再补充

# diff 算法的优化

  • 不再和 vue2 一样,完全对比两棵虚拟 DOM 树,Vue3 采用动静结合的方法,优化 diff 性能
  • 通过编译阶段对静态模板进行分析,编译生成 Block tree。更新性能由 模版整体大小相关 =》与动态内容的数量相关,这是一个非常大的性能突破。将代码提升到渲染函数之外,这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。

# 源码管理

  • vue2 poly-repo
    • vue2.x 的源码托管在 src 目录中,然后依据功能拆分出了 complier(模板编译的相关代码),core(与平台无关的通用运行时代码),platforms(平台专有代码),server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码) 等目录
  • vue3 mono-repo
    • package 可以独立于vue.js 去使用,这样例如用户想要使用 vue3.0 的响应式,可以单独依赖reactive,而不必依赖整个 vue.js,减少引用包的体积,而 vue2.x 却做不到这一点。
  • 源码结构对比 image.png

# 三、全新的 API

什么是组合式 API?- Vue 官方 (opens new window)

  • 解决了过去组件过长时,optionsApi 带来的难以维护的问题
  • 逻辑可以整块复用
  • 所有 API 都是 import 引入的,对 Tree- shaking 很友好,没用到功能,打包的时候会被清理掉,减小包的大小

# 3.1 setup

  • 新的  setup  选项在组件被创建之前执行,一旦  props  被解析完成,它就将被作为组合式 API 的入口。
  • 可以当做 Vue2 的 beforeCreate 和 create 生命周期用
  • 可直接写await语法
  • SFC 单文件组件中直接使用<script lang="ts" setup>即可,或者也可以结合export default使用
<script lang="ts" setup>
  const result = await Http.get('/getUserInfo')
</script>;
// or
export default {
  setup(props, context) {
    // Attribute (非响应式对象,等同于 $attrs)
    console.log(context.attrs);
    // 插槽 (非响应式对象,等同于 $slots)
    console.log(context.slots);
    // 触发事件 (方法,等同于 $emit)
    console.log(context.emit);
    // 暴露公共 property (函数)
    console.log(context.expose);
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.2 ref

  • ref 用来创建基础类型的响应式数据
  • template 中默认调用 value 显示数据,script 中需要使用.value 调用
  • 和 react ref 差不多,react 是.current 获取值,vue3 是.value。
  • Ref 的本质是通过 Reactive 创建的,Ref(10)=>Reactive({value:10})

有一定的心智负担,尤大也明确说了不会支持 script 中直接访问。究其原因,还是基础类型无法拦截它的变化,当然也有大哥提出用 new String('foo')类似的语法对基础类型进行包装。个人感觉直接拿已支持的 reactive 来搞也不错

# 相关 api

  • Ref ts 定义 import { type Ref } from 'vue';
  • isRef 判断是否为 ref 对象。一般是 ref,toRef,toRefs 创建的变量
  • toRefsreactive对象解构为单个响应式对象
  • shallowRef 创建一个跟踪自身  .value  变化的 ref,但不会使其值也变成响应式的,简单理解为创建一个和 ref 相同结构的非响应式变量
  • triggerRef 强制更新页面 DOM。即使创建的 ref 没有变,想更新 dom 可以用
  • customRef 提供类似于 computed 的 get 和 set,可自定义 ref 行为

# 3.3 reactive

  • reactive 用来创建引用类型的响应式数据
  • reactive 的本质是将每一层的数据都解析成proxy对象
  • reactive 的响应式默认都是递归的,改变某一层的值都会递归的调用一遍,重新渲染 dom。
  • 直接解构,响应性会丢失,需要用toRefs包裹。引用类型直接改变引用地址也会导致响应式丢失

# 相关 api

  • readonly 将 reactive 的值更改为只读
  • shallowReactive 只能对浅层的数据响应 如果是深层的数据只会改变值 不会改变视图
import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Vue Team',
  year: '2020',
  title: 'Vue 3 Guide',
  description: 'You are reading this book right now ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.4、生命周期

区别不大,把 setup 当 created 用,其它就当改了个名

<script setup lang="ts">
import { onMounted } from 'vue';

const getUserInfo = () => {
  console.log('获取用户信息');
};
onMounted(getUserInfo);
</script>
1
2
3
4
5
6
7
8

image.png

# 3.5 watch & watchEffect

# watch

  • 功能和 vue2 一致
  • watch(监听参数,变化回调,配置参数)
  • 注意监听对象的单个属性:watch(() => articleInfo.author, (newVal) => {}),第一个参数为箭头函数返回要监听的目标属性
import { ref, reactive, watch } from 'vue'

const counter1 = ref(0)
const counter2 = ref(0)
// 监听多个
watch([counter1, counter2], (newValue, oldValue) => {
  console.log('The new counter1 value is: ' + counter1.value)
  console.log('The new counter2 value is: ' + counter2.value)
})

const obj = reactive({
  name: 'JJ',
  age: 18
})
// 深度监听对象
watch(obj, (newValue, oldValue) => {
  console.log('The new obj value is: ' + obj.value)
}, {
   deep: true,
   immediate: true
})
// watch监听单个属性
watch(() => obj.name, (newValue, oldValue) => {
  console.log('The new obj value is: ' + obj.value)
}, {
   deep: true,
   immediate: true
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# watchEffect

  • 类似 React useEffect,但是不需要写依赖项,只要我们回调中引用了响应式的属性
  • 和 watch 的区别:
    • 同一个功能的两种不同形态,底层的实现是一样的
    • watch 可以获取到新值与旧值(更新前的值),而  watchEffect  是拿不到的。
    • watch - 显式指定依赖源,watchEffect - 自动收集依赖源
    • watchEffect 在组件初始化的时候就会执行一次用以收集依赖,watch 指定了依赖,所以不需要。
    • 可以理解为watchEffect 就是配置了{ immediate: true } 的watch
  • 使用场景:antfu小哥:推荐在大部分时候用 watch 显式的指定依赖以避免不必要的重复触发,也避免在后续代码修改或重构时不小心引入新的依赖。watchEffect 适用于一些逻辑相对简单,依赖源和逻辑强相关的场景(或者懒惰的场景 )。
const stopWatch = watchEffect(
  (oninvalidate): void => {
    oninvalidate(() => {
      console.log("前置校验函数");
    });
    // 引用了响应式的属性 count
    console.log("watchEffect count变化", count.value);
  },
  {
    // 副作用刷新时机 flush 一般使用post
    // 可选:pre(组件更新前执行)/sync(强制效果始终同步触发)/post(组件更新后执行)
    flush: "post",
    // 开发调试
    onTrigger() {
      console.log("开发调试");
    },
  }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 3.6 computed

  • 更加灵活,可以在定义响应式变量时声明
  • 作用和 vue2 无差异
import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
// get set写法
const plusOne = computed({
  get: () => counter.value + 1,
  set: (val) => {
    counter.value = val - 1
  },
})

plusOne.value = 1
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 四、组件

# 4.1 异步组件

  • 通过进行引入 defineAsyncComponent
  • 可配合 Suspense 进行更多操作,可用于 loading 和骨架屏相关,和 react Suspense 基本一致。不过 react Suspense 基本一致这个属性都不太好用,vue 的不清楚实际场景咋样
// template
<Suspense>
  <template #default>
    <AsyncComponent></AsyncComponent>
  </template>

  <template #fallback>
    <div>loading...</div>
  </template>
</Suspense>

// script
const AsyncComponent = defineAsyncComponent(() => import('./asyncComponent.vue'))
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.2 Teleport 传送组件

Teleport 是一种能够将我们的模板渲染至指定 DOM 节点,不受父级 style、v-show 等属性影响,但 data、prop 数据依旧能够共用的技术;类似于 React 的 Portal。之前写 react 是不怎么用这个属性,vue3 这个估计也没多大用。

主要解决的问题 因为 Teleport 节点挂载在其他指定的 DOM 节点下,完全不受父级 style 样式影响

to 属性 插入指定元素位置,body,html,自定义 className 等等

<Teleport to="body">
  <Loading></Loading>
</Teleport>
1
2
3

# 4.3 keep-alive 缓存组件

  • 作用和 vue2 还是一样的,生命周期名称变了
  • 初次进入时:onMounted> onActivated
  • 退出后触发 deactivated
  • 再次进入:只会触发 onActivated

# 4.4 组件通信

# defineXxxx

defineXxxx 无需 import 即可直接使用

  • defineProps 代替过去的 props
  • defineEmits 代替过去的$emit
  • defineOptions 自定义一些组件属性,比如组件名称(需要插件支持)
  • defineComponent 用于 render 函数、TSX、IDE 提醒等
  • defineExpose 子组件声明的数据,暴露给父组件直接用

# provide/inject

和 vue2 一致

# vuex & pina

两者用法,除了 pina 没有 Mutations,差别不大。但是官方推荐的东西,自然有它的各种优点

  • Vuex: State、Gettes、Mutations(同步)、Actions(异步)
  • Pinia: State、Gettes、Actions(同步异步都支持)
  • Vuex4 用于 Vue3 ,Vuex3 用于 Vue2
  • Pinia2.x 即支持 Vue2 也支持 Vue3

# 五、TS 支持

  • 可以让写 react 的兄弟,快速上手写 vue3
  • react 中 {{}} => {}
  • 兼容的指令:v-model,v-if,v-show
import { ref } from "vue";
let v = ref < string > "";
const renderDom = () => {
  return (
    <>
      <input v-model={v.value} type="text" />
      <div>{v.value}</div>
    </>
  );
};
export default renderDom;
1
2
3
4
5
6
7
8
9
10
11

# 六、插件

# 6.1 开源插件

# unplugin-auto-import/vite

无需导入 xxx,import { reactive,ref } from "vue";,只需要用即可

# unplugin-vue-define-options

自定义组件名称,需要引入插件unplugin-vue-define-options,并在 vite 中配置

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import DefineOptions from "unplugin-vue-define-options/vite";

export default defineConfig({
  plugins: [vue(), DefineOptions()],
});
1
2
3
4
5
6
7

不使用插件,也可以通过多写一个 script 标签来单独写 options

<script lang="ts">
    export default {
        name: "TButton",
    };
</script>
<script lang="ts" setup>
  defineOptions({
    name: 'TButton',
  });
</script>
1
2
3
4
5
6
7
8
9
10

# 6.2 vscode 插件

# volar vscode

  • vetur 只支持 vue2,volar 只支持 vue3,两者冲突。
  • 建议禁用 vetur,格式化代码使用 prettier,本地使用 volar 做代码高亮。
  • 或者通过项目配置,指定相关插件配置 image.png

# 七、指令

# 7.1 v-model

  • 底层语法糖时间改变,之前 vue2 是 update:input,vue3 是 update:modelValue
  • 支持多个 v-model
  • 支持自定义修饰符
  • 弃用.sync 等

# 7.2 自定义指令

# 生命周期(和 vue3 一致)

  • created 元素初始化的时候
  • beforeMount 指令绑定到元素后调用 只调用一次
  • mounted 元素插入父级 dom 调用
  • beforeUpdate 元素被更新之前调用
  • update 这个周期方法被移除 改用 updated
  • beforeUnmount 在元素被移除前调用
  • unmounted 指令被移除后调用 只调用一次

# 自定义拖拽指令 v-move

  • 比如这个 v-move 封装自定义拖拽指令
import { Directive } from "vue";
const vMove: Directive = {
  mounted(el: HTMLElement) {
    let moveEl = el.firstElementChild as HTMLElement;
    const mouseDown = (e: MouseEvent) => {
      //鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器最左边的距离-物体左边框相对于浏览器最左边的距离
      console.log(e.clientX, e.clientY, "-----起始", el.offsetLeft);
      let X = e.clientX - el.offsetLeft;
      let Y = e.clientY - el.offsetTop;
      const move = (e: MouseEvent) => {
        el.style.left = e.clientX - X + "px";
        el.style.top = e.clientY - Y + "px";
        console.log(e.clientX, e.clientY, "---改变");
      };
      document.addEventListener("mousemove", move);
      document.addEventListener("mouseup", () => {
        document.removeEventListener("mousemove", move);
      });
    };
    moveEl.addEventListener("mousedown", mouseDown);
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 八、Hook

用了 react hook 的人都知道很香,vue3 支持这个相当不错,能解决很多业务场景的封装

# 8.1 自定义 Hook

可以当做 mixins 写

// useWindowResize
import { onMounted, onUnmounted, ref } from "vue";

function useWindowResize() {
  const width = ref(0);
  const height = ref(0);
  function onResize() {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  }
  onMounted(() => {
    window.addEventListener("resize", onResize);
    onResize();
  });
  onUnmounted(() => {
    window.removeEventListener("resize", onResize);
  });

  return {
    width,
    height,
  };
}

export default useWindowResize;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 8.2 hook 库

# 8.3 react vs vue3

# 九、结语

  • Vue3 的依赖追踪是全自动的,不需要担心传了错误的依赖数组给 useEffect/useMemo/useCallback 从而导致回调中- 使用了过期的值
  • Vue3 Hook 也没 React Hook 那么多限制,后续用用看怎么样
  • 个人比较喜欢 SFC 语法,html、js、css 分离开

笔者 vue3 也刚用不久,如有错误,还请兄弟们指出

本文所有 demo 都在该仓库中JJ-UI 一款 Vue3 组件库 (opens new window),参考大佬文章刚刚搭建好,后续会基于这个架子开发自己的 vue3 组件库

image.png

# 十、参考文章

# 十一、往期推荐

# 十二、求赞、求三连

image.png

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿 (opens new window)

最近更新时间: 2022/08/09 10:33:08
最近更新
01
2023/02/08 00:00:00
02
2023/01/03 00:00:00
03
2022/12/21 00:00:00