vue3.0
亮点
- Performance 性能比vue2.0开1.2~2倍
- Tree shaking support :按需编译,体积比vue2.x更小
- Composition API :组合 API (类似React Hooks)
- Better TypeScript support :更好的Ts 支持
- Custom Render API :暴露自定义的渲染API
- Fragment,Teleport (protal),suspense :更先进的组件
变化
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,就不需要创建对象,然后根据对象渲染。
vue3.0快速上手
创建有三种方式:
- Vue-cli
- Webpack
- Vite
安装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
<template>
<div>
<p>{{ count }}</p>
<button @click="myFun">count+1</button>
<form>
<input type="text" v-model="state2.stu.id" />
<input type="text" v-model="state2.stu.name" />
<input type="text" v-model="state2.stu.age" />
<input type="submit" @click="addStu" />
</form>
<ul>
<li
v-for="(item, index) in state.stus"
:key="item.id"
@click="remove(index)"
>
{{ item.name }}-{{ item.age }}
</li>
</ul>
</div>
</template>
<script>
// 注意必须引入reactive和ref才可以使用组合函数
import { reactive, ref } from "vue";
// 下面这样是放到独立一个模块当中去管理
import userRemoveStudent from "./components/remove";
import addStuent from "./components/add";
export default {
name: "App",
// setup()函数 组合API入口函数
setup() {
// let count = 0;
// 定义一个名称叫做count的变量,初始值为0
// 这个变量发生变化时,vue会自动更新UI
// ref 函数注意点:
// ref 函数只能监听简单类型的变化,不能监听复杂数据类型(object/ array)
let count = ref(0);
// 在组合API中定义方法,不用到methods中,直接定义暴露即可
function myFun() {
console.log(count);
count.value += 1;
}
// let state = reactive({
// stus: [
// { id: 1, name: "niuniu", age: 18 },
// { id: 2, name: "fqniu", age: 25 },
// { id: 3, name: "shaniu", age: 30 },
// ],
// });
// function remove(id) {
// state.stus = state.stus.filter((item, idx) => idx !== id);
// }
// 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 = "";
// }
// 注意点:
// 在组合API上定义的变量和方法 要想在外界使用,必须通过return {xxx, xxx}暴露出去
// return { count, state, myFun, remove , state2, addStu};
let { state, remove } = userRemoveStudent();
let { state2, addStu } = addStuent(state);
return { state, remove, state2, addStu };
},
};
// 把删除的逻辑放在一起 解决vue2中业务分散,data,methods,computed,watch
// function userRemoveStudent() {
// let state = reactive({
// stus: [
// { id: 1, name: "niuniu", age: 18 },
// { id: 2, name: "fqniu", age: 25 },
// { id: 3, name: "shaniu", age: 30 },
// ],
// });
// function remove(id) {
// state.stus = state.stus.filter((item, idx) => idx !== id);
// }
// return { state, remove };
// }
// 把添加的逻辑放在一起 解决vue2中业务分散,data,methods,computed,watch
// function addStuent(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 };
// }
</script>
组合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?
- 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 一样,也是用实现响应式数据的方法
- 由于 reactive 必须传递一个对象,所以导致在企业开发中,如果只是让某个变量实现响应式会非常麻烦,所以 vue3就给我们提供了ref方法,实现对简单的监听
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 };
},
递归监听 shallowRef / shallowReactive
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 才是第一层
toRaw方法
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 方法
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 { toRef } from "vue";
setup() {
let obj = { name: "fqniu" };
// let state = ref(obj.name);
let state = toRef(obj, "name");
console.log(state);
/**
* ref(obj.name) —— ref(fqniu)
* reactive({value:fqniu})
*
* ref ——> 复制 修改响应式数据不会影响以前的数据
* 数据发生变化,界面就会自动刷新
* toRef ——> 引用 修改响应式数据会影响以前的数据
* 数据发生变化,界面也不会自动刷新
*
* toRef应用场景:
* 如果想让响应式和以前的数据关联起来,并且更新响应式数据之后还不想更新UI,那么就可以使用toRef
*/
function myfun() {
state.value = "ffqq";
/**
* 结论:
* 如果利用ref将某个对象中的属性变为响应式的数据
* 我们修改为响应式数据是不会影响到原始数据;
*
* 结论:
* 如果利用toRef将某个对象中的属性变为响应式的数据
* 我们修改为响应式数据是会影响到原始数据;
* 但是如果响应式的数据是通过toRef创建的,那么修改了数据并不会触发UI页面的更新
*/
console.log(obj);
console.log(state);
}
return { state, myfun };
toRefs 方法
多个属性变成响应式,使用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 };
以上都是做性能优化的
customRef
返回一个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 };
},
}
ref获取元素
<div ref="box">我是div</div>
setup() {
/**
* 1、获取元素
* 在vue2.x中我们可以通过给元素添加ref = 'xxx'
* 然后在代码中通过refs.xxx 的方式获取元素
* 在vue3中我们也可以通过ref来获取元素
*/
// 注意 this.$refs.box 这种方式不能获取到元素
// console.log(this.$refs.box);
//setup是在beforeCreate 前触发的
// 1、先创建响应式数据, 2、在暴露出去, 3、对响应式数据赋值,4、在生命周期中获取到
let box = ref(null); //ref本质还是reactive({value:null})
console.log(box.value);
// 挂载后,去执行里面的回调函数 这样就可以拿到
onMounted(() => {
console.log("onMounted", box.value);
});
return { box };
},
readonly
用于创建一个只读的数据,并且是递归只读;
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响应式数据本质
- vue2中通过defineProperty()来实现响应式数据的
- vue3中通过proxy来实现响应式数据的
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)
手写shallowReactive和shallowRef
非递归监听
// 手写 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",
},
},
}
手写reactive和ref
递归监听
// 递归监听
// 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
手写shallowReadonly
function shallowReadonly() {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, val) {
// obj[key] = val
// return true
// 更新UI
console.warn(`${key} 是只读的,不能赋值`)
}
})
}
// 测试
let obj = {
value: 18,
a: "a",
gf: {
b: "b",
f: {
d: "d",
e: "e",
},
},
}
let state = shallowReadonly(obj)
state.a = 1
// a 是只读的,不能赋值