导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


reactive

<template>
  <div>{{ proxyObj.name }}</div>
</template>

<script>
import { reactive } from 'vue';

export default {
  name: 'App',
  setup() {
    const proxyObj = reactive({
      name: 'Lance',
      age: 233
    });

    console.log(proxyObj);

    return {
      proxyObj
    }
  }
}
</script>

Untitled

<template></template>
<script>
import { reactive } from 'vue';
export default {
  name: 'App',
  setup() {
    const obj = reactive(1); // 无法包装基本类型
  }
}
</script>

Untitled

reactive() 的局限性

reactive() API 有两条限制:

  1. 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
  2. 因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!) 
state = reactive({ count: 1 })

同时这也意味着当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:

const state = reactive({ count: 0 })

// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state n++

// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++

// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)

数组异步赋值问题

这样赋值页面是不会变化的因为会脱离响应式

let person = reactive<number[]>([]);
setTimeout(() => {
  person = [1, 2, 3];
  console.log(person);
}, 1000);

解决方案:

  1. 使用 push
import { reactive } from 'vue';
let person = reactive<number[]>([]);
setTimeout(() => {
  const arr = [1, 2, 3];
  person.push(...arr);
  console.log(person);
},1000)
  1. 包裹一层对象
type Person = {
  list?:Array<number>
}
let person = reactive<Person>({
   list: []
});
setTimeout(() => {
  const arr = [1, 2, 3];
  person.list = arr;
  console.log(person);
}, 1000);

🔥 ref

<template>
  <div>{{ count }}</div>
</template>

<script>
import { ref } from 'vue';

export default {
  name: 'App',
  setup() {
    const count = ref(0);
    console.log("count:", count);
    console.log("===");
    console.log("count.value:", count.value);
    console.log("===");

    const obj = ref({
      name: 'Lance',
      age: 233,
      info: {
        grade: 120
      }
    });

    console.log("obj", obj);
    console.log("===");
    console.log("obj.value", obj.value); // 第一层需要 .value 拆包
    console.log("===");
    console.log("obj.value.info", obj.value.info); // 后续都是 Proxy 对象了,不用拆包
    console.log("===");
    console.log("obj.value.info.grade", obj.value.info.grade);

    return {
      count,
      obj
    }
  }
}
</script>

Untitled

reactive 包裹 ref

<template>
  <div>{{ proxyObj.count }}</div>
</template>

<script>
import { ref, reactive } from 'vue';

export default {
  name: 'App',
  setup() {
    const count = ref(0);
    const proxyObj = reactive({
      name: 'Lance',
      age: 233,
      count // 相当于vue给你解包了:count: count.value
    });

    console.log(proxyObj.count); // 所以此处不用再 .value,template 中 也不用

    return {
      proxyObj
    }
  }
}
</script>

新的 ref 会覆盖老的 ref

<template>
  <div>{{ count }}</div>
</template>

<script>
import { reactive, ref } from 'vue';

export default {
  name: 'App',
  setup() {
    const count = ref(0);

    const state = reactive({
      count
    });

    const newCount = ref(1);
    state.count = newCount; // 新的ref替换旧的ref
    console.log("state.count:", state.count); // 新的会覆盖旧的
    console.log("count:", count.value); // 原来的不变

    return {
      count,
      state
    }
  }
}
</script>

Untitled

ref 放进数组、Map 等原始集合类型的 reactive 中时不会自动拆包

<template>
  <div>{{ count }}</div>
</template>

<script>
import { reactive, ref } from 'vue';

export default {
  name: 'App',
  setup() {
    const count = ref(0);

    const arr = reactive([count]);
    console.log(arr[0].value); // 需要手动拆包

    const map = reactive( new Map( [['name', ref(233)]] ) );
    console.log(map.get('name').value); // 需要手动拆包

    return {
      count
    }
  }
}
</script>

Untitled

unref

原理

<template></template>

<script>
import { isRef, ref } from 'vue';
export default {
  name: 'App',
  setup() {
    const info = {
      name: 'Lance',
      age: 233
    };
    const refInfo = ref({
      name: 'GC',
      age: 111
    });

    const obj = isRef(info) ? info.value : info;
    const obj2 = isRef(refInfo) ? refInfo.value : refInfo;
    console.log(obj);
    console.log(obj2);
  }
}
</script>

Untitled

使用

<template>
  <div></div>
</template>

<script>
import { ref, unref } from 'vue';
export default {
  name: 'App',
  setup() {
    const info = {
      name: 'Lance',
      age: 2333
    };
    const refInfo = ref({
      name: 'GC',
      age: 111
    });

    const obj = unref(info); // 返回原始对象
    const obj2 = unref(refInfo); // 会拆包,返回 Proxy 对象
    console.log(obj);
    console.log(obj2);
  }
}
</script>

Untitled

toRef

<template></template>

<script>
import { reactive, toRef } from 'vue';

export default {
  name: 'App',
  setup() {
    const state = reactive({
      name: 'Lance'
    });
    const nameRef = toRef(state, 'name');
    nameRef.value = 'GC';
    console.log(state.name); // 同步更新了
    state.name = 'Lance';
    console.log(nameRef.value); // 也更新了
  }
}
</script>

使用场景:自定义 composition api 中使用响应式对象下某个属性

<template></template>

<script>
import { reactive, toRef } from 'vue';

function useDoSth(name) {
  return `My name is ${name.value}`;
}
export default {
  name: 'App',
  setup() {
    const state = reactive({
      name: 'Lance'
    });
    const nameRef = toRef(state, 'name');

    const sentence = useDoSth(nameRef);
    console.log(sentence);
  }
}
</script>

Untitled

🔥 toRefs

<template></template>

<script>
import { reactive, toRefs } from 'vue';

export default {
  name: 'App',
  setup() {
    const state = reactive({
      name: 'Lance',
      age: 233,
      info: {
        grade: 100
      }
    });
    const stateRefs = toRefs(state);
    console.log(stateRefs);
    console.log(stateRefs.name.value);
  }
}
</script>

Untitled

作用

<template>
  <div>
    <!-- 通过 reactive 访问属性 -->
    <div>{{ state.name }}</div>
    <!-- 通过 toRefs reactive 之后越过对象名直接访问属性 -->
    <div>{{ name }}</div>
  </div>
</template>

<script>
import { reactive, toRefs } from 'vue';

export default {
  name: 'App',
  setup() {
    const state = reactive({
      name: 'Lance',
      age: 233,
      info: {
        grade: 100
      }
    });
    const stateRefs = toRefs(state);

    return {
      state,
      ...stateRefs
    }
  }
}
</script>

Untitled

isRef

<template></template>

<script>
import { reactive, toRefs, isRef } from 'vue';

export default {
  name: 'App',
  setup() {
    const state = reactive({
      name: 'Lance',
      age: 233,
      info: {
        grade: 100
      }
    });
    const stateRefs = toRefs(state);
    console.log("isRef(stateRefs):", isRef(stateRefs));
    console.log("isRef(stateRefs.name):", isRef(stateRefs.name));
    return {
      state,
      ...stateRefs
    }
  }
}
</script>

Untitled

customRef

<template>
  <div>
    <span>{{ text }}</span>
    <input type="text" v-model="text">
  </div>
</template>

<script>
import { customRef } from 'vue';

/**
 * value: 值
 * delay: 延迟
 */
function useDebounce(value, delay = 200) {
  let t = null;

  // 返回 customRef, customRef 接收一个工厂函数
  /**
   * track: getter访问时执行
   * trigger: setter触发更新时执行
   */
  return customRef((track, trigger) => {
    // 返回一个对象, 包含 getter、setter
    return {
      get() {
        track();
        return value;
      },
      set(newVal) {
        clearTimeout(t);
        t = setTimeout(() => {
          value = newVal;
          trigger();
        }, delay);
      }
    }
  });
}
export default {
  name: 'App',
  setup() {
    const text = useDebounce('', 500);
    return {
      text
    }
  }
}
</script>

Untitled

shallowRef/triggerRef