[TOC]
关联响应式数据:
- 简单响应式数据(常用):ref(也可以监听复杂数据)
- 复杂响应式数据(常用):reactive
- 非递归监听(场景:数据量大时):shallowRef & shallowReactive
- 数据不会被追踪, 且不会更新ui界面:toRaw & markRaw
- 数据会被追踪,但不更新ui:toRef & toRefs
- 创建递归只读数据:readonly
- 创建非递归只读数据:shallReadonly
响应式数据本质 手写Proxy
```javascript let json = { name: “zs”, age: “5” }; let arr = [1, 3, 5];
let state = new Proxy(arr, { //obj:传入的对象,key:对象的key或数组索引 get(obj, key) { console.log(‘get’, obj, key) return obj[key] }, //value:修改对应obj[key]的值 set(obj, key, value) { obj[key] = value; console.log(obj, key, value) return true; //告诉每一次修改成功 } }); //测试json //state.name = “ww”; //测试arr state[2] = 2; state[3] = 6;
<a name="PtF0u"></a>
## setup
1. 理解:Vue3.0中一个新的配置项,值为一个函数。
1. setup是所有**Composition API(组合API)**“ 表演的舞台 ”。
1. 组件中所用到的:数据、方法等等,均要配置在setup中。
1. setup函数的两种返回值:
1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
1. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
5. 注意点:
1. 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中**可以访问到**setup中的属性、方法。
- 但在setup中**不能访问到**Vue2.x配置(data、methos、computed...)。
- 如果有重名, setup优先。
2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
```vue
<template>
<div>
<p>{{count}}</p>
<button @click="addCount">click</button>
<ul>
<li
v-for="(item,index) in state.stus"
@click="deletestu(index)"
:key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script>
//简单变量用ref(也可以用于复杂数据)
import {ref} from 'vue'
//复杂数据用reactive
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//定义变量
let count = ref(0);
//定义方法
function addCount(){
count.value+=1;
}
//定义复杂数组reactive会将其渲染为Proxy
let state=reactive({
stus:[
{id:"1",name:"zs",age:"12"},
{id:"2",name:"ls",age:"41"},
{id:"3",name:"ww",age:"54"}
]
})
//定义方法删除数组
function deletestu(index){
state.stus = state.stus.filter((stu,idx)=>idx!==index)
}
//暴露变量和方法
return{count,addCount,state,deletestu}
}
}
</script>
抽离出数据和方法
setup中抽离出来
<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="(item,index) in state.stus"
@click="deletestu(index)"
:key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script>
//复杂数据用reactive
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//引入
let {state,deletestu} = userDeleteStu();
let {state2,addStu} = userAddStu(state);
//暴露变量和方法
return{state2,addStu,state,deletestu}
}
}
function userAddStu(state){
//变量添加学生的form
let state2=reactive({
stu:[{
id:"",
name:"",
age:""
}]
});
//添加学生方法
function addStu(e){
e.preventDefault();
const stu = Object.assign({},state2.stu);
state.stus.push(stu)
}
return{state2,addStu}
};
function userDeleteStu(){
//定义复杂数组reactive会将其渲染为Proxy
let state=reactive({
stus:[
{id:"1",name:"zs",age:"12"},
{id:"2",name:"ls",age:"41"},
{id:"3",name:"ww",age:"54"}
]
})
//定义方法删除数组
function deletestu(index){
state.stus = state.stus.filter((stu,idx)=>idx!==index)
}
return{state,deletestu}
};
</script>
模块化功能
ref & reactive
reactive

<template>
<div>
<P>{{test1}}</P>
<P>{{test2.age}}</P>
<P>{{test3}}</P>
<P>{{test4.date}}</P>
<button @click="editTest">Edit</button>
</div>
</template>
<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
let test1 = reactive(123);
let test2 = reactive({age:123});
let test3 = reactive([1,2,3]);
let test4 = reactive({date:new Date()});
function editTest(){
test1 = 456; //不是(Json/Arry) 无法修改
test2.age=456; //可以修改成功
test3[0]=456; //可以修改成功, 会将数组渲染为Proxy
test4.date.setDate(test4.date.getDate()+1); //可以修改成功 也可以赋值修改
};
//暴露变量和方法
return{test1,test2,test3,test4,editTest}
}
}
</script>
ref
ref和reactive的区别
ref & reactive递归监听
手写ref&reactive
function ref(val) {
return reactive({ value: val })
};
//两种可能性
//数组中含对象: [{ name: "Dan", age: 12}, { name: "ivy", age: "18"} }];
//对象中含对象: {a:{ name: "Dan", age: 12},b: { name: "ivy", age: "18"} }};
function reactive(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
//判断数组中每一个元素是否为对象,是则递归也封装成proxy
obj.forEach((item, index) => {
if (typeof item === 'object') {
//此处不能写 item = reactive(item)必须是如下写法
//因为 let item是单独个体,我们的目的是把obj 封装状proxy
obj[index] = reactive(item)
}
})
} else {
//判断对象属性取值是否为对象,是则递归也封装成proxy
for (let key in obj) {
item = obj[key];
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
obj[key] = value;
console.log('ui..')
return true
}
})
} else {
console.warn('message:' + `$(obj) is not a object`)
};
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
};
// let state = reactive(obj);
// state.a = 1; //注释之后不console ui...
// state.gf.b = 2;
// state.gf.f.c = 3;
let obj2 = [{ name: "Dan", age: 12, arg: { color: "gary" } }, { name: "ivy", age: "18", arg: { color: "pink" } }];
// let state = reactive(obj2);
// state[0].name = 'Amanda';
// state[1].name = 'Jim';
// state[1].age.color = 'green';
// let state2 = ref(obj);
// state2.value.a = 1;
// state2.value.gf.b = 2;
// state2.value.gf.f.c = 3;
let state2 = ref(obj2);
console.log(state2)
state2.value[0].name = 'Amanda';
state2.value[1].name = 'Jim';
state2.value[1].age.color = 'green';
shallowRef & shallowReactive
shallowRef & shallowReactive非递归监听
shallowRef & triggerRef
<template>
<div>
<h4>{{state.a}}</h4>
<h4>{{state.gf.b}}</h4>
<h4>{{state.gf.f.c}}</h4>
<button @click="myfn">change</button>
</div>
</template>
<script>
import {shallowRef} from 'vue'
import {triggerRef} from 'vue'
export default {
name: 'App',
setup(){
let state = shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c'
}
}
});
function myfn (){
// state.value.a='1';
// state.value.gf.b='2';
// state.value.gf.f.c='3';
//只有state被封装其他未被封装,证明是非递归监听
//shallowRef监听的是.value的变化,此时只有state被包装此时界面不会发生改变
//因为shallowRef的本质是shallowReactive({value:{ a:'a',gf:{b:'b',f:{c:'c'}}}})
//所以修改.value界面才会发生变化( state.value={ a:'1',gf:{b:'2',f:{c:'3'}}} )
// console.log(state);//RefImpl {_rawValue: {…}, _shallow: true, __v_isRef: true, _value: {…}}
// console.log(state.value);//{a: "1", gf: {…}}
// console.log(state.value.gf);//{b: "2", f: {…}}
// console.log(state.value.gf.f);//{c: "3"}
//如果只想修改某一层数据,则需要triggerRef,此时只有3被修改
////state.value.a='1';
////state.value.gf.b='2';
state.value.gf.f.c='3';
triggerRef(state)
};
return {state,myfn}
}
}
</script>
shallowReactive
<template>
<div>
<h4>{{state.a}}</h4>
<h4>{{state.gf.b}}</h4>
<h4>{{state.gf.f.c}}</h4>
<button @click="myfn">change</button>
</div>
</template>
<script>
import {shallowReactive} from 'vue'
import {shallowRef} from 'vue'
import {triggerRef} from 'vue'
export default {
name: 'App',
setup(){
let state = shallowReactive({
a:'a',
gf:{
b:'b',
f:{
c:'c'
}
}
});
function myfn (){
//state.a='1';
state.gf.b='2';
state.gf.f.c='3';
//此时可以修改成功,只有第一层被封装成Proxy证明不是递归监听
//如果注释掉第一层//state.a='1'; 此时第23层无法修改成功
console.log(state) //Proxy {a: "a", gf: {…}}
console.log(state.gf) //{b: "2", f: {…}}
console.log(state.gf.f) //{c: "3"}
};
return {state,myfn}
}
}
</script>
手写shallowRef & shallowReactive
function shallowRef(val) {
return shallowReactive({ value: val })
};
function shallowReactive(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
obj[key] = value;
console.log('ui..')
return true
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
};
let state = shallowReactive(obj);
//state.a = 1; //注释之后不console ui...
//state.gf.b = 2;
//state.gf.f.c = 3;
let state2 = shallowRef();
//state2.value.a=1;
state2.value = {
a: '1',
gf: {
b: '2',
f: {
c: '3'
}
}
}
toRaw & markRaw
<template>
<div>
<h4>{{state}}</h4>
<button @click="myfn">change</button>
</div>
</template>
<script>
import{markRaw, ref}from 'vue';
import{reactive}from 'vue';
import {toRaw}from 'vue'
export default {
name: 'App',
setup(){
let obj = {name:"zs",age:"14"};
obj=markRaw(obj) //添加markRaw之后则不会被追踪, 界面obj.name="11"不会发生变化
let state = reactive(obj);
let state2 = ref(obj);
/**
* ref/reactive数据类型特点:
* 每次修改都会被追踪, 都会更新ui界面,但是这样很消耗性能
* 不需要修改ui界面时 使用toRawo方法拿到原始数据不被追踪 减少性能消耗
*/
let obj2 = toRaw(state)
let obj3 = toRaw(state2.value) //ref时要获取 .value
function myfn (){
//state.name="ll"; //数据发生变化, 界面发生改变
obj.name="11"; //state引用obj 数据发生变化, 界面不改变
//obj2.name = "22" //obj === obj2 数据发生变化, 界面不改变
console.log(obj === state) //false
console.log(obj === obj2) //true
console.log(state) //Proxy {name: "zs", age: "14"}
console.log(obj2) //{name: "zs", age: "14"}
};
return {state,myfn,obj,obj2,obj3}
}
}
</script>
toRef & toRefs

<template>
<div>
<!-- 界面一旦有一个会刷新ui界面 则都会刷新 所以注释掉 -->
<!-- <h4>{{state}}</h4> -->
<h4>{{state2}}</h4>
<button @click="myfn">change</button>
</div>
</template>
<script>
import{ref,toRef,toRefs}from 'vue';
export default {
name: 'App',
setup(){
let obj = {name:"zs",age:"14"};
let state = ref(obj.name)
let obj2 = {name:"zs",age:"14"};
//toRef只能绑定对象中一个 name
let state2 = toRef(obj2,'name')
let obj3 = {name:"zs",age:"14"};
//toRefs直接绑定一个对象
let state3 = toRefs(obj3)
function myfn (){
state.value='ls';
console.log(obj) //数据不变{name: "zs", age: "14"}
console.log(state) //RefImpl {_rawValue: "ls", _shallow: false, __v_isRef: true, _value: "ls"}
state2.name='ww'; //state2.value='ww';也可以修改
console.log(obj2)//数据变化{name: "ww", age: "14"}
console.log(state2) //ObjectRefImpl {_object: {…}, _key: "name", __v_isRef: true}
state3.name.value='zl';
state3.age.value='18';
console.log(obj3) //{name: "zl", age: "18"}
console.log(state3)
}
return {state,state2,myfn}
}
}
</script>
customRef
基本使用原理
配合调用接口
<template>
<div>
<ul>
<li v-for="item in state" :key="item.id">{{item.name}}</li>
</ul>
</div>
</template>
<script>
import{customRef}from 'vue';
function myRef(path){
return customRef((track,trigger)=>{
//调用文件获取数据
let value = [];
fetch(path)
.then((res)=>{
return res.json();
})
.then((data)=>{
value = data;
trigger()
})
.catch((err)=>{
console.log(err)
})
return {
get(){
/**
* 不能在get中发送网络请求因为
* 渲染界面 -> 调用get ->发送网络请求
* 保存数据-> 界面更新 -调用get
* 会一直循环一直调用get
*/
track();//告诉vue这个值要追踪变化
return value;
},
set(newValue){
value = newValue;
trigger();//告诉Vue触发界面更新
}
}
});
}
export default {
name: 'App',
setup(){
let state= myRef('../public/data.json')
return {state}
}
}
</script>
setup中使用声明周期函数 & ref获取Dom
- vue3.0中不能使用$ref获取Dom
- 使用什么声明周期函数都可以,引入就行
```vue
123
<a name="o7oZQ"></a>
## readonly & shallReadonly & isReadonly
```vue
<template>
<div>
<h4>{{state}}</h4>
<button @click="myfn">myfn</button>
</div>
</template>
<script>
//导入onMounted声明周期函数
import{readonly,shallowReadonly,isReadonly}from 'vue';
export default {
name: 'App',
setup(){
//readonly 创建一个只读数据 并且是递归只读
//let state = readonly({name:"zs",age:"14"});
//shallowReadonly 创建一个只读数据 非递归只读 arg数据会被修改
let state = shallowReadonly({name:"zs",age:"14",arg:{cloor:"red"}});
//const和readonly的区别:
//const:赋值保护 不能给变量重新赋值 但是可以修改 value.age
//readonly:属性保护 不能给属性重新赋值
const value = {age:"ll",age:"66"}
function myfn(){
state.name='ls';
state.age='16';
state.arg.cloor='blue';
console.log(state)
//isReadonly 判断是否为只读数据
console.log(isReadonly(state)) //true
};
return {state,myfn}
}
}
</script>
手写readonly & shallReadonly
//两种可能性
//数组中含对象: [{ name: "Dan", age: 12}, { name: "ivy", age: "18"} }];
//对象中含对象: {a:{ name: "Dan", age: 12},b: { name: "ivy", age: "18"} }};
function readonly(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
//判断数组中每一个元素是否为对象,是则递归也封装成proxy
obj.forEach((item, index) => {
if (typeof item === 'object') {
//此处不能写 item = readonly(item)必须是如下写法
//因为 let item是单独个体,我们的目的是把obj 封装状proxy
obj[index] = readonly(item)
}
})
} else {
//判断对象属性取值是否为对象,是则递归也封装成proxy
for (let key in obj) {
item = obj[key];
if (typeof item === 'object') {
obj[key] = readonly(item)
}
}
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
//set不给数据 实现递归只读
console.warn('message:' + `${key}是只读的不能赋值`)
}
})
} else {
console.warn('message:' + `$(obj) is not a object`)
};
}
function shallReadonly(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
//set不给数据 实现递归只读
console.warn('message:' + `${key}是只读的不能赋值`)
}
})
}
let obj = {
a: 'a',
gf: {
b: 'b',
f: {
c: 'c'
}
}
};
let state = readonly(obj);
// state.a = 1;
// state.gf.b = 2;
// state.gf.f.c = 3;
let obj2 = [{ name: "Dan", age: 12, arg: { color: "gary" } }, { name: "ivy", age: "18", arg: { color: "pink" } }];
// let state = readonly(obj2);
// state[0].name = 'Amanda';
// state[1].name = 'Jim';
// state[1].age.color = 'green';
//let state2 = shallReadonly(obj);
//state2.a = 1;
// state2.gf.b = 2;
// state2.gf.f.c = 3;
let state2 = shallReadonly(obj2);
// state2[0] = { name: "zs" }
// state2[0].age = { name: "ls" }



