获取响应式数据

API 传入 返回 备注
reactive plain-object 对象代理 深度代理对象中的所有成员
readonly plain-object or proxy 对象代理 只能读取代理对象中的成员,不可修改
ref any { value: ... } 对value的访问是响应式的如果给value的值是一个对象,则会通过reactive函数进行代理如果已经是代理,则直接使用代理
computed function { value: ... } 当读取value值时,会根据情况决定是否要运行函数

应用:
- 如果想要让一个对象变为响应式数据,可以使用reactiveref

  • 如果想要让一个对象的所有属性只读,使用readonly
  • 如果想要让一个非对象数据变为响应式数据,使用ref
  • 如果想要根据已知的响应式数据得到一个新的响应式数据,使用computed

介绍表格中四种响应式Api

vue3中的数据响应格式就两种

  • proxy
  • ref object

    proxy

    reactive

    使用es6的代理实现数据的响应式
    但是es6d的代理只能代理对象, 原始数据就无法实现响应式
    深度代理:即使里面嵌套啦对象,嵌套对象里的属性也是响应式

readonly

传入参数:对象
使用es6的代理实现数据的响应式,只是使用es6代理下getter,所以才能只读
让一个对象的所有属性只读
但是传入的参数为代理,如下面这样
readonly > reactive > obj 如果是这样的话,
readonly无法改变数据
可以在reactive处修改数据,这样再readonly读取数据时就会发生变化
深度代理:即使里面嵌套啦对象,嵌套对象里的属性也是响应式
返回一个新的代理
readonly != reactive

ref object

ref

传入参数:任何数据
让数据成为响应式, 他会将数据放到一个对象中value属性中
即使是原始数据也会成变为响应式
如果给value的值是一个对象,则会通过reactive函数进行代理
如果已经是代理,则直接使用代理

computed

传入参数:函数
当读取值会根据情况运行函数
当连续调用且没有修改值,只会运行一次,因为会有缓存

案例

1.下面的代码输出结果是什么?

  1. import { reactive, readonly, ref, computed } from "vue";
  2. const state = reactive({
  3. firstName: "Xu Ming",
  4. lastName: "Deng",
  5. });
  6. const fullName = computed(() => {
  7. console.log("changed");
  8. return `${state.lastName}, ${state.firstName}`;
  9. });
  10. console.log("state ready");
  11. console.log("fullname is", fullName.value);
  12. console.log("fullname is", fullName.value);
  13. const imState = readonly(state);
  14. console.log(imState === state);
  15. const stateRef = ref(state);
  16. console.log(stateRef.value === state);
  17. state.firstName = "Cheng";
  18. state.lastName = "Ji";
  19. console.log(imState.firstName, imState.lastName);
  20. console.log("fullname is", fullName.value);
  21. console.log("fullname is", fullName.value);
  22. const imState2 = readonly(stateRef);
  23. console.log(imState2.value === stateRef.value);
  24. /**
  25. * 输出结果
  26. * state ready
  27. * changed
  28. * fullname is Deng, Xu Ming
  29. * fullname is Deng, Xu Ming
  30. * false
  31. * true
  32. * Cheng Ji
  33. * changed
  34. * fullname is Ji, Cheng
  35. * fullname is Ji, Cheng
  36. * false
  37. */

2.按照下面的要求完成函数

  1. function useUser(){
  2. // 在这里补全函数
  3. return {
  4. user, // 这是一个只读的用户对象,响应式数据,默认为一个空对象
  5. setUserName, // 这是一个函数,传入用户姓名,用于修改用户的名称
  6. setUserAge, // 这是一个函数,传入用户年龄,用户修改用户的年龄
  7. }
  8. }

答案:

  1. import { readonly, reactive } from "vue";
  2. function useUser() {
  3. // 在这里补全函数
  4. const userOrigin = reactive({});
  5. const user = readonly(userOrigin);
  6. const setUserName = (name) => {
  7. userOrigin.name = name;
  8. };
  9. const setUserAge = (age) => {
  10. userOrigin.age = age;
  11. };
  12. return {
  13. user, // 这是一个只读的用户对象,响应式数据,默认为一个空对象
  14. setUserName, // 这是一个函数,传入用户姓名,用于修改用户的名称
  15. setUserAge, // 这是一个函数,传入用户年龄,用户修改用户的年龄
  16. };
  17. }

3.按照下面的要求完成函数

  1. function useDebounce(obj, duration){
  2. // 在这里补全函数
  3. return {
  4. value, // 这里是一个只读对象,响应式数据,默认值为参数值
  5. setValue // 这里是一个函数,传入一个新的对象,需要把新对象中的属性混合到原始对象中,混合操作需要在duration的时间中防抖
  6. }
  7. }

答案:

  1. import { reactive, readonly } from "vue";
  2. function useDebounce(obj, duration) {
  3. // 在这里补全函数
  4. const valueOrigin = reactive(obj);
  5. const value = readonly(valueOrigin);
  6. let timer = null;
  7. const setValue = (newValue) => {
  8. clearTimeout(timer);
  9. timer = setTimeout(() => {
  10. console.log("值改变了");
  11. Object.entries(newValue).forEach(([k, v]) => {
  12. valueOrigin[k] = v;
  13. });
  14. }, duration);
  15. };
  16. return {
  17. value, // 这里是一个只读对象,响应式数据,默认值为参数值
  18. setValue, // 这里是一个函数,传入一个新的对象,需要把新对象中的属性混合到原始对象中,混合操作需要在duration的时间中防抖
  19. };
  20. }

监听数据变化

watchEffect

  1. const stop = watchEffect(() => {
  2. // 该函数会立即执行,然后追中函数中用到的响应式数据,响应式数据变化后会再次执行
  3. // 该函数中用到的响应式数据,变化后会再次执行
  4. })
  5. // 通过调用stop函数,会停止监听
  6. stop(); // 停止监听

watch

  1. // 等效于vue2的$watch
  2. // 监听单个数据的变化
  3. const state = reactive({ count: 0 })
  4. watch(() => state.count, (newValue, oldValue) => {
  5. // ...
  6. }, options)
  7. const countRef = ref(0);
  8. watch(countRef, (newValue, oldValue) => {
  9. // ...
  10. }, options)
  11. // 监听多个数据的变化
  12. watch([() => state.count, countRef], ([new1, new2], [old1, old2]) => {
  13. // ...
  14. });

注意:无论是watchEffect还是watch,当依赖项变化时,回调函数的运行都是异步的(微队列)
应用:除非遇到下面的场景,否则均建议选择watchEffect

  • 不希望回调函数一开始就执行
  • 数据改变时,需要参考旧值
  • 需要监控一些回调函数中不会用到的数据

    案例

    1.下面的代码输出结果是什么

    ```javascript import { reactive, watchEffect, watch } from “vue”; const state = reactive({ count: 0, }); watchEffect(() => { console.log(“watchEffect”, state.count); }); watch( () => state.count, (count, oldCount) => { console.log(“watch”, count, oldCount); } ); console.log(“start”); setTimeout(() => { console.log(“time out”); state.count++; state.count++; }); state.count++; state.count++;

console.log(“end”); /**

  • 输出结果
  • watchEffect 0
  • start
  • end
  • watchEffect 2
  • watch 2 0
  • time out
  • watchEffect 4
  • watch 4 2 */ ```

    判断

API 含义
isProxy 判断某个数据是否是由reactivereadonly
isReactive 判断某个数据是否是通过reactive创建的详细:https://v3.vuejs.org/api/basic-reactivity.html#isreactive
isReadonly 判断某个数据是否是通过readonly创建的
isRef 判断某个数据是否是一个ref对象

转换

unref

等同于:isRef(val) ? val.value : val
应用:

  1. function useNewTodo(todos){
  2. todos = unref(todos);
  3. // ...
  4. }

toRef

得到一个响应式对象某个属性的ref格式

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const fooRef = toRef(state, 'foo'); // fooRef: {value: ...}
  6. fooRef.value++
  7. console.log(state.foo) // 2
  8. state.foo++
  9. console.log(fooRef.value) // 3

toRefs

把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object中返回

  1. const state = reactive({
  2. foo: 1,
  3. bar: 2
  4. })
  5. const stateAsRefs = toRefs(state)
  6. /*
  7. stateAsRefs: not a proxy
  8. {
  9. foo: { value: ... },
  10. bar: { value: ... }
  11. }
  12. */

应用:

  1. setup(){
  2. const state1 = reactive({a:1, b:2});
  3. const state2 = reactive({c:3, d:4});
  4. return {
  5. ...state1, // lost reactivity 失去响应式 此处展开后为: a:1,b:2 这可不是响应式数据
  6. ...state2 // lost reactivity 失去响应式
  7. }
  8. }
  9. setup(){
  10. const state1 = reactive({a:1, b:2});
  11. const state2 = reactive({c:3, d:4});
  12. return {
  13. ...toRefs(state1), // reactivity 此处展开后为:{a:refObj, b:refObj}
  14. ...toRefs(state2) // reactivity
  15. }
  16. }
  17. // composition function
  18. function usePos(){
  19. const pos = reactive({x:0, y:0});
  20. return pos;
  21. }
  22. setup(){
  23. const {x, y} = usePos(); // lost reactivity 没有响应式
  24. const {x, y} = toRefs(usePos()); // reactivity
  25. }

降低心智负担

这两种响应式数据格式总会让人混淆,故使用下面的方法
所有的composition function均以ref的结果返回,以保证setup函数的返回结果中不包含reactivereadonly直接产生的数据

示例:

  1. function usePos(){
  2. const pos = reactive({ x:0, y:0 });
  3. return toRefs(pos); // {x: refObj, y: refObj}
  4. }
  5. function useBooks(){
  6. const books = ref([]);
  7. return {
  8. books // books is refObj
  9. }
  10. }
  11. function useLoginUser(){
  12. const user = readonly({
  13. isLogin: false,
  14. loginId: null
  15. });
  16. return toRefs(user); // { isLogin: refObj, loginId: refObj } all ref is readonly
  17. }
  18. setup(){
  19. // 在setup函数中,尽量保证解构、展开出来的所有响应式数据均是ref
  20. return {
  21. ...usePos(),
  22. ...useBooks(),
  23. ...useLoginUser()
  24. }
  25. }