reactive
<template>
<div>
<p>{{state.time}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.什么是reactive?
- reactive是Vue3中提供的实现响应式数据的方法
- 在Vue2中响应式数据是通过defineProperty来实现的
而在Vue3中响应式数据是通过ES6的Proxy来实现的
2.reactive注意点:
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其它对象
+ 默认情况下修改对象, 界面不会自动更新
+ 如果想更新, 可以通过重新赋值的方式
* */
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>
ref
<template>
<div>
<!-- <p>{{state.age}}</p>-->
<!--
注意点:
如果是通过ref创建的数据, 那么在template中使用的时候不用通过.value来获取
因为Vue会自动给我们添加.value
-->
<p>{{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.什么是ref?
- ref和reactive一样, 也是用来实现响应式数据的方法
- 由于reactive必须传递一个对象, 所以导致在企业开发中
如果我们只想让某个变量实现响应式的时候会非常麻烦
所以Vue3就给我们提供了ref方法, 实现对简单值的监听
2.ref本质:
- ref底层的本质其实还是reactive
系统会自动根据我们给ref传入的值将它转换成
ref(xx) -> reactive({value:xx})
3.ref注意点:
- 在Vue中使用ref的值不用通过value获取
- 在JS中使用ref的值必须通过value获取
* */
// 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>
ref和reactive区别
<template>
<div>
<!--
ref和reactive区别:
如果在template里使用的是ref类型的数据, 那么Vue会自动帮我们添加.value
如果在template里使用的是reactive类型的数据, 那么Vue不会自动帮我们添加.value
Vue是如何决定是否需要自动添加.value的
Vue在解析数据之前, 会自动判断这个数据是否是ref类型的,
如果是就自动添加.value, 如果不是就不自动添加.value
Vue是如何判断当前的数据是否是ref类型的
通过当前数据的__v_ref来判断的
如果有这个私有的属性, 并且取值为true, 那么就代表是一个ref类型的数据
-->
<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>
递归监听
<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>
/*
1.递归监听
默认情况下, 无论是通过ref还是reactive都是递归监听
2.递归监听存在的问题
如果数据量比较大, 非常消耗性能
3.非递归监听
* */
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>
非递归监听
<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>
/*
1.递归监听存在的问题
如果数据量比较大, 非常消耗性能
2.非递归监听
shallowRef / shallowReactive
3.如何触发非递归监听属性更新界面?
如果是shallowRef类型数据, 可以通过triggerRef来触发
4.应用场景
一般情况下我们使用 ref和reactive即可
只有在需要监听的数据量比较大的时候, 我们才使用shallowRef/shallowReactive
* */
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>
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>
toRaw
在 reactive 的应用
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.toRaw
从Reactive 或 Ref中得到原始数据
2.toRaw作用
做一些不想被监听的事情(提升性能)
* */
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>
在 ref 的应用
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
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>
markRaw
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.markRaw
将数据标记为永远不能追踪的数据
一般在编写自己的第三方库时使用
* */
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>
toRef
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.toRef
创建一个ref类型数据, 并和以前的数据关联
2.toRefs
批量创建ref类型数据, 并和以前数据关联
3.toRef和ref区别
ref-创建出来的数据和以前无关(复制)
toRef-创建出来的数据和以前的有关(引用)
ref-数据变化会自动更新界面
toRef-数据变化不会自动更新界面
* */
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>
toRefs
<template>
<div>
<p>{{state}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.toRef
创建一个ref类型数据, 并和以前的数据关联
2.toRefs
批量创建ref类型数据, 并和以前数据关联
3.toRef和ref区别
ref-创建出来的数据和以前无关(复制)
toRef-创建出来的数据和以前的有关(引用)
ref-数据变化会自动更新界面
toRef-数据变化不会自动更新界面
* */
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>
customRef 上
<template>
<div>
<p>{{age}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.customRef
返回一个ref对象,可以显式地控制依赖追踪和触发响应
* */
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>
<ul>
<li v-for="item in state" :key="item.id">{{item.name}}</li>
</ul>
</template>
<script>
/*
1.customRef
返回一个ref对象,可以显式地控制依赖追踪和触发响应
* */
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>
customRef 下
<template>
<ul>
<li v-for="item in state" :key="item.id">{{item.name}}</li>
</ul>
</template>
<script>
/*
1.customRef
返回一个ref对象,可以显式地控制依赖追踪和触发响应
* */
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>
通过 ref 获取 Dom 元素
<template>
<div ref="box">我是div</div>
</template>
<script>
/*
1.获取元素
在Vue2.x中我们可以通过给元素添加ref='xxx',
然后再代码中通过refs.xxx的方式来获取元素
在Vue3.x中我们也可以通过ref来获取元素
* */
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>
readonly 家族
<template>
<div>
<p>{{state.name}}</p>
<p>{{state.attr.age}}</p>
<p>{{state.attr.height}}</p>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
/*
1.readonly
- 只读数据
2.readonly和const
- const 赋值保护
- readonly 递归保护
3.isReadonly
- 判断是否是readonly
4.shallowReadonly
- 非递归保护
* */
import {readonly, isReadonly, shallowReadonly} from 'vue'
export default {
name: 'App',
setup() {
// readonly:用于创建一个只读的数据, 并且是递归只读
let state = readonly({name:'lnj', attr:{age:18, height: 1.88}});
// shallowReadonly: 用于创建一个只读的数据, 但是不是递归只读的
// let state = shallowReadonly({name:'lnj', attr:{age:18, height: 1.88}});
// const和readonly区别:
// const: 赋值保护, 不能给变量重新赋值
// readonly: 属性保护, 不能给属性重新赋值
// const value = 123;
const value = {name:'zs', age:123};
function myFn() {
state.name = '知播渔';
state.attr.age = 666;
state.attr.height = 1.66;
console.log(state);
console.log(isReadonly(state));
// value = 456;
// console.log(value);
value.name = 'ls';
value.age = 456;
console.log(value);
}
return {state, myFn};
}
}
</script>
<style>
</style>
Vue3响应式数据本质
/*
1.Vue3响应式数据本质
- 在Vue2.x中是通过defineProperty来实现响应式数据的
详见: 手写Vue全家桶视频
- 在Vue3.x中是通过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 知播渔
obj[key] = value;
console.log('更新UI界面');
}
});
// console.log(state.name); // lnj
state.name = '知播渔';
console.log(state);
/*
1.Proxy注意点
- set方法必须通过返回值告诉Proxy此次操作是否成功
* */
// let obj = {name:'lnj', age:18};
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, shallowRef
/*
1.shallowReactive, shallowRef
2.shallowReadonly
3.reactive, ref
4.readonly
* */
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 = shallowReactive(obj);
// state.a = '1';
state.gf.b = '2';
state.gf.f.c = '3';
state.gf.f.s.d = '4';
*/
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 ref
/*
1.shallowReactive, shallowRef
2.shallowReadonly
3.reactive, ref
4.readonly
* */
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 = shallowReactive(obj);
// state.a = '1';
state.gf.b = '2';
state.gf.f.c = '3';
state.gf.f.s.d = '4';
*/
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
}
}
}
}