本文共 13614 字,大约阅读时间需要 45 分钟。
Performance
:性能
比Vue 2.x快
1.2~2倍Tree shaking support
:按需编译
,体积
比Vue2.x更小
Composition API
: 组合API(类似React Hooks)
Better TypeScript support
:更好的 Ts 支持
Custom Renderer API
:暴露了自定义渲染API
Fragment, Teleport(Protal), Suspense
:更先进的组件
diff 算法优化
hoistStatic 静态提升
cacheHandlers 事件侦听器缓存
ssr 渲染
全量的对比
静态标记(PatchFlag)
与上次虚拟节点进行对比时候
,只对比带有patch flag的节点
通过 flag 的信息得知当前节点要对比的具体内容
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "张三"), _createVNode("p", null, "张三"), _createVNode("p", null, "张三"), _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) ]))}张三
张三
张三
{
{ msg}}}
PatchFlags
export const enum PatchFlags { TEXT = 1,// 动态文本节点 CLASS = 1 << 1, // 2 // 动态 class STYLE = 1 << 2, // 4 // 动态 style PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式 FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。 HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点 STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较 DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot HOISTED = -1, // 静态节点 // 指示在 diff 过程应该要退出优化模式 BAIL = -2}
无论元素是否参与更新, 每次都会重新创建, 然后再渲染
对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
================================================================================静态提升之前:export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("p", null, "张三"), _createVNode("p", null, "张三"), _createVNode("p", null, "张三"), _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) ]))}================================================================================静态提升之后:const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "张三", -1 /* HOISTED */)export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _hoisted_1, _hoisted_2, _hoisted_3, _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */) ]))}张三
张三
张三
{
{ msg}}}
onClick
会被视为动态绑定
, 所以每次都会去追踪它的变化
因为是同一个函数
,所以没有追踪变化
, 直接缓存起来复用
即可事件监听缓存================================================================================开启事件监听缓存之前:export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"]) ]))}================================================================================开启事件监听缓存之后:export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args))) }, "按钮") ]))}
Vue-CLI
Webpack
Vite
npm install -g @vue/clivue create projectNamecd projectNamevue add vue-nextnpm run serve
git clone https://github.com/vuejs/vue-next-webpack-preview.git projectNamecd projectNamenpm installnpm run dev
Vite
是 Vue 作者开发的一款意图取代 webpack 的工具
ES6
的 import
会发送请求去加载文件
的特性拦截请求
, 做一些预编译
, 省去 webpack 冗长的打包时间
npm install -g create-vite-app
create-vite-app projectName
cd projectName
npm install
npm run dev
数据与逻辑处理分开
耗性能,维护不方便
- { {stu.name}} -- { {stu.age}}
{
{count}}
- { {stu.name}} - { {stu.age}}
App.vue
- { {stu.name}} - { {stu.age}}
rem.js
import { reactive} from 'vue';function useRemoveStudent() { let state = reactive({ stus:[ { id:1, name:'zs', age:10}, { id:2, name:'ls', age:20}, { id:3, name:'ww', age:30}, ] }); function remStu(index) { state.stus = state.stus.filter((stu, idx) => idx !== index); } return { state, remStu};}export default useRemoveStudent;
add.js
import { reactive} from 'vue';function useAddStudent(state) { let state2 = reactive({ stu:{ id:'', name:'', age:'' } }); function addStu(e) { e.preventDefault(); const stu = Object.assign({ }, state2.stu); state.stus.push(stu); state2.stu.id = ''; state2.stu.name = ''; state2.stu.age = ''; } return { state2, addStu}}export default useAddStudent;
图解组合 API
Composition API
和 Option API
混合使用
组合API/注入API
) 将暴露的数据注入到 data 中
将暴露的方法注入到 methods 中
{
{name}}{
{age}}
setup
beforeCreate
: 表示组件刚刚被创建出来, 组件的data和methods还没有初始化好
Created
: 表示组件刚刚被创建出来, 并且组件的data和methods已经初始化好
没有执行 Created 生命周期方法
在 setup 函数中,是无法使用 data 和 methods
直接将 setup 函数中 this 修改成了 undefined
setup 函数只能是同步的不能是异步的
{
{name}}{
{age}}
reactive
是 Vue3 中提供的实现响应式数据的方法
Vue2 中响应式数据是通过 defineProperty 来实现
的在 Vue3 中响应式数据是通过 ES6 的 Proxy 来实现
的reactive 参数必须是对象(json/arr)
默认情况下修改对象, 界面不会自动更新
重新赋值
的方式{
{state.time}}
实现响应式数据
的方法reactive 必须传递一个对象
只想让某个变量实现响应式
的时候会非常麻烦ref 方法
, 实现对简单值的监听
ref 底层的本质其实还是 reactive
自动根据我们给 ref 传入的值
将它转换成 ref(xx) -> reactive({value:xx})
在 Vue 中使用 ref 的值不用通过 value 获取
在 JS 中使用 ref 的值必须通过 value 获取
{
{age}}
ref 和 reactive 区别
ref 类型
的数据, 那么 Vue 会自动帮我们添加 .value
reactive 类型
的数据, 那么 Vue 不会自动帮我们添加 .value
Vue 是如何决定
是否需要自动添加 .value 的
解析数据之前
, 会自动判断这个数据是否是 ref 类型
的如果是就自动添加 .value, 如果不是就不自动添加 .value
Vue 是如何判断
当前的数据是否是 ref 类型的
数据的私有属性 __v_ref 来判断
的私有的属性
, 并且取值为 true
, 那么就代表是一个 ref 类型的数据
我们如何判断
数据到底是 ref 还是 reactive
isRef / isReactive
方法{
{age}}
无论是通过 ref 还是 reactive 都是递归监听
数据量比较大, 非常消耗性能
{
{state.a}}{
{state.gf.b}}{
{state.gf.f.c}}{
{state.gf.f.s.d}}
如何触发非递归监听属性更新界面
shallowRef
类型数据, 可以通过 triggerRef
来触发需要监听的数据量比较大的时候
, 我们才使用 shallowRef/shallowReactive
{
{state.a}}{
{state.gf.b}}{
{state.gf.f.c}}{
{state.gf.f.s.d}}
{
{state}}
toRaw
从 Reactive 或 Ref 中得到原始数据
toRaw 作用
做一些不想被监听的事情(提升性能)
{
{state}}
{
{state}}
将数据标记为永远不能追踪的数据
{
{state}}
创建一个 ref 类型数据, 并和以前的数据关联
创建出来的数据和以前无关(复制)
数据变化会自动更新界面
创建出来的数据和以前的有关(引用)
数据变化不会自动更新界面
{
{state}}
批量创建 ref 类型数据, 并和以前数据关联
{
{state}}
customRef
返回一个 ref 对象, 可以显式地控制依赖追踪和触发响应
{
{age}}
{
{age}}
通过给元素添加 ref='xxx'
通过 refs.xxx 的方式来获取元素
通过 ref 来获取元素
我是div
readonly
只读
的数据,并且是递归只读
shallowReadonly
只读
的数据,但是不是递归只读的
isReadonly
判断是否是一个只读的数据
const 和 readonly 区别
const
: 赋值保护
,不能给变量重新赋值
readonly
: 属性保护
,不能给属性重新赋值
{
{state.name}}{
{state.attr.age}}{
{state.attr.height}}
Vue 2.x
中是通过 defineProperty
来实现响应式数据的Vue 3.0
中是通过 Proxy
来实现响应式数据的let obj = { name: 'lnj', age: 18};let state = new Proxy(obj, { get(obj, key){ console.log(obj, key); // { name: 'lnj', age: 18 } name return obj[key]; }, set(obj, key, value){ console.log(obj, key, value ); // { name: 'lnj', age: 18 } name zx obj[key] = value; console.log('更新UI界面'); }});// console.log(state.name); // lnjstate.name = 'zx';console.log(state);
set
方法必须通过返回值
告诉 Proxy
此次操作是否成功
let arr = [1, 3, 5]; // [1, 3, 5, 7]let state = new Proxy(arr, { get(obj, key){ console.log(obj, key); // [1, 3, 5 ] 1 return obj[key]; }, set(obj, key, value){ // [1, 3, 5 ] 3 7 // [1, 3, 5, 7 ] length 4 console.log(obj, key, value ); obj[key] = value; console.log('更新UI界面'); return true; }});console.log(state[1]);state.push(7);
shallowReactive
function shallowReactive(obj){ return new Proxy(obj, { get(obj, key){ return obj[key]; }, set(obj, key, val){ obj[key] = val; console.log('更新UI界面'); return true; } })}let obj = { a: 'a', gf:{ b:'b', f;{ c:'c', s:{ d:'d' } } }}let state = shallowReactive(obj);state.a = '1'; // 更新UI界面state.gf.b = '2';state.gf.f.c = '3';state.gf.f.s.d = '4';
shallowRef
function shallowRef(val){ return shallowReactive({ value:val});}function shallowReactive(obj){ return new Proxy(obj, { get(obj, key){ return obj[key]; }, set(obj, key, val){ obj[key] = val; console.log('更新UI界面'); return true; } })}let obj = { a: 'a', gf:{ b:'b', f;{ c:'c', s:{ d:'d' } } }}let state = shallowRef(obj);// 没效果// state.value.a = '1';// state.value.gf.b = '2';// state.value.gf.f.c = '3';// state.value.gf.f.s.d = '4';state.value = { a:1, gf:{ b:2, f;{ c:3, s:{ d:4 } } }}
reactive
function reactive(obj){ if(typeof obj === 'object'){ if(obj instanceof Array){ // 如果是一个数组,那么取出数组中的每一个元素, // 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy obj.forEach((item, index) => { if(typeof item === 'object'){ obj[index] = reactive(item); } }) }else{ // 如果是一个对象,那么取出对象属性的取值, // 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy for(let key in obj){ let item = obj[key]; if(typeof item === 'object'){ obj[key] = reactive(item); } } } return new Proxy(obj, { get(obj, key){ return obj[key]; }, set(obj, key, val){ obj[key] = val; console.log('更新UI界面'); return true; } }) }else{ console.warn(`${ obj} is not object`); }}/*let obj = { a: 'a', gf:{ b:'b', f;{ c:'c', s:{ d:'d' } } }}let state = reactive(obj)state.a = 1;state.gf.b = 2;state.gf.f.c = 3;state.gf.f.s.d = 4;*/let arr = [{ id:1, name:'鲁班', attr:{ age:18}}, { id:2, name:'虞姬'}]let state = reactive(arr)state[0].name='zx'state[0].attr.age = 666state[1].id = 3
ref
function ref(val){ return reactive({ value:val})}function reactive(obj){ if(typeof obj === 'object'){ if(obj instanceof Array){ // 如果是一个数组,那么取出数组中的每一个元素, // 判断每一个元素是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy obj.forEach((item, index) => { if(typeof item === 'object'){ obj[index] = reactive(item); } }) }else{ // 如果是一个对象,那么取出对象属性的取值, // 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成 Proxy for(let key in obj){ let item = obj[key]; if(typeof item === 'object'){ obj[key] = reactive(item); } } } return new Proxy(obj, { get(obj, key){ return obj[key]; }, set(obj, key, val){ obj[key] = val; console.log('更新UI界面'); return true; } }) }else{ console.warn(`${ obj} is not object`); }}/*let obj = { a: 'a', gf:{ b:'b', f;{ c:'c', s:{ d:'d' } } }}let state = reactive(obj)state.a = 1;state.gf.b = 2;state.gf.f.c = 3;state.gf.f.s.d = 4;*/let arr = [{ id:1, name:'鲁班', attr:{ age:18}}, { id:2, name:'虞姬'}]let state = reactive(arr)state[0].name='zx'state[0].attr.age = 666state[1].id = 3
shallowReadonly
function shallowReadonly(obj){ return new Proxy(obj, { get(obj, key){ return obj[key]; }, set(obj, key, val){ // obj[key] = val; // console.log('更新UI界面'); // return true; console.warn(`${ key}是只读的,不能赋值`); } })}let obj = { a: 'a', gf:{ b:'b', f;{ c:'c', s:{ d:'d' } } }}let state = shallowReadonly(obj)state.a = 1state.gf.b = 2
转载地址:http://dcqwi.baihongyu.com/