vue3入门04 - 响应式系统 Api

496 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

上一节我们讲述了如何在不使用构建工具的情况下使用vue3,基于上一节的代码,我们来学习一下vue3的响应式系统Api

响应式系统介绍

在vue3的源码中,响应式系统使用单独的一个模块,路径在packages/reactivity下。 响应式以系统是i一组具有响应式特性的函数API,都是以函数形式提供的。 也就是说我们是可以在项目中单独使用这一部分API,让我们的数据具备响应式。 主要包含以下几个api:

  • reactive
  • ref
  • computed
  • readonly
  • watchEffect
  • watch

为了更好的演示,我们还是在vue3的setup环境下进行讲述。

reactive

  • reactive()函数接收一个普通对象,返回该普通对象的响应式代理对象。
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性。
<div id="app">
  <button @click="increment">
    Count is: {{ state.count }}
  </button>
</div>
<script>
  const { createApp, reactive } = Vue
  createApp({
    setup() {
      const state = reactive({ count: 0 });
      function increment() {
        state.count++;
      }
      // 不要忘记同时暴露 increment 函数
      return {
        state,
        increment,
      };
    },
  }).mount('#app')
</script>

ref

  • ref()函数接收一个参数值,返回一个响应式的数据对象。该对象只包含一个指向内部值的 .value 属性
  • 如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。
<div id="app">
  <button @click="increment">
    Count is: {{ count }}
  </button>
</div>
<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const count = ref(0);
      function increment() {
        count.value++;
      }
      return {
        count,
        increment,
      };
    },
  }).mount('#app')
</script>

注意:当 ref 作为渲染上下文的属性返回(即在setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value

  • reactive包裹ref

reactive对象中访问ref时,无需通过.value属性,它会自动展开

const count = ref(0)
const state = reactive({
  count,
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

注意:当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套:

const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)

const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)

computed

  • computed() 函数用来创建计算属性,函数的返回值是一个 ref 的实例

getter函数

<div id="app">
  <div>{{ count }}</div>
  <div>{{plusOne}}</div>
</div>
<script>
  const { createApp,  ref, computed } = Vue
  createApp({
    setup() {
      const count = ref(1);
      /**
       *!只读的计算属性
       */
      // 根据count创建一个响应式的计算属性
      const plusOne = computed(() => {
        // get函数
        return count.value + 1;
      });
      console.log(plusOne.value);
      return {
        count,
        plusOne,
      };
    },
  }).mount('#app')
</script>

当我们只传入一个函数时,此函数为getter函数,也就是读取值,返回一个默认不可手动修改的 ref 对象。

getter和setter同时使用

  • 我们也可以传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态
<div id="app">
  <div>{{ count }}</div>
  <button @click="increment">{{plusOne}}</button>
</div>
<script>
  const { createApp, ref, computed } = Vue
  createApp({
    setup() {
      const count = ref(1);
      function increment() {
        plusOne.value++
      }
      const plusOne = computed({
        get: () => {
          return count.value + 1;
        },
        set: (val) => {
          count.value = val * 2;
        },
      });
      console.log(plusOne.value);
      return {
        count,
        plusOne,
        increment
      };
    },
  }).mount('#app')
</script>

readonly

  • readonly()函数接收一个对象(普通或响应式),返回一个原始对象的只读代理对象
const state = reactive({
  count: 1,
});
const proxy = readonly(state);
console.log(state.count);
console.log(proxy.count);

//! 代理对象只读的,不允许修改
// proxy.count++ 

//! 修改原始对象时代理对象也会响应式变化
state.count++;
console.log(state.count);
console.log(proxy.count);

watchEffect

  • watchEffect()函数接收一个函数作为参数,并立即执行该函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(1);
/**
 * 1. 先立即执行传入的函数
 * 2. 当函数内依赖的数据发生变化时,会再次执行函数
 */
watchEffect(() => {
  console.log(count.value);
});
onMounted(() => {
  console.log('mounted');
})
setTimeout(() => {
  count.value++;
}, 3000);

注意:第一次进入页面时,watchEffectonMounted之前执行,如果我们想获取模版refs或者dom元素,需要在生命周期之后执行watchEffect

onMounted(() => {
  watchEffect(() => {
    // 在这里可以访问到 DOM 或者 template refs
  })
})

停止监听

  • setup函数或者声明周期中调用watchEffect时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
  • 但是有些时候我们想要手动停止监听,我们可以通过返回值来做操作。
const stop = watchEffect(() => {
  /* ... */
})

// 之后
stop()

清除副作用

如果我们想在watchEffect中异步获取数据,但是当我们想在组件销毁时取消异步获取数据,减少无意义的请求怎么做呢。我们可以借接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调。

watchEffect((onInvalidate) => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // id 改变时 或 停止侦听时
    // 取消之前的异步操作
    token.cancel()
  })
})

watch

  • watch() 函数用来监视数据的变化,从而触发特定的操作,等同于 vue 2.x 中的 this.$watch

监听单个数据源

  • 监听ref数据时,之间监听即可,不需要使用.value
//! 1. 监视ref数据源
const count = ref(1);
watch(count, (newVal, oldVal) => {
  console.log(oldVal);
  console.log(newVal);
});
setTimeout(() => {
  count.value = 6;
}, 3000);
  • 监听reactive中单个数据时,需要使用函数的形式来进行监听
//! 2. 监视rective数据源
const state = reactive({
  count: 1,
});
// 监视state.count, 必须以函数形式定义
watch(
  () => state.count,
  (newVal, oldVal) => {
    console.log(oldVal);
    console.log(newVal);
  }
);
setTimeout(() => {
  state.count = 8;
}, 3000);

监听多个数据源

  • 我们也可以使用数组形式,监听多个数据源。
//! 3.监视多个数据源
const name = ref("tom");
const age = ref("18");
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
  console.log(oldName, newName);
  console.log(oldAge, newAge);
});
setTimeout(() => {
  name.value = "张三";
  age.value = "11";
}, 3000);

总结

以上就是关于响应式系统的基本介绍,更多内容还需要在实战中继续深入研究。