Skip to content

Commit

Permalink
[feat](translate) 030/020 computed and watch
Browse files Browse the repository at this point in the history
  • Loading branch information
miyuesc committed Mar 13, 2024
1 parent 14d255a commit c5a29c0
Showing 1 changed file with 57 additions and 49 deletions.
106 changes: 57 additions & 49 deletions book/online-book/src/30-basic-reactivity-system/020-computed-watch.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# computed / watch api

::: warning
2023 年 の 12 月末に [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) がリリースされましたが、これには [reactivity のパフォーマンス改善](https://github.com/vuejs/core/pull/5912) が含まれています。
このオンラインブックはそれ以前の実装を参考にしていることに注意しくてださい。
然るべきタイミングでこのオンラインブックも追従する予定です。
2023 年 12 月月底 [Vue 3.4](https://blog.vuejs.org/posts/vue-3-4) 发布了,其中包括了 [reactivity 的性能优化](https://github.com/vuejs/core/pull/5912) 部分。
需要注意的是,本书参考的是 Vue.js 之前的实现方式。
本章内容不会有太大改变,但是文件结构可能略有调整,代码也有部分改动。
我也会在日后对这本书进行相应的更新。
:::

## computed のおさらい (と実装)
## 回顾 computed (和实现)

前のチャプターで ref 系の api を実装しました。続いては computed です。
https://vuejs.org/api/reactivity-core.html#computed
在上一小章中,我们实现了 `ref` 相关的一些 API。现在,该实现 `computed` 了。

computed には読み取り専用と書き込み可能の 2 つのシグネチャがあります。
https://cn.vuejs.org/api/reactivity-core.html#computed

`computed` 有两种使用方式:只读和可写。

```ts
// read-only
Expand All @@ -31,9 +33,9 @@ function computed<T>(
): Ref<T>
```

本家の実装は短いながらに少しだけ複雑なので、まずはシンプルな構成を考えてみましょう
源代码中的实现虽然不长,但是逻辑比较复杂,所以我们先从一个简单的实现开始

最も簡単な方法として思いつくのは、valueget する際に毎度コールバックを発火するという手法でしょう
我目前能想到的最简单的方法就是,在每次读取 `value` 的时候就执行一次计算(`getter()`)得到计算结果然后返回

```ts
export class ComputedRefImpl<T> {
Expand All @@ -47,14 +49,15 @@ export class ComputedRefImpl<T> {
}
```

しかしこれでは computed というか、ただ関数を呼んでいるだけです (そりゃそう。特に何も嬉しくないですね、残念。)
但是,在这种情况下 `computed` 只能称为是一个函数调用(我觉得这并没有什么值得兴奋的)。

实际上,我们希望它能自动追踪依赖项,并在依赖项的值发生改变时重新计算它。

実際には依存関係を追跡し、その値が変更されたときに再計算したいわけです
为了实现这个效果,我们使用了一种新的机制,将 `_dirty` 标志的更新函数作为调度器的 `job` 来执行

これをどのように実現しているかというと、\_dirty フラグの書き換えをスケジューラのジョブとして実行するような仕組みになっています。
\_dirty フラグというのはいわば「再計算する必要があるか?」という状態を持つフラグで、依存によって trigger された場合にこのフラグを書き換えます。
`_dirty` 标志用来确定 “是否需要重新计算”,在依赖项发生变化时被重写更新。

イメージ的には以下のようなものです。
下面是它的工作原理的示例:

```ts
export class ComputedRefImpl<T> {
Expand Down Expand Up @@ -82,23 +85,25 @@ export class ComputedRefImpl<T> {
}
```

computed は実は遅延評価のような性質を持っており、再計算が必要な場合は値が読み取られる際に初めて再計算されます。
そしてこのフラグを true に書き換え関数は不特定多数の依存に trigger されるため、ReactiveEffectscheduler として登録します。
`computed` 实际上还有 “惰性计算” 的特性,如果需要重新进行计算,也只有在发送计算属性值读取的时候才会重新计算(即不是在依赖发生改变时就立即重新计算)。

然后,将更改标志为 `true` 的函数注册为一个 `ReactiveEffect` 调度程序,因为这个函数的执行是由这个计算属性对应的依赖项更新触发的。

基本的な流れはこのような感じです。実装するにあたって、加えて注意する点がいくつかあるので以下にまとめておきます。
基本的流程就是这样的。但是在实现时,还有一些需要注意的事项,大概总结如下:

- \_dirty フラグを true に書き換えた段階で自信が持つ依存関係は trigger してしまう
- `_dirty` 修改为 `true` 时,还会触发所有与这个计算属性相关(依赖它)的副作用函数执行
```ts
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
```
- computed も分類的には`ref`なので、`__v_isRef`true にマークしておく
- setter を実装したかったら最後に実装する。まずは算出できるようになることを目指す
- `computed` 在分类上也属于 `ref` 一类的,所以计算属性对象的 `__v_isRef` 属性为 `true`
- 如果你想实现 `setter`,请在最后再实现它,首要目标是实现值的计算

さて、これで準備は整ったので実際に実装してみましょう!
以下のようなコードが期待通りに動けば OK です! (それぞれ、依存している computed だけが発火することを確認してください! )
现在我们已经准备好了,让我们开始实现它吧!

如果下面的代码能像预期的那样工作,那就 OK 了!(请确保只有内部的依赖项改变才会触发重新计算!)

```ts
import { computed, createApp, h, reactive, ref } from 'chibivue'
Expand Down Expand Up @@ -139,17 +144,21 @@ const app = createApp({
app.mount('#app')
```

当前源代码位于:
当前源代码位于:
[chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/050_computed)
(setter 込みはこちら):

(`setter` 相关的内容在这里):
[chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/060_computed_setter)

## Watch の実装
## Watch 的实现

https://vuejs.org/api/reactivity-core.html#watch
https://cn.vuejs.org/api/reactivity-core.html#watch

watch にもいろんな形式の api があります。まずは最も単純な、getter 関数によって監視するような api を実装してみましょう。
まずは、以下のようなコードが動くことを目指します。
`watch` 这个类型下也有很多的 API

首先,让我们先显示一个最简单的 API,它由一个 `getter` 函数进行依赖收集。

我们目前的目标是能正常运行下面的代码。

```ts
import { createApp, h, reactive, watch } from 'chibivue'
Expand All @@ -173,11 +182,13 @@ const app = createApp({
app.mount('#app')
```

watch の実装は reactivity ではなく、runtime-core の方に実装していきます (apiWatch.ts)。
我们会在 `runtime-core` 目录中实现 `watch` 函数(`apiWatch.ts`),而不是之前的 `reactivity`

因为源码中混合了很多其他的 API,所以看起来非常复杂,但是如果我们缩小一下范围,就会发现其实它非常简单。

我已经实现了当前要实现的 `watch` 函数对应的类型签名,大家试着自己实现一下吧。

さまざまな api が混在しているので、少し複雑に見えますが、範囲を絞ればとても単純なことです。
目標とする api(watch 関数)のシグネチャを以下に実装しておくので、是非実装してみてください。
今までリアクティビティの知識を培ってきたみなさんなら実装できると思います!
如果你已经掌握了到目前为止的所有响应式相关的知识,我相信你可以轻松的做到这一点。

```ts
export type WatchEffect = (onCleanup: OnCleanup) => void
Expand All @@ -194,21 +205,20 @@ export function watch<T>(
}
```

当前源代码位于:
[chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)
当前源代码位于: [chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/070_watch)

## watch の その他の api
## watch API 的其他实现

ベースができてしまえば、後は拡張するだけです。これも特に解説は必要ないでしょう
一旦我们掌握了基础部分,那么扩展它就是很容易的事情了,这些也不用做太多的解释

- ref の監視
- 监听 `ref`
```ts
const count = ref(0)
watch(count, () => {
/** some effects */
})
```
- 複数の source の監視
- 监听多个来源。

```ts
const count = ref(0)
Expand All @@ -220,7 +230,7 @@ export function watch<T>(
;``
```

- immediate
- `immediate` 立即执行

```ts
const count = ref(0)
Expand All @@ -233,7 +243,7 @@ export function watch<T>(
)
```

- deep
- `deep` 深度监听

```ts
const state = reactive({ count: 0 })
Expand All @@ -246,7 +256,7 @@ export function watch<T>(
)
```

- reactive object
- `reactive object` 响应式对象

```ts
const state = reactive({ count: 0 })
Expand All @@ -255,14 +265,13 @@ export function watch<T>(
}) // automatically in deep mode
```

当前源代码位于:
[chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)
当前源代码位于: [chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/080_watch_api_extends)

## watchEffect

https://vuejs.org/api/reactivity-core.html#watcheffect
https://cn.vuejs.org/api/reactivity-core.html#watcheffect

watch の実装を使えば watchEffect の実装は簡単です
使用 `watch` 的实现原理来实现 `watchEffect` 是很容易的

```ts
const count = ref(0)
Expand All @@ -274,11 +283,10 @@ count.value++
// -> logs 1
```

イメージてには immediate のような実装をすれば OK です
看起来就像实现 `immediate``true` 时的 `watch` 一样

当前源代码位于:
[chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)
当前源代码位于: [chibivue (GitHub)](https://github.com/Ubugeeei/chibivue/tree/main/book/impls/30_basic_reactivity_system/090_watch_effect)

---

クリーンアップについては別のチャプターで行います
我们将在其他章节讨论 `watch` 监听的清理

0 comments on commit c5a29c0

Please sign in to comment.