Vue3 响应式API-Computed和Watch

1,688 阅读3分钟

这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战

很感谢掘金让我深入学习某样东西并把它记录下来!

什么是响应式

响应性是一种允许我们以声明式的方式去适应变化的编程范例。
当一个值依附另一个值进行计算时, 如果前一个值发生了变化, 依附的那个也会同时进行变更.
典型案例参见 excel 函数. 当单元格A3=SUM(A1:A2)时, 此时更改A1或A2任意一个发生改变时, A3会立即作出响应更新.

响应式是Vue3的核心API。
官方也把此响应式API进行单独打包,即便不使用Vue3的情况下也可以独立使用:@vue/reactivity

响应式API-Computed和Watch

在响应式核心库中一共有三大模块,ref、reactive响应式变量、computed计算属性变量、watch状态监听函数

computed 计算属性

方式一

接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

方式二

它也可以使用具有 get 和 set 函数的对象来创建可写的 ref 对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

watch 状态监听

watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生更改时被调用。

侦听一个单一源

侦听器数据源可以是具有返回值的 getter 函数,也可以是 ref

// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

侦听多个源

侦听器还可以使用数组同时侦听多个源:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

深度监听对象源

尝试检查深度嵌套对象或数组中的 property 变化时,仍然需要 deep 选项设置为 true。

const state = reactive({ 
  id: 1,
  attributes: { 
    name: ''
  }
})

watch(
  () => state,
  (state, prevState) => {
    console.log(
      'deep',
      state.attributes.name,
      prevState.attributes.name
    )
  },
  { deep: true }
)

state.attributes.name = 'Alex' // 日志: "deep" "Alex" "Alex"

或者,也可以使用深拷贝函数实现深度监听

import _ from 'lodash'

const state = reactive({
  id: 1,
  attributes: {
    name: '',
  }
})

watch(
  () => _.cloneDeep(state),
  (state, prevState) => {
    console.log(
      state.attributes.name,
      prevState.attributes.name
    )
  }
)

state.attributes.name = 'Alex' // 日志: "Alex" ""

watchEffect

为了根据响应式状态自动应用重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> logs 0

setTimeout(() => {
  count.value++
  // -> logs 1
}, 100)

此方法用法虽并非难事, 但在实际应用如稍处理不当代码可读性堪忧, 如果久置代码可能会令人茫然.

watchEffect->stop() 停止监听

显式调用返回值以停止监听:

const stop = watchEffect(() => {
  /* ... */
})

stop()

感悟与愿想

目前Vue3的响应式框架可算是把函数式玩的出神入化.

API使用不难, 但您实际编写项目时真能完全有效拆解并直观的读懂代码以及执行逻辑的运行轨迹么?
假设你写完的你认为拆分很棒的逻辑代码, 别人能直接上手改么?

函数式版响应式框架给我带来了一些苦恼:

第一个苦恼, 每次声明出来的东西都要加value. 很难受! 真的很难受! 非常难受!😣

第二个苦恼, 每次都要在callback()中进行. 令人抓狂!😫

如果声明响应式变量能有语法糖的解决方案, 避免每次都要const, 并把它移植入ECMAScript规范中, 那JS将会更上层楼.

以下是我的ref和computed语法糖想法:

ref count = 1;
computed plusOne = () => count + 1

console.log(plusOne) // 2
plusOne++ // 错误

处理getter、setter并存的计算属性情况.

ref count = 1;
computed plusOne = {
  get: () => count + 1,
  set: val => {
    count = val - 1
  }
}

plusOne = 1
console.log(count) // 0

以上片段只是构想!

文献