Composition-API

201 阅读2分钟

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