1、diff算法
vue2中的虚拟DOM是进行全量的对比vue3增加了静态标记(PatchFlag) 在于上次虚拟节点对比时,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容;在创建虚拟DOM的时候,会根据DOM中的内容会不会变化,添加静态标记; 对比地址 :vue template explorer
2、hoistState :静态提升
vue2 无论元素是否参与更新,每次都会重新创建,然后再渲染vue3 中对不参与更新的元素,会做静态提升,只会创建一次,在渲染时直接复用即可 <div>Hello World!</div> <div>Hello World!</div> <div>Hello World!</div> <div>Hello World!</div> <div>{{message}}</div> import { createVNode as _createVNode, toDisplayString as _toDisplayString, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue" // 静态提升: 不需要创建的元素放到外面 const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */) const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */) const _hoisted_3 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */) const _hoisted_4 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock(_Fragment, null, [ _hoisted_1, _hoisted_2, _hoisted_3, _hoisted_4, _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */) ], 64 /* STABLE_FRAGMENT */)) }3、cacheHandler 事件侦听器缓存
默认情况下onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一函数,所以没有追踪变化,直接缓存起来复用即可 // 事件监听缓存 <div> <button @click="onClick">按钮</button> </div> // 开启事件监听缓存之前 export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createBlock("div", null, [ _createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"]) ])) } // 开启事件监听缓存之后 // 没有 静态标记了 就不会追踪,不会对比,性能就变好 //注意vue3 的diff算法中,只有有静态标记的才会进行比较,追踪 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))) }, "按钮") ])) }4、SSR渲染
当有大量静态的内容是,这些内容会被当做纯字符串推进一个buffer里面,即是存在动态的绑定,会通过这些模板嵌入进去,这样会比通过虚拟DOM来渲染的快很多当静态内容大到一定量级是,会用 _createStaticVNode 方法在客户端去生成一个static node,这些静态node 会被直接innerHTML,就不需要创建对象,然后根据对象渲染。创建有三种方式:
Vue-cliWebpackVite安装vite npm install -g create-vite-app
利用vite创建vue3项目 create-vite-app projectName
安装依赖运行项目 cd projectName npm install npm run dev
vue3兼容vue2
什么是vite?
vite是vue作者开发的一款意图取代webpack的工具
实现原理:利用ES6的import 会发送请求去请求加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间
组合API的本质
1、Compositon API 和 Option API 混合使用 2、Composition API 本质 (组合PI / 注入API) 3、setup 执行时机 4、setup 注意点
Option API 就是之前的data、methods、computed、watch等 Compositon API 就是setup中的ref、reactive等
本质:组合或者注入API
setup 执行时机: 是在beforecreate 钩子之前完成的;
setup 注意点:
由于在执行setup函数时,还没有执行created生命周期方法,所以在setup函数中是无法使用data和methods;在setup函数中是无法使用data和methods,所以vue为了避免我们错误,它直接setup函数中的this修改为了undefined;setup函数只能是同步的,不能是异步的;什么是reactive?
reactive是vue3中提供的实现响应式数据的方法vue2中响应式数据是通过Object.defineProperty()实现vue3中响应式数据是通过ES6的 proxy实现reactive的注意点
reactive参数必须是对象(json,arr)如果给reactive传递了其他对象 默认情况下修改了对象,界面不会自动刷新如果想更新,可以通过重新赋值的方式 <p>{{ state2.time }}</p> setup() { // 创建一个响应式数据 // 本质 :将传入的数据包装为一个proxy对象 let state1 = reactive(333); let state2 = reactive({ age: 25, time: new Date(), }); function myfun() { // 由于在创建响应式数据的时候传递的不是一个对象,所以无法实现响应式 state1 = 666; state2.age = 18; console.log(state1); console.log(state2); const newTime = new Date(state2.time.getTime() + 1); newTime.setDate(state2.time.getDate() + 1); state2.time = newTime; console.log(state2.time); } return { state1, state2, myfun }; },ref的本质
ref底层的本质其实还是 reactive系统会自动根据我们给 ref 传入的值将它转换成 ref(xxx) — reactive ({ value:xxx })ref注意点
在 vue 中的 template 中使用ref 的值不用通过value 获取在 js 中的 script 中 使用 ref 的值必须通过 value 获取 <template> <div> //这里不用value获取 <p>{{ state }}</p> <div> <template> setup() { // 创建一个响应式数据 // ref底层的本质其实还是 reactive // 底层会自动根据我们给 ref 传入的值将它转换成 ref(xxx) — reactive ({ value:xxx }) let state = ref(18); function myfun() { console.log(state) // 在 js 中使用 ref 的值必须通过 value 获取 state.value = 25; } return { state, myfun }; }, ref和reactive的区别 如果在template里使用时ref 类型的数据,那么vue会自动帮我们添加 .value 如果在template里使用时 reactive 类型的数据,那么vue 不会自动帮我们添加 .value vue是如果自动帮我们添加 .value的? vue在解析数据之前,会先判断这个数据是否是ref类型的,如果是就自动添加.value, 否则不添加 vue是如何判断当前的数据是否是ref类型的? 通过当前的数据的 __v_isref 来判断的 如果有这个私有的属性,并且取值为true , 那么就代表一个ref类型的数据 import { reactive, ref, isRef, isReactive } from "vue"; setup() { // 创建一个响应式数据 // 本质 :将传入的数据包装为一个proxy对象 // let state = ref(18); let state = reactive({ value: 18 }); function myfun() { console.log(state); state.value = 25; console.log(isRef(state)); // false console.log(isReactive(state)); // true } return { state, myfun }; },1、递归监听 默认情况下,无论通过 ref 还是 reactive 都是递归监听;
2、递归监听 存在的问题 如果数据量比较大,非常消耗性能
3、非递归监听 只能监听第一层,不能监听里面的其他层数
4、应用场景 一般使用 ref和 reactive 即可 只有在需要监听的数据量比较大的时候,才使用shallowRef / shallowReactive
<p>{{ state.value }}</p> <p>{{ state.a }}</p> <p>{{ state.gf.b }}</p> <p>{{ state.gf.f.d }}</p> <button @click="myfun">按钮</button> setup() { // let state = reactive({ // value: 18, // a: "a", // gf: { // b: "b", // f: { // d: "d", // e: "e", // }, // }, // }); let state = ref({ value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, }); function myfun() { // state.a = 1; // state.gf.b = 2; // state.gf.f.d = 3; state.value.a = 1; state.value.gf.b = 2; state.value.gf.f.d = 3; } return { state, myfun }; },非递归监听
只监听第一层,不能监听里面的其他层数
创建非监听数据的两个方法:shallowReactive 和 shallowRef
如何触发非监听属性更新界面?
如果是 shalllowRef 类型数据,可以通过 triggerRef 触发
import { reactive, ref, shallowReactive, shallowRef } from "vue"; let state = shallowReactive({ value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, }); function myfun() { state.a = 1; state.gf.b = 2; state.gf.f.d = 3; console.log(state); console.log(state.gf); console.log(state.gf.f); } return { state, myfun }; },注意:虽然是监听第一层,但是下面的数据也发生变化了,只要第一层的数据发生变化,他就会更新UI,他一更新UI,下面的就显示出来了;
但是我注释掉 state.a =1 ,会发现第一层监听页面不会更新了,下面的就也不会更新了,UI也就不更新了;
let state = shallowRef({ value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, }); function myfun() { state.value.a = 1; state.value.gf.b = 2; state.value.gf.f.d = 3; // 注意点:如果是shallowRef 创建的数据,vue监听的是.value的变化,并不是第一层的变化 console.log(state); console.log(state.value); console.log(state.value.gf); console.log(state.value.gf.f); } return { state, myfun };引入一个方法:triggerRef :根据你传入的数据主动的更新我的界面;
let state = shallowRef({ value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, }); function myfun() { // state.a = 1; // state.gf.b = 2; // state.gf.f.d = 3; // console.log(state); // console.log(state.gf); // console.log(state.gf.f); // state.value.a = 1; // state.value.gf.b = 2; state.value.gf.f.d = 3; // 注意点:只提供了triggerRef 的方法,没有triggerReactive 方法; // 如果是reactive 类型的数据,那么是无法主动触发页面更新的 triggerRef(state) // 注意点:如果是shallowRef 创建的数据,vue监听的是.value的变化,并不是第一层的变化 console.log(state); console.log(state.value); console.log(state.value.gf); console.log(state.value.gf.f); } return { state, myfun };shallowRef 本质
// ref(10) —— reactive({value:10}) // shallowRef(10) —— shallowReactive({value:10}) // 如果是 shallowRef 创建的数据,它监听的是 .value的变化 // 因为底层本质上value 才是第一层1、toRaw 从 reactive 或 ref 中得到原始数据
2、toRaw的作用 做一些不想监听的事情(提升性能)
import { reactive, ref, shallowReactive, shallowRef, toRaw } from "vue"; setup() { let obj = { name: "fqniu", age: 25 }; /** * ref / reactive * 每次更新都会被追踪,都会更新UI页面,但是这样非常消耗性能的 * 所以如果我们有一些操作不需要追踪,不需要更新UI界面, * 那么这个时候可以通过 toRaw 方法 拿到他的原始数据,对原始数据进行修改 * 这样就不会被追踪,这样就不会更新UI界面 */ let state = reactive(obj); // 通过 toRaw 方法 拿到他的原始数据,对原始数据进行修改 let obj2 = toRaw(state); console.log(obj === state); // false console.log(obj2 === state); // false console.log(obj2 === obj); // true // state 和 obj 关系 // 引用关系:state本质是proxy对象,在这个proxy对象中引用了obj function myfun() { obj2.name = "ffqq"; console.log(obj2); console.log(state); } return { obj, obj2, state, myfun }; }, let obj = { name: "fqniu", age: 25 }; /** * ref 本质 —— reactive * ref(obj) —— reactive({value:obj}) * * 注意点: * 如果想通过toRaw 拿到ref类型的原始数据(创建时传入的那个数据) * 那么就必须明确告诉toRaw 方法 要获得的是 .value的值 * 因为经过vue处理之后,.value中保存的才是当初创建时传入的那个原始数据 */ let state = ref(obj); let obj2 = toRaw(state); console.log(obj === state); // false console.log(obj2 === state); // false console.log(obj2 === obj); // true function myfun() { obj2.name = "ffqq"; console.log(obj2); console.log(state); } return { obj, obj2, state, myfun }; },markRaw方法 ,数据永远不被追踪;
import {reactive, markRaw} from 'vue let obj = { name: "fqniu", age: 25 }; obj = markRaw(obj); let state = reactive(obj); function myfun() { state.name = 'ffqq' } return { obj, state, myfun }; },多个属性变成响应式,使用toRef
import { toRefs } from "vue"; setup() { let obj = { name: "fqniu", age: 25 }; // let state = ref(obj.name); // let states = toRef(obj, 'name'); // let states = toRef(obj, 'age'); // 相当于遍历上面的 let state = toRefs(obj); console.log(state); function myfun() { state.name.value = "ffqq"; state.age.value = 18; console.log(obj); console.log(state); } return { state, myfun };以上都是做性能优化的
返回一个ref对象,可以显式的控制依赖追踪和触发响应
自定义myRef
function myRef(value) { return customRef((track, trigger) => { return { get() { track(); // 告诉vue这个数据是响应式需要追踪变化的 console.log("get", value); return value; }, set(newValue) { console.log(newValue); value = newValue; trigger(); // 告诉vue触发界面更新 }, }; }); } function myRef(value) { return customRef((track, trigger) => { fetch("../public/data.json") .then((res) => { return res.json(); }) .then((data) => { console.log(data); value = data; trigger(); }) .catch((err) => { console.log(err); }); return { get() { track(); // 告诉vue这个数据是响应式需要追踪变化的 console.log("get", value); // 注意不能在get方法中 发送网络请求 // 渲染页面 ——> 调用get ——> 发送网络请求 // 保存数据 ——> 更新界面 ——> 调用get // fetch("../public/data.json") // .then((res) => { // return res.json(); // }) // .then((data) => { // console.log(data); // value = data; // trigger(); // }) // .catch((err) => { // console.log(err); // }); return value; }, set(newValue) { console.log(newValue); value = newValue; trigger(); // 告诉vue触发界面更新 }, }; }); } export default { setup() { // setup 函数只能是同步函数,不能说是 异步函数 // let state = ref([]); // fetch("../public/data.json") // .then((res) => { // return res.json(); // }) // .then((data) => { // console.log(data); // state.value = data; // }) // .catch((err) => { // console.log(err); // }); let state = myRef("../public/data.json"); return { state }; }, }用于创建一个只读的数据,并且是递归只读;
setup() { // 用于创建一个只读的数据,并且是递归只读 let state = readonly({ name: "fqniu", attr: { age: 18, height: 1.73 } }); function myfun() { state.name = "niuniu"; state.attr.age = 25; state.attr.height = 1.88; } return { state, myfun }; }, setup() { // 用于创建一个只读的数据,不是递归只读 只递归第一层 第一层不变化,之后的数据变化 let state = shalowReadonly({ name: "fqniu", attr: { age: 18, height: 1.73 } }); function myfun() { state.name = "niuniu"; state.attr.age = 25; state.attr.height = 1.88; } return { state, myfun }; }, isReadonly 判断是否是 isReadonly(state) // true const 和 readonly 的区别? const 是赋值保护,不能给变量重新赋值 readonly 是属性保护,不能给属性重新赋值vue3之前:
受 JavaScript 的限制,Vue 无法检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。 还有一种情况,vue无法检测到data属性值为数组或对象的修改,所以我们需要用原对象与要混合进去的对象的属性一起创建一个新的对象。可以使用this.$set或者对象的深拷贝,如果是数组则可以使用splice,扩展运算符等方法来更新。
// 把对象包装为响应式数据 let obj = { name: 'fqniu', age: 25 } // new Proxy是系统自带的类 // 第一个参数 是想把谁变为响应式,第二参数是把数据变为响应式之后如何监听它的获取和赋值 let state = new Proxy(obj, { get(obj, key) { // console.log(obj, key) // { name: 'fqniu', age: 25 } name return obj[key] }, // set方法监听修改 第一个是操作的对象,第二个是操作的属性,第三个是新赋值的值 set(obj, key, value) { console.log(obj, key, value) // { name: 'fqniu', age: 25 } name niuniu // 更新UI界面,先赋值,在更新 obj[key] = value console.log('更新UI界面') return true } }) // console.log(state.name) // fqniu state.name = 'niuniu' console.log(state) // { name: 'niuniu', age: 25 } // 获取对象的值 执行get方法 // 设置对象的值 执行set方法 什么时候设置值就知道什么时候更新UI界面 // proxy 注意点 // set方法必须返回值告诉proxy此次操作是否成功 ,不然可能不会继续执行 let arr = [1, 3, 5] let state = new Proxy(arr, { get(obj, key) { console.log(obj, key) // [ 1, 3, 5 ] 1 return obj[key] }, // set方法监听修改 第一个是操作的对象,第二个是操作的属性,第三个是新赋值的值 set(obj, key, value) { console.log(obj, key, value) // [ 1, 3, 5 ] 3 7 // 更新UI界面,先赋值,在更新 obj[key] = value console.log('更新UI界面') } }) // console.log(state[1]) state.push(7)发现这时候报错了? 解决方法 :set方法必须返回值告诉proxy此次操作是否成功 ,不然可能不会继续执行;
// proxy 注意点 // set方法必须返回值告诉proxy此次操作是否成功 ,不然可能不会继续执行 let arr = [1, 3, 5] let state = new Proxy(arr, { get(obj, key) { console.log(obj, key) // [ 1, 3, 5 ] 1 return obj[key] }, // set方法监听修改 第一个是操作的对象,第二个是操作的属性,第三个是新赋值的值 set(obj, key, value) { // [ 1, 3, 5 ] 3 7 // 第一次添加一个索引 为7 // [ 1, 3, 5, 7 ] length 4 // 第二次把长度改为 4 console.log(obj, key, value) // [ 1, 3, 5 ] 3 7 // 更新UI界面,先赋值,在更新 obj[key] = value console.log('更新UI界面') return true; //助力必须有返回值,告诉当前操作是否成功,否则不会继续下一次操作,会报错 } }) // console.log(state[1]) state.push(7)非递归监听
// 手写 shallowRef function shallowRef(val) { return shallowReactive({ value: val }) } // 手写 shallowReactive function shallowReactive(obj) { return new Proxy(obj, { get(obj, key) { return obj[key] }, set(obj, key, val) { obj[key] = val // 更新UI console.log('更新UI') return true } }) } // 测试 shallowReactive let obj = { value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, } // let state = shallowReactive(obj) // 触发 : 更新UI // state.a = 1; // state.gf.b = 2; // state.gf.f.d = 3; let state = shallowRef(obj) // 但是下面这种触发不了 更新UI state.value.a = 1; state.value.gf.b = 2; state.value.gf.f.d = 3; // 用这种触发 : 更新UI state.value = { value: 25, a: "1", gf: { b: "2", f: { d: "3", e: "4", }, }, }递归监听
// 递归监听 // let arr = [{id:1,name:'fqniu'}, {id:2,name:'niuniu'}] // let obj = {a:{id:1,name:'fqniu'}, b:{id:2,name:'niuniu'}} 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 // 更新UI console.log('更新UI') return true } }) } else { console.warn(`${obj} is not object`) } } // 测试 对象 let obj = { value: 18, a: "a", gf: { b: "b", f: { d: "d", e: "e", }, }, } let state = reactive(obj) // 触发更新 state.a = 1; state.gf.b = 2; state.gf.f.d = 3; // 测试 数组 let arr = [{ id: 1, name: 'fqniu' }, { id: 2, name: 'niuniu' }] let state = reactive(arr) // // 触发更新 state[0].name = 'ffqq' state[0].id = 3