问题

在初次使用vue3的时候,页面上有一个点击按钮,当点击按钮的时候页面数据发生相应的改变。

在这里插入图片描述

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    let data = {
      num: 0,
    };

    let say = "hello";

    function add() {
      data.num++;
      say = "Vue3";
    }

    return { data, add, say };
  },
};

然而无论怎么点击也无法实现效果
在这里插入图片描述

原因是vue3中的数据响应式需要引入vue内置的方法实现


1 ref函数

vue3中提供了ref来创建基本数据的响应式,

// 引入ref函数
import { ref } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 使用ref接收数据参数, 返回响应处理的data数据

    let data = ref({
      num: 0,
      o: ["1", 2],
    });

    let say = ref("hello");

    function add() {
      
      data.value.num++;
      
      data.value.o[data.value.o.length] = "4";
      
      say.value = "Vue3";

      console.log(data.value);
    }

    return { data, add, say };
  },
};

注意:使用ref函数的话,在setup中获取数据需要.value属性

在这里插入图片描述

可以发现, 数据已经是响应式了

这里先暂停一下,我们先去看一下经过ref包装之后的数据是什么样的?

import { ref } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
	// 使用ref接收数据参数, 返回响应处理的data数据

    let data = ref({
      num: 0,
      o: ["1", 2],
    });

    let say = ref("hello");

    function add() {
   	  data.value.num++;
      
      data.value.o[data.value.o.length] = "4";
      
      say.value = "Vue3";

      console.log(data.value);
    }

    return { data, add, say };
  },
};
  • 经过ref函数包装 之后,会为包装的数据进行一个(Proxy)代理,包装一个响应式对象,对象中创建一个属性value,保存着传入的数据。
  • 当包装返回的实例属性value发生改变的时候(定义的响应式数据改变),便会被代理检测,检测到数据变化则会重新触发渲染
  • 当使用ref包装一个引用数据的时候,ref会使用reactive函数, 它会将引用数据对象中的所有属性(包括深层属性)创建ref,同时为每一个包装的实例添加value属性,这个value保存的也就是对应的属性值,ref也就实现了引用数据类型的响应式

这里打印经过包装的引用类型数据

Proxy {num: 0}

2 reactive

reactive可以处理数据响应式问题

和ref不同的是,reactive可以深层代理数据

import { ref, reactive } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
	// 使用ref接收数据参数, 返回响应处理的data数据

      let data = reactive({
          num: 0,
          o: ["1", 2],
      });

    let say = ref("hello");

    function add() {
   	  data.num++;
      
      data.o[data.o.length] = "4";
      
      say.value = "Vue3";

      console.log(data);
    }

    return { data, add, say };
  },
};

在这里插入图片描述
reactive为传入的数据包装ref,生成value,利用Proxy代理检测数据变化。

注意:一般引用数据类型添加响应式使用reactive, 基本数据类型使用ref


响应是数据与源数据引用问题:

  • ref
    根据数据类型保持与源数据的引用关系来决定源数据是否改变(基本数据不保持引用关系,引用数据保持引用关系),即修改ref返回的基本类型数据不会改变源数据,但是修改引用乐行数据会改变源数据
// 引入ref函数
import { ref } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      },
      str = "hello";

    // 包装ref
    let refData = ref(data),
      dataStr = ref(str);

    function add() {
      // 改变两种类型数据的响应式
      refData.value.num++;

      refData.value.o.push("233");

      refData.value.job.wage += 1000;

      dataStr.value = "Vue3!";
    }

    // 返回响应数据和响应数据
    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述

源数据中,对象源数据发生改变,而基本数据类型数据没有改变

  • reactive
    因为在ref中代理对象时是引用Reactive方法,所以最终reactive代理展现的效果与ref代理对象的效果无异(reactive代理基本数据类型也不会修改源数据,代理引用数据类型会修改源数据)

  • 两种情况本质上就是数据类型决定源数据的改变


3 toRef

创建一个ref对象,这个响应式对象的value引用了接收的对象的某个响应式属性,且与源数据保持引用关系

// 引入ref函数
import { ref, toRef, reactive } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
      },
      str = "hello";

    // 为数据添加响应式
    let refData = reactive(data);

    // 创建一个ref对象,这个对象的value保持对传入对象属性的引用
    // 当想要单独提取出响应式对象中的响应式属性时可以使用
    let dynamicProp = toRef(refData, "o");

    let say = ref(str);

    function add() {
      data.num++;

      dynamicProp.value[dynamicProp.value.length] = "4";

      say.value = "Vue3";

      console.log(data.o, str);
      // 通过检测发现,使用ref定义的响应式数据如果是基本数据类型,
      // 那么定义后的数据不保持对源数据的引用.
      // 如果定义的是引用数据类型,那么将会保持对对源数据的引用
      // 使用toRef后, toRef包装后的value属性保持对源数据的引用,修改响应数据后
      // 源数据也会发生改变
    }

    return { data, add, say, dynamicProp };
  },
};

在这里插入图片描述

4 toRefs

和toRefs功能差不多,只不过toRefs可以创建多个ref对象
因为当有多个响应式对象需要单独提取的时候,toRef就显得过于复杂了,使用toRefs可以简化这个过程

import { toRefs, reactive } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
      num: 0,
      o: ["1", 2],
      job: {
        myJob: "web",
        wage: 2000,
      },
    };

    let refData = reactive(data);

    // 为每一个属性都添加value引用,
    // 通过结构获取到想要的ref对象,
    // 这个时候既拿到了响应数据,源数据因为引用关系而改变。
    let { o, num, job } = toRefs(refData);

    function add() {
      num.value++;

      o.value.push("233");

      job.value.wage += 1000;
    }

    return { add, o, num, job, data };
  },
};

在这里插入图片描述

单独拿到了响应数据了,且源数据发生了改变


5 shallowRef

特殊的ref,只处理基本数据类型的响应式,无法为引用数据类型添加响应式

  • 添加基本数据
// 引入ref函数
import { reactive, ref, shallowRef } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      },
      str = "hello";

    let refData = shallowRef(data),
      dataStr = shallowRef(str);

    function add() {
      // refData.value.num++;

      // refData.value.o.push("233");

      // refData.value.job.wage += 1000;

	  // 稍微更改了添加规则
      dataStr.value += " Vue3!";
    }

    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述

  • 添加引用数据
export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      },
      str = "hello";

    let refData = shallowRef(data),
      dataStr = shallowRef(str);

    function add() {
      refData.value.num++;

      refData.value.o.push("233");

      refData.value.job.wage += 1000;
    }

    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述

注意当同时改变的响应式数据有基本数据和引用数据时,页面也会触发更新


6 shallowReactive

只为对象的第一层属性添加响应式

  • 当改变对象响应数据最外层时
export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      },
      str = "hello";

    let refData = shallowReactive(data),
      dataStr = shallowRef(str);

    function add() {
      // 第一层的数据
      refData.num++;

      // 不是第一层的数据
      // refData.o.push("233");

      // refData.job.wage += 1000;
    }

    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述

  • 当改变响应数据非最外层时
// 引入ref函数
import { shallowReactive, shallowRef } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      },
      str = "hello";

    let refData = shallowReactive(data),
      dataStr = shallowRef(str);

    function add() {
      // 第一层的数据
      // refData.num++;

      // 不是第一层的数据
      refData.o.push("233");

      refData.job.wage += 1000;
    }
    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述

当同时改变的是最外层数据,和非最外层数据时会触发页面更新


7 triggerRef

ref生成的数据强制在页面更新页面

前面在使用shallowRef添加响应对象时,数据不能更新到页面,使用triggerRef便可以强制更新

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      };

    let refData = shallowRef(data);

    function add() {
      refData.value.num++;

      refData.value.o.push("233");

      refData.value.job.wage += 1000;

      triggerRef(refData);
    }

    return { add, refData, data, dataStr, str };
  },
};

在这里插入图片描述
页面视图发生了更新


8 readony

让一个响应式数据变为深只读

<template>
  <div>
    <button @click="add">点击数据将会发生改变</button>
    <p>响应式数据:{{ readonlyData }}</p>
  </div>
</template>

<script>
// 引入ref函数
import { readonly, shallowReadonly, reactive } from "vue";

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
      num: 0,
      o: ["1", 2],
      job: {
        myJob: "web",
        wage: 2000,
      },
    };

    let refData = reactive(data);

    // 将响应式数据添加只读
    let readonlyData = readonly(refData);

    function add() {
      // 更改只读的响应式数据
      readonlyData.num++;

      readonlyData.o.push("233");

      readonlyData.job.wage += 1000;
    }
    return { add, refData, data, readonlyData };
  },
};
</script>

在这里插入图片描述

无论是浅层还是深层响应式数据都只能读,不能修改


9 shallowReadony

让一个响应式数据变为浅只读

export default {
  name: "HelloWorld",

  props: {
    msg: String,
  },

  setup(props) {
    // 一个普通的数据
    let data = {
      num: 0,
      o: ["1", 2],
      job: {
        myJob: "web",
        wage: 2000,
      },
    };

    let refData = reactive(data);

    // 将响应式数据添加只读
    let shallowReadonlyData = shallowReadonly(refData);

    function add() {
      // 更改只读的响应式数据
      shallowReadonlyData.num++;

      shallowReadonlyData.o.push("233");

      shallowReadonlyData.job.wage += 1000;
    }
    return { add, refData, data, shallowReadonlyData };
  },
};

点击前
在这里插入图片描述
点击后
在这里插入图片描述

发现只有浅层数据有只读限制,深层数据并未设置只读,蓝色区域数据发生改变


10 toRaw

将响应式数据变更为非响应式数据(基础数据)

export default {
  name: "HelloWorld",

  setup(props) {
    let reData = reactive({
        num: 0,
        o: ["1", 2],
        job: {
          myJob: "web",
          wage: 2000,
        },
      }),
      dataStr = reactive("hello");

    let strRaw = toRaw(dataStr),
      dataRaw = toRaw(reData);

    // 从打印可以看出只有使用reactive创建的响应式数据才能转换为普通数据
    // 使用ref创建的响应式数据转换后还是ref对象
    console.log(dataRaw, strRaw);
    //  job: {myJob: 'web', wage: 2000}
    // num: 0
    // o: (2) ['1', 2]

   // 'hello'

    function add() {
      dataRaw.num++;
      dataRaw.o.push("233");
      dataRaw.job.wage += 1000;
      strRaw = "vue3";

      console.log("reData: ", reData);
      console.log("dataStr: ", strRaw);
    }
    return { add, dataRaw, strRaw };
  },
};

点击前
在这里插入图片描述
点击后
在这里插入图片描述

页面数据没有更新,但是检测的去响应数据发生了改变
如果比较源数据与转换后的非响应数据,我们会发现它们是一个数据。


11 markRaw

将响应式数据永久变更为非响应式数据

当有一段数据,都是响应式的,现需要添加一段展示数据不需要添加响应式,为了避免性能浪费,可以使用markRaw来永久改变某个数据为非响应式

<template>
  <div>
    <button @click="add">添加一段非响应式数据</button>
    <button @click="upDate">更新响应式数据</button>
    <button @click="reData.job.addProp.msg = '更新了'">更新添加的数据</button>

    <p>响应数据:{{ reData }}</p>
  </div>
</template>

<script>
// 引入ref函数
import { reactive, markRaw } from "vue";

export default {
  name: "HelloWorld",

  setup(props) {
    let reData = reactive({
      num: 0,
      job: {
        myJob: "web",
        wage: 2000,
      },
    });

    // 更新数据
    function upDate() {
      reData.num++;
      reData.job.myJob = "www";
      reData.job.wage += 100;
    }

    // 添加数据
    function add() {
      let newData = {msg: "这是添加的数据"}
      reData.job["addProp"] = newData;
    }

    return { upDate, add, upDataAdd, reData };
  },
};
</script>

在这里插入图片描述

后添加的数据会成为响应式数据

此时需要使用markRaw将响应数据转为普通数据

 // 添加数据
    function add() {
      let newDate = markRaw({ msg: "这是添加的数据" });

      reData.job["addProp"] = newDate;

      console.log(newDate);
    }

在这里插入图片描述

数据无法响应了


12 customRef

自定义ref, 通过基础ref配置满足自定义要求的ref

<template>
  <div>
    <input type="text" v-model="delayWord" />添加数据

    <p>响应数据:{{ delayWord }}</p>
  </div>
</template>

<script>
// 引入自定义ref函数
import { customRef } from "vue";

export default {
  name: "HelloWorld",

  setup() {
    // 创建一初始数据
    let hotWord = "初始数据";

    // 定义自己的ref ---
    function myRef(value, delay) {
      // 定时器名称
      let timer;

      // 返回一个customRef
      // 相当于一个只有响应式功能的基础ref
      // 接收一个回调函数
      // 回调函数接收两个参数 track, trigger. 返回一个对象
      // 参数 track ---> 通知getter监视返回的数据
      // 参数 trigger ---> 通知set更改数据后去更新视图

      return customRef((track, trigger) => {
        return {
          get() {
            console.log("获取了" + value);

            // 通知检测返回值
            track();
            return value;
          },
          set(newValue) {
            console.log("设置了: " + value + "\t新值为: " + newValue);

            // 清除定时器 --- 用于节流
            clearTimeout(timer);

            // 更改数据
            value = newValue;

            timer = setTimeout(() => {
              // 通知更新视图
              trigger();
            }, delay);
          },
        };
      });
    }

    let delayWord = myRef(hotWord, 1000);

    return { delayWord };
  },
};
</script>

效果
在这里插入图片描述

注意customRef参数的作用


13 补充

检测响应式数据的方法

isRef: 检查一个值是否为一个 ref 对象

isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

Logo

前往低代码交流专区

更多推荐