Vue进阶之 Vue3.X
3.0介绍
源码组织方式的变化
- 源码采用 TypeScript 重写,做类型检查;
- 使用 Monorepo 管理项目结构,将每个独立的模块都提起到每个包儿中,在packages;
Monorepo 模式,使用同一个项目管理多个的包儿,将不同功能的代码,放入packages中管理,这样可以使得每个功能模块划分明确,依赖也很明确,并且每个功能模块儿都可以单独测试、发布和使用。
Composition API (组合式 API)
可参考官方文档
https://github.com/vuejs/rfcs
https://v3.cn.vuejs.org/api/composition-api.html#setup
Composition API 主要是将,一个组件的所用功能代码都集合在一起,不用分散在vue的各个方法中(data、methods、props等)
Options API 开发复杂组件,同一个功能逻辑的代码就会被拆分到不同选项中。
一个 Composition API 的例子,将逻辑代码都合到了一起。
最后可用这张图来区分 Composition API 和 Options API
- Composition API 提供了一组函数式的API,更方便我们组织组件的逻辑;
- Composition API 可以更合理的组织组件的结构,方便将组件提取出来,更好的重用。
性能的提升
响应式系统升级
- Vue.js 2.x 中响应式系统的核心 defineProperty, 需要初始化时遍历所有属性添加响应式;
- Vue.js 3.0 中使用 Proxy 对象重写响应式系统,代理对象可拦截原对象的访问、赋值、删除等操作,不需要初始化时遍历所有属性。并且有多层属性嵌套时,只有访问某个属性时才会去递归处理下一级的属性。
- 可以监听动态新增的属性
- 可以监听删除的属性
- 可以监听数组的索引和 length 属性
编译优化
- Vue.js 2. X 中只标记了静态根节点,优化 diff 的过程
- Vue.js 3.0
- 标记和提升所有的静态根节点,diff 的时候只需要对比动态节点内容
- Fragments 片段特性模板不需要创建唯一的根节点了(vscode中升级 vetur 插件)
- 静态提升节点的提升,diff时会直接跳过静态节点,节省时间。
- Patch flag,标记动态节点的类型,做更优化的对比。例如 如果节点中文本是动态内容,那边diff时,就只会对比文本内容的变化,属性也一样。
- 缓存事件处理函数,将事件函数缓存为新的匿名函数,每次调用都会去取最新的函数。但是匿名函数永远不会变,就不用diff对比了,提升了diff性能。
优化打包体积
- Vue.js 3.0 中移除了一些不常用的 API 如 inline-template、filter等;
- 更好的 Tree-shaking 内置的组件和模块都是按需引入的,包括一些API
Vite
Vite(快) Vue3.0的新脚手架
- Vite 在开发模式下不需要打包可以直接运行(它使用浏览器原生支持的 ESmodule 加载模块)
- Vue-CLI 开发模式下必须对项目打包才可以运行
Vite在启动后,会搭建一个本地的服务,当遇到浏览器不能识别的模块如果.vue模块,就在服务里编译文件为浏览器可识别的文件。
Vite特点
- 快速冷启动
- 按需编译
- 模块热更新
Vite 在生产环境下使用 Rollup 打包,基于 ES Module 的方式打包,打包体积较小
而Vue-CLI 使用 Webpack 打包
Composition API 使用
setup
一个组件选项,在创建组件之前执行(在beforeCreate和created之间执行的),一旦 props 被解析,并作为组合式 API 的入口点
<!-- MyBook.vue -->
<template>
<div>{{ readersNumber }} {{ book.title }}</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const readersNumber = ref(0)
// reactive 创建响应式对象的函数
const book = reactive({ title: 'Vue 3 Guide' })
// expose to template
return {
readersNumber,
book
}
}
}
</script>
声明周期钩子
setup中,Vue的生命周期都有钩子执行。映射关系
beforeCreate-> use setup()created-> use setup()- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeUnmount -> onBeforeUnmount
- unmounted -> onUnmounted
- errorCaptured -> onErrorCaptured
- renderTracked -> onRenderTracked (首次调用也会触发)
- renderTriggered -> onRenderTriggered (首次调用不会触发)
响应式API
1、reactive 将一个对象代理为响应式对象,返回对象的响应式副本(代理对象);
2、ref 将一个基本类型的数据代理为响应式对象,返回一个响应式对象(获取值需要调动.value属性,模板中使用时可以省略.value);
3、toRefs 将一个代理对象内的每个属性都代理为响应式对象,内部处理每个属性类似于ref。它必须接收一个响应式代理对象;当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行分解/扩散:
vue3.0新增一个 watchEffect 在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
- 接收一个函数作为参数,监听函数内响应式数据的变化,当数据变化执行这个函数。
Vue.3.0 响应式回顾
- Proxy 对象实现属性监听
- 多层属性嵌套,在访问属性过程中处理下一级属性 • 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和 length 属性
- 可以作为单独的模块使用
模式实现,Vue3.0的响应式原理 Proxy
注意,Vue3.0中,代理对象操作,用使用 Reflect (反射对象来操作),它与Object的一些操作相同,但是建议可以使用 Reflect 的都使用Reflect;有以下几点好处:
- Reflect 优化了语言内部的方法;
- Reflect 的一些操作会返回 Boolean 值,提示成功与失败,而Object的没有,例如Object.definePropery(obj,name,desc)无法定义属性时报错,而Reflect.definedProperty(obj,name,desc)则会返回false。
- Object的一些行为,如:name in obj和delete obj[name],可以让Reflect.has(name)和Reflect.deleteProperty(obj,name)以函数的形式替代。
- Reflect方法和Proxy方法一一对应。主要就是为了实现本体和代理的接口一致性,方便用户通过代理操作本体。
实现代码一些核心方法 reactive、ref、toRefs、computed、effect、track、trigger
const isObject = val => val !== null && typeof val === 'object';
const convert = res => isObject(res) ? reactive(res) : res;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (target, key) => hasOwnProperty.call(target, key);
export function reactive(target) {
if(!isObject(target)) return target;
const handler = {
get(target, key, receiver) { //receiver为生成的代理对象 可以是getter调用时的this
// 收集依赖
track(target, key);
console.log('get', key)
const res = Reflect.get(target, key, receiver);
// 如果这个结果是对象,需要继续给这个对象添加响应式;
return convert(res);
},
set(target, key, val, receiver) { //receiver为生成的代理对象
let oldval = Reflect.get(target, key, receiver);
let boo = true; //没有变化返回true,默认返回true
if(oldval !== val) {
boo = Reflect.set(target, key, val, receiver);
// 派发依赖
trigger(target, key)
}
return boo
},
deleteProperty(target, key) { //删除属性
let has = hasOwn(target, key); //判断是否有这个属性
let boo = Reflect.deleteProperty(target, key);
if(has && boo){
// 派发依赖
trigger(target, key)
}
return boo
}
};
return new Proxy(target, handler)
}
let activeEffect = null; //全局唯一的,每次执行时都只有一个正在执行,此时收集依赖的对象,就会收集activeEffect正在执行的函数。
export function effect(callback) {
activeEffect = callback;
callback(); //访问对象属性,触发收集依赖;
activeEffect = null; //执行完立即置位空;
}
let targetMap = new WeakMap()
// targetMap 结构
/*
targetMap = {
target(代理对象) : Map {
key(代理对象属性) : Set [
effect中的回调函数,
effect中的回调函数,
effect中的回调函数
]
}
}
*/
export function track(target, key) { //收集依赖
if(!activeEffect) return;
let depsMap = targetMap.get(target);
if(!depsMap){
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if(!dep){
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
export function trigger(target, key) { //派发更新
const depsMap = targetMap.get(target);
if(!depsMap) return;
const dep = depsMap.get(key);
if(dep) {
dep.forEach(effect => {
effect()
});
}
}
/* reactive vs ref
- ref 可以把基本数据类型数据,转成响应式对象
- ref 返回的对象,重新赋值成对象也是响应式的
- reactive 返回的对象,重新赋值丢失响应式
- reactive 返回的对象不可以解构
*/
export function ref (raw) { //ref函数
// 判断是否是对象,并且是ref创建的代理对象直接返回
if(isObject(raw) && raw.__v_isRef) return;
let value = convert(raw); //如果是对象添加响应式
const r = {
__v_isRef: true,
get value() {
track(r, 'value') //收集依赖
return value
},
set value(newVal) {
if(newVal !== value){
raw = newVal //赋给原值
value = convert(raw) //赋给代理值
trigger(r, 'value')
}
}
}
return r
}
export function toRefs (proxy) {
// 判断是否是reactive创建的对象代理器,不是返回
// 省略
const ret = proxy instanceof Array ? new Array(proxy.length) : {};
for(const key in proxy){
ret[key] = toProxyRef(proxy, key)
}
return ret
}
function toProxyRef(proxy, key) {
return {
__v_isRef: true,
get value () {
return proxy[key] // 这里直接调取,会在proxy的代理对象里在调用get,收集依赖
},
set value (newValue) {
proxy[key] = newValue // 同上这里直接设置
}
}
}
export function computed(getter) { // 计算属性,执行effect
const res = ref();
effect(() => {res.value = getter()})
return res;
}
Vite
- Vite 是一个面向现代浏览器的一个更轻、更快的 Web 应用开发 工具
- 它基于 ECMAScript 标准原生模块系统(ES Modules)实现
主要解决,webpack 在开发阶段webpack devServer 冷启动时间过长和 webpack HMR 热更新反应慢的问题。
Vite 特性
- 快速冷启动 不需要整体打包儿编译,web服务器可以立即启动
- 模块热更新 实时的模块热更新
- 按需编译 只编译需要的模块,热更新也是按需编译热更新快
- 开箱即用 减少各种配置