Vue 3.0 学习理解
-
- 1. Vue 3.0 开篇
- 2. Vue 3.0 diff 算法
- 3. Vue 3.0 静态提升和监听缓存
- 4. Vue 3.0 快速上手
- 5. Vue 3.0 组合 API
- 6. setup 执行时机及注意点
- 7. reactive 理解
- 8. ref 理解
- 9. ref 和 reactive 区别
- 10. 递归监听
- 11. 非递归监听
- 12. shallowRef 本质
- 13. toRaw 理解
- 14. markRaw 理解
- 15. toRef 理解
- 16. toRefs 理解
- 17. customRef 理解
- 18. ref 获取元素
- 19. readonly 家族
- 20. Vue 3.0 响应式数据本质
- 21. 手写 shallowReactive——shallowRef 理解
- 22. 手写 reactive-ref 理解
- 23. 手写 readonly-shallowReadonly 理解
1. Vue 3.0 开篇
1.1 Vue 3.0 六大亮点
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
:更先进的组件
1.2 Vue3.0是如何变快的
diff 算法优化
hoistStatic 静态提升
cacheHandlers 事件侦听器缓存
ssr 渲染
2. Vue 3.0 diff 算法
2.1 diff 算法优化
- Vue2 中的虚拟dom是进行
全量的对比
- Vue3 新增了
静态标记(PatchFlag)
- 在
与上次虚拟节点进行对比时候
,只对比带有patch flag的节点
- 并且可以
通过 flag 的信息得知当前节点要对比的具体内容
- 在
<div>
<p>张三</p>
<p>张三</p>
<p>张三</p>
<p>{
{
msg}}}</p>
</div>
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 */)
]))
}
2.2 静态标记值
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
}
3. Vue 3.0 静态提升和监听缓存
3.1 hoistStatic 静态提升
- Vue2 中
无论元素是否参与更新, 每次都会重新创建, 然后再渲染
- Vue3 中
对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
<div>
<p>张三</p>
<p>张三</p>
<p>张三</p>
<p>{
{
msg}}}</p>
</div>
================================================================================
静态提升之前:
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 */)
]))
}
3.2 cacheHandlers 事件侦听器缓存
- 默认情况下
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"])
]))
}
================================================================================
开启事件监听缓存之后:
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. Vue 3.0 快速上手
4.1 创建Vue 3.0 的3种方式
Vue-CLI
Webpack
Vite
4.2 Vue-CLI 创建
npm install -g @vue/cli
vue create projectName
cd projectName
vue add vue-next
npm run serve
4.3 Webpack 创建
git clone https://github.com/vuejs/vue-next-webpack-preview.git projectName
cd projectName
npm install
npm run dev
4.4 Vite 创建
4.4.1 什么是 Vite
Vite
是 Vue 作者开发的一款意图取代 webpack 的工具
4.4.2 Vite 实现原理
- 利用
ES6
的import
会发送请求去加载文件
的特性 拦截请求
, 做一些预编译
,省去 webpack 冗长的打包时间
4.4.3 安装 Vite
npm install -g create-vite-app
4.4.4 利用 Vite 创建 Vue 3.0 项目
create-vite-app projectName
4.4.5 安装依赖运行项目
cd projectName
npm install
npm run dev
4.5 Vue 3.0 兼容 Vue 2.x
4.5.1 Vue 2.x 存在的问题
数据与逻辑处理分开
耗性能,维护不方便
<template>
<div>
<form>
<input type="text" v-model="stu.id">
<input type="text" v-model="stu.name">
<input type="text" v-model="stu.age">
<input type="submit" @click="addStu">
</form>
<ul>
<li v-for="(stu, index) in stus"
:key="stu.id"
@click="remStu(index)">
{
{stu.name}} -- {
{stu.age}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data:function () {
return {
stus:[
{
id:1, name:'zs', age:10},
{
id:2, name:'ls', age:20},
{
id:3, name:'ww', age:30},
],
stu:{
id:'',
name:'',
age:''
}
// 新增功能1的数据
// 新增功能2的数据
}
},
methods:{
remStu(index){
this.stus = this.stus.filter((stu, idx) => idx !== index);
},
addStu(e){
e.preventDefault();
const stu = Object.assign({
}, this.stu);
this.stus.push(stu);
this.stu.id='';
this.stu.name='';
this.stu.age='';
}
// 新增功能1的业务逻辑
// 新增功能2的业务逻辑
},
computed:{
// 新增功能1的业务逻辑
// 新增功能2的业务逻辑
},
watch:{
// 新增功能1的业务逻辑
// 新增功能2的业务逻辑
}
}
</script>
<style>
</style>
5. Vue 3.0 组合 API
5.1 Composition API 基本用法
<template>
<div>
<p>{
{count}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
ref} from 'vue';
export default {
name: 'App',
// setup函数是组合API的入口函数
setup(){
// let count = 0;
// 定义了一个名称叫做count变量, 这个变量的初始值是0
// 这个变量发生改变之后, Vue会自动更新UI
let count = ref(0);
// 在组合API中, 如果想定义方法, 不用定义到methods中, 直接定义即可
function myFn() {
// alert(123);
// console.log(count.value);
count.value += 1;
}
// 注意点:
// 在组合API中定义的变量/方法, 要想在外界使用, 必须通过return {xxx, xxx}暴露出去
return{
count, myFn}
}
}
</script>
<style>
</style>
5.2 Composition API 抽取
<template>
<div>
<ul>
<li v-for="(stu, index) in state.stus"
:key="stu.id"
@click="remStu(index)">
{
{stu.name}} - {
{stu.age}}
</li>
</ul>
</div>
</template>
<script>
import {
reactive} from 'vue';
export default {
name: 'App',
setup() {
/*
// ref函数注意点:
// ref函数只能监听简单类型的变化, 不能监听复杂类型的变化(对象/数组)
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);
}
*/
let {
state, remStu} = useRemoveStudent();
return {
state1, remStu}
}
}
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};
}
</script>
<style>
</style>
5.3 Composition API 组合
App.vue
<template>
<div>
<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="(stu, index) in state.stus"
:key="stu.id"
@click="remStu(index)">
{
{stu.name}} - {
{stu.age}}
</li>
</ul>
</div>
</template>
<script>
import useRemoveStudent from './rem';
import useAddStudent from './add';
export default {
name: 'App',
setup() {
let {
state, remStu} = useRemoveStudent();
let {
state2, addStu} = useAddStudent(state);
return {
state, remStu, state2, addStu}
}
}
/*
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}
}
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};
}
*/
</script>
<style>
</style>
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
5.4 Composition API 本质
Composition API
和Option API
混合使用
- Composition API 本质 (
组合API/注入API
)将暴露的数据注入到 data 中
将暴露的方法注入到 methods 中
<template>
<div>
<p>{
{name}}</p>
<button @click="myFn1">按钮</button>
<p>{
{age}}</p>
<button @click="myFn2">按钮</button>
</div>
</template>
<script>
import {
ref} from 'vue';
export default {
name: 'App',
data: function(){
return {
name: 'lnj',
// age: 18
}
},
methods:{
myFn1(){
alert('abc');
},
// myFn2() {
// alert('www.it666.com');
// }
},
setup() {
let age = ref(18);
function myFn2() {
alert('www.it666.com');
}
return {
age, myFn2}
}
}
</script>
<style>
</style>
6. setup 执行时机及注意点
6.1 setup 执行时机
setup
beforeCreate
: 表示组件刚刚被创建出来,组件的data和methods还没有初始化好
Created
: 表示组件刚刚被创建出来, 并且组件的data和methods已经初始化好
6.2 setup 注意点
- 由于在执行 setup 函数的时候, 还
没有执行 Created 生命周期方法
- 所以
在 setup 函数中,是无法使用 data 和 methods
- 所以
- 由于不能在 setup 函数中使用 data 和 methods
- 所以 Vue 为了避免错误的使用, 它
直接将 setup 函数中 this 修改成了 undefined
- 所以 Vue 为了避免错误的使用, 它
setup 函数只能是同步的不能是异步的
<template>
<div>
<p>{
{name}}</p>
<button @click="myFn1">按钮</button>
<p>{
{age}}</p>
<button @click="myFn2">按钮</button>
</div>
</template>
<script>
import {
ref} from 'vue';
export default {
name: 'App',
data: function(){
return {
name: 'lnj',
}
},
methods:{
myFn1(){
alert('abc');
},
},
// async setup() {
setup() {
let age = ref(18);
function myFn2() {
alert('www.it666.com');
}
// console.log(this); // undefined
// console.log(this.name);
// this.myFn1();
return {
age, myFn2}
}
}
</script>
<style>
</style>
7. reactive 理解
7.1 什么是reactive?
reactive
是 Vue3 中提供的实现响应式数据的方法
- 在
Vue2 中响应式数据是通过 defineProperty 来实现
的 - 而
在 Vue3 中响应式数据是通过 ES6 的 Proxy 来实现
的
7.2 reactive注意点
reactive 参数必须是对象(json/arr)
- 如果给 reactive 传递了其它对象
默认情况下修改对象, 界面不会自动更新
- 如果想更新, 可以通过
重新赋值
的方式
<template>
<div>
<p>{
{state.time}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
reactive} from 'vue';
export default {
name: 'App',
setup() {
// 创建一个响应式数据
// 本质: 就是将传入的数据包装成一个Proxy对象
// let state = reactive(123);
// let state = reactive({
// age: 123
// });
// let state = reactive([1, 3, 5]);
let state = reactive({
time: new Date()
});
function myFn() {
// state = 666; // 由于在创建响应式数据的时候传递的不是一个对象, 所以无法实现响应式
// state.age = 666;
// state[0] = 666;
// 直接修改以前的, 界面不会更新
// state.time.setDate(state.time.getDate() + 1);
// 重新赋值
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
console.log(state.time);
}
return {
state, myFn};
}
}
</script>
<style>
</style>
8. ref 理解
8.1 什么是 ref
- ref 和 reactive 一样, 也是用来
实现响应式数据
的方法 - 由于
reactive 必须传递一个对象
- 所以导致在企业开发中,如果我们
只想让某个变量实现响应式
的时候会非常麻烦 - 所以 Vue3 就给我们提供了
ref 方法
, 实现对简单值的监听
- 所以导致在企业开发中,如果我们
8.2 ref 本质
ref 底层的本质其实还是 reactive
- 系统会
自动根据我们给 ref 传入的值
将它转换成ref(xx) -> reactive({value:xx})
8.3 ref 注意点
在 Vue 中使用 ref 的值不用通过 value 获取
在 JS 中使用 ref 的值必须通过 value 获取
<template>
<div>
<!-- <p>{
{state.age}}</p>-->
<!--
注意点:
如果是通过ref创建的数据, 那么在template中使用的时候不用通过.value来获取
因为Vue会自动给我们添加.value
-->
<p>{
{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
// import {reactive} from 'vue';
import {
ref} from 'vue';
export default {
name: 'App',
setup() {
// let state = reactive({
// age: 18
// })
/*
ref本质:
ref本质其实还是reactive
当我们给ref函数传递一个值之后, ref函数底层会自动将ref转换成reactive
ref(18) -> reactive({value: 18})
* */
let age = ref(18);
function myFn() {
// state.age = 666;
// age = 666;
age.value = 666;
console.log(age);
}
return {
age, myFn}
}
}
</script>
<style>
</style>
9. ref 和 reactive 区别
-
ref 和 reactive 区别
- 如果在 template 里使用的是
ref 类型
的数据, 那么 Vue 会自动帮我们添加 .value
- 如果在 template 里使用的是
reactive 类型
的数据, 那么Vue 不会自动帮我们添加 .value
- 如果在 template 里使用的是
-
Vue 是
如何决定
是否需要自动添加 .value 的- Vue 在
解析数据之前
, 会自动判断这个数据是否是 ref 类型
的 如果是就自动添加 .value, 如果不是就不自动添加 .value
- Vue 在
-
Vue 是
如何判断
当前的数据是否是 ref 类型的- 通过当前
数据的私有属性 __v_ref 来判断
的 - 如果有这个
私有的属性
, 并且取值为 true
, 那么就代表是一个 ref 类型的数据
- 通过当前
-
我们
如何判断
数据到底是 ref 还是 reactive- 通过
isRef / isReactive
方法
- 通过
<template>
<div>
<p>{
{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.ref和reactive区别
- 如果是reactive在template中不会自动添加.value
2.reactive为什么不会自动添加.value?
- 因为Vue在添加的时候首先会判断当前数据类型是ref还是reactive
3.Vue如何判断?
通过包装后的私有属性
__v-isRef
4.我们如何判断数据到底是ref还是reactive?
通过isRef / isReactive 方法
* */
import {
isRef, isReactive} from 'vue';
import {
reactive} from 'vue';
// import {ref} from 'vue';
export default {
name: 'App',
setup() {
// ref(18) -> reactive({value: 18})
// let age = ref(18);
let age = reactive({
value: 18});
function myFn() {
console.log(isRef(age));
console.log(isReactive(age));
age.value = 666;
}
return {
age, myFn}
}
}
</script>
<style>
</style>
10. 递归监听
10.1 递归监听
- 默认情况下,
无论是通过 ref 还是 reactive 都是递归监听
10.2 递归监听存在的问题
- 如果
数据量比较大, 非常消耗性能
<template>
<div>
<p>{
{state.a}}</p>
<p>{
{state.gf.b}}</p>
<p>{
{state.gf.f.c}}</p>
<p>{
{state.gf.f.s.d}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
reactive} from 'vue';
// import {ref} from 'vue';
export default {
name: 'App',
setup() {
let state = reactive({
// let state = ref({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
});
function myFn() {
// state.a = '1';
// state.gf.b = '2';
// state.gf.f.c = '3';
// state.gf.f.s.d = '4';
// state.value.a = '1';
// state.value.gf.b = '2';
// state.value.gf.f.c = '3';
// state.value.gf.f.s.d = '4';
console.log(state);
console.log(state.gf);
console.log(state.gf.f);
console.log(state.gf.f.s);
}
return {
state, myFn}
}
}
</script>
<style>
</style>
11. 非递归监听
如何触发非递归监听属性更新界面
- 如果是
shallowRef
类型数据, 可以通过triggerRef
来触发
- 如果是
11.1 应用场景
- 一般情况下我们使用 ref 和 reactive 即可
- 只有在
需要监听的数据量比较大的时候
, 我们才使用shallowRef/shallowReactive
<template>
<div>
<p>{
{state.a}}</p>
<p>{
{state.gf.b}}</p> <p>{
{state.gf.f.c}}</p>
<p>{
{state.gf.f.s.d}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
shallowReactive} from 'vue';
import {
shallowRef, triggerRef} from 'vue';
export default {
name: 'App',
setup() {
// let state = shallowReactive({
let state = shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
});
function myFn() {
// state.a = '1';
// state.gf.b = '2';
// state.gf.f.c = '3';
// state.gf.f.s.d = '4';
//
// console.log(state);
// console.log(state.gf);
// console.log(state.gf.f);
// console.log(state.gf.f.s);
// state.value = {
// a:'1',
// gf:{
// b:'2',
// f:{
// c:'3',
// s:{
// d:'4'
// }
// }
// }
// }
// state.value.a = '1';
// state.value.gf.b = '2';
// state.value.gf.f.c = '3';
// state.value.gf.f.s.d = '4';
state.value.gf.f.s.d = '4';
// 注意点: Vue3只提供了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);
console.log(state.value.gf.f.s);
}
return {
state, myFn}
}
}
</script>
<style>
</style>
12. shallowRef 本质
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
shallowReactive} from 'vue';
import {
shallowRef} from 'vue';
export default {
name: 'App',
setup() {
// ref -> reactive
// ref(10) -> reactive({value:10})
// shallowRef -> shallowReactive
// shallowRef(10) -> shallowReactive({value: 10})
// 所以如果是通过shallowRef创建的数据, 它监听的是.value的变化
// 因为底层本质上value才是第一层
let state1 = shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
});
let state2 = shallowReactive({
value: {
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
}
});
function myFn() {
}
return {
state, myFn}
}
}
</script>
<style>
</style>
13. toRaw 理解
-
toRaw
从 Reactive 或 Ref 中得到原始数据
-
toRaw 作用
做一些不想被监听的事情(提升性能)
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
reactive, toRaw} from 'vue';
export default {
name: 'App',
setup() {
let obj = {
name:'lnj', age:18};
/*
ref/reactive数据类型的特点:
每次修改都会被追踪, 都会更新UI界面, 但是这样其实是非常消耗性能的
所以如果我们有一些操作不需要追踪, 不需要更新UI界面, 那么这个时候,
我们就可以通过toRaw方法拿到它的原始数据, 对原始数据进行修改
这样就不会被追踪, 这样就不会更新UI界面, 这样性能就好了
* */
let state = reactive(obj);
let obj2 = toRaw(state);
// console.log(obj === obj2); // true
// console.log(obj === state); // false
// state和obj的关系:
// 引用关系, state的本质是一个Proxy对象, 在这个Proxy对象中引用了obj
function myFn() {
// 如果直接修改obj, 那么是无法触发界面更新的
// 只有通过包装之后的对象来修改, 才会触发界面的更新
obj2.name = 'zs';
console.log(obj2); // {name: "zs", age: 18}
console.log(state); // {name: "zs", age: 18}
// state.name = 'zs';
// console.log(state);
}
return {
state, myFn}
}
}
</script>
<style>
</style>
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.markRaw
将数据标记为永远不能追踪的数据
一般在编写自己的第三方库时使用
* */
import {
reactive, toRaw, ref} from 'vue';
export default {
name: 'App',
setup() {
let obj = {
name:'lnj', age:18};
/*
1.ref本质: reactive
ref(obj) -> reactive({value: obj})
* */
let state = ref(obj);
// 注意点: 如果想通过toRaw拿到ref类型的原始数据(创建时传入的那个数据)
// 那么就必须明确的告诉toRaw方法, 要获取的是.value的值
// 因为经过Vue处理之后, .value中保存的才是当初创建时传入的那个原始数据
// let obj2 = toRaw(state);
let obj2 = toRaw(state.value);
console.log(obj);
console.log(state);
console.log(obj2);
function myFn() {
}
return {
state, myFn}
}
}
</script>
<style>
</style>
14. markRaw 理解
将数据标记为永远不能追踪的数据
- 一般在编写自己的第三方库时使用
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
reactive, markRaw} from 'vue';
export default {
name: 'App',
setup() {
let obj = {
name: 'lnj', age: 18};
obj = markRaw(obj);
let state = reactive(obj);
function myFn() {
state.name = 'zs';
}
return {
state, myFn}
}
}
</script>
<style>
</style>
15. toRef 理解
15.1 toRef
创建一个 ref 类型数据, 并和以前的数据关联
15.2 toRef 和 ref 区别
- ref
创建出来的数据和以前无关(复制)
数据变化会自动更新界面
- toRef
创建出来的数据和以前的有关(引用)
数据变化不会自动更新界面
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
ref, toRef} from 'vue';
export default {
name: 'App',
setup() {
let obj = {
name:'lnj'};
/*
ref(obj.name) -> ref(lnj)
-> reactive({value:lnj})
* */
// ref->复制
// let state = ref(obj.name);
// toRef->引用
/*
ref和toRef区别:
ref->复制, 修改响应式数据不会影响以前的数据
toRef->引用, 修改响应式数据会影响以前的数据
ref->数据发生改变, 界面就会自动更新
toRef->数据发生改变, 界面也不会自动更新
toRef应用场景:
如果想让响应式数据和以前的数据关联起来, 并且更新响应式数据之后还不想更新UI, 那么就可以使用toRef
* */
let state = toRef(obj, 'name');
function myFn() {
state.value = 'zs';
/*
结论: 如果利用ref将某一个对象中的属性变成响应式的数据
我们修改响应式的数据是不会影响到原始数据的
* */
/*
结论: 如果利用toRef将某一个对象中的属性变成响应式的数据
我们修改响应式的数据是会影响到原始数据的
但是如果响应式的数据是通过toRef创建的, 那么修改了数据并不会触发UI界面的更新
* */
console.log(obj);
console.log(state);
}
return {
state, myFn}
}
}
</script>
<style>
</style>
16. toRefs 理解
16.1 toRefs
批量创建 ref 类型数据, 并和以前数据关联
<template>
<div>
<p>{
{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
ref, toRef, toRefs} from 'vue';
export default {
name: 'App',
setup() {
let obj = {
name:'lnj', age:18};
// let name = toRef(obj, 'name');
// let age = toRef(obj, 'age');
let state = toRefs(obj);
function myFn() {
// name.value = 'zs';
// age.value = 666;
state.name.value = 'zs';
state.age.value = 666;
console.log(obj);
console.log(state);
// console.log(name);
// console.log(age);
}
return {
state, myFn}
}
}
</script>
<style>
</style>
17. customRef 理解
customRef
返回一个 ref 对象, 可以显式地控制依赖追踪和触发响应
<template>
<div>
<p>{
{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
ref, customRef } from 'vue';
function myRef(value) {
return customRef((track, trigger)=>{
return {
get(){
track(); // 告诉 Vue 这个数据是需要追踪变化的
console.log('get', value);
return value;
},
set(newValue){
console.log('set', newValue);
value = newValue;
trigger(); // 告诉 Vue 触发界面更新
}
}
});
}
export default {
name: 'App',
setup() {
// let age = ref(18); // reactive({value: 18})
let age = myRef(18);
function myFn() {
age.value += 1;
}
return {
age, myFn}
}
}
</script>
<style>
</style>
<template>
<div>
<p>{
{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
ref, customRef } from 'vue';
function myRef(value) {
return customRef((track, trigger)=>{
fetch(value)
.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
return value;
},
set(newValue){
console.log('set', newValue);
value = newValue;
trigger(); // 告诉 Vue 触发界面更新
}
}
});
}
export default {
name: 'App',
// 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};
}
}
</script>
<style>
</style>
18. ref 获取元素
18.1 获取元素
- 在 Vue2.x 中我们可以
通过给元素添加 ref='xxx'
- 然后在代码中
通过 refs.xxx 的方式来获取元素
- 在 Vue3.0 中我们也可以
通过 ref 来获取元素
<template>
<div ref="box">我是div</div>
</template>
<script>
import {
ref, onMounted } from 'vue';
export default {
name: 'App',
/*
beforeCreate
setup
created
*/
setup() {
// console.log(this.$refs.box);
let box = ref(null); // reactive({value: null})
onMounted(()=>{
console.log('onMounted',box.value);
})
console.log(box.value); // null
return {
box};
}
}
</script>
<style>
</style>
19. readonly 家族
readonly
- 用于创建一个
只读
的数据,并且是递归只读
- 用于创建一个
shallowReadonly
- 用于创建一个
只读
的数据,但是不是递归只读的
- 用于创建一个
isReadonly
判断是否是一个只读的数据
const 和 readonly 区别
const
:赋值保护
,不能给变量重新赋值
readonly
:属性保护
,不能给属性重新赋值
<template>
<div>
<p>{
{state.name}}</p>
<p>{
{state.attr.age}}</p>
<p>{
{state.attr.height}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {
readonly, isReadonly, shallowReadonly } from 'vue'
export default {
name: 'App',
setup() {
// let state = readonly({name: 'lnj', attr:{age:18, height: 1.88}});
// let state = shallowReadonly({name:'lnj', attr:{age:18,height: 1.88}});
// const value = 345; 只读
const value = {
name: 'zs', age:123};
function myFn(){
state.name = 'zx';
state.attr.age = 123;
state.attr.height = 1.78;
console.log(state);
consloe.log(isReadonly(state));
// value = 456;
// console.log(value);
value.name = 'ls';
value.age = 456;
console.log(value); // 能改变值
}
return {
state, myFn};
}
}
</script>
<style>
</style>
20. Vue 3.0 响应式数据本质
20.1 Vue 3.0 响应式数据本质
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); // lnj
state.name = 'zx';
console.log(state);
20.2 Proxy 注意点
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);
21. 手写 shallowReactive——shallowRef 理解
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
}
}
}
}
22. 手写 reactive-ref 理解
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 = 666
state[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 = 666
state[1].id = 3
23. 手写 readonly-shallowReadonly 理解
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 = 1
state.gf.b = 2