[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>

模块化功能

image.png

ref & reactive

reactive

image.png

<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

image.png
image.png

ref和reactive的区别

image.png
image.png

ref & reactive递归监听

image.png
修改内容时,界面会改变
image.png

手写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非递归监听

image.png

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

image.png

<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

基本使用原理

image.png

配合调用接口

<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

  1. vue3.0中不能使用$ref获取Dom
  2. 使用什么声明周期函数都可以,引入就行 ```vue
<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" }