在 react 中使用 vue hooks

本文最后更新于:2022年1月11日 凌晨

场景

你看到标题,可能会想:为什么要这样做?
react hooks 是一个有用的发明,它重新组织了代码编写和思考的模式,利用更小的抽象将状态与函数按功能分割,而非集中到一个状态或生命周期中。但 react hooks 存在状态依赖的概念,更糟的是:它依赖于人工管理 – 尽管 react 官方提供了 eslint 规则,但如果你用过它,会发现它的误报率很高 – 尤其是在复杂组件中。
那么,这是否就意味着没有解决方法了呢?并不,vue3 的作者在某次演讲中讲过 vue3 hooks 相比于 react hooks 的改进之处,其中一项就是不需要手动管理依赖,可以观看 dotJS 2019 - Evan You - State of Components。但 react 在生态上(包括各种库、IDE 开发体验)确实更胜一筹,所以吾辈尝试在 react 通过某种方式实现自动依赖管理,实现一些行为类似于 vue3 的原始钩子。

这个灵感的主要来源是 solid.jsreact-easy-state

思考

vue3 的常用原始钩子列表

  • ref
  • reactive
  • computed
  • watchEffect
  • watch

在 react 中提到可变状态,可能最容易想到的就是 mobx(与之相对的是不可变的 redux),所以下面便基于它实现以上这些 hooks 函数。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { observer } from 'mobx-react'
import { action, autorun, computed, observable, reaction } from 'mobx'
import { useCallback, useEffect, useMemo, useState } from 'react'

export function useVReactive<T extends object>(value: T): T {
const [state] = useState(() => observable(value))
return state
}

export function useVRef<T>(value: T): { value: T } {
return useVReactive({ value })
}

export function useVComputed<T>(fn: () => T): { value: T } {
const computedValue = useMemo(() => computed(fn), [])
return {
get value() {
return computedValue.get()
},
}
}

export function useVFn<T extends (...args: any[]) => any>(fn: T): T {
return useCallback(action(fn), [])
}

export function useVWatch(deps: () => any, fn: () => void): void {
useEffect(() => reaction(deps, fn), [])
}

export function useVWatchEffect(fn: () => void): void {
useEffect(() => autorun(fn), [])
}

const contextMap = observable(new Map<ContextKey<any>, any>())
// eslint-disable-next-line
export interface ContextKey<T> extends Symbol {}
export function useVProvide<T>(key: ContextKey<T>, value: T): void {
useState(action(() => contextMap.set(key, value)))
useEffect(
action(() => {
contextMap.set(key, value)
return action(() => {
contextMap.delete(key)
})
}),
[],
)
}
export function useVInject<T>(key: ContextKey<T>): T | undefined {
const value = useMemo(() => computed(() => contextMap.get(key)), [])
const state = useVRef(value.get())
useVWatchEffect(() => (state.value = value.get()))
return state.value
}

export const defineComponent = observer

吾辈已将之发布到 npm @liuli-util/mobx-vue3-hooks

使用

使用起来与 vue3 hooks 的感觉差不多,只是声明一个状态,然后直接修改它,一切就自动响应了 – 不再需要手动管理依赖和了解 hooks 的心智模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
defineComponent,
useVRef,
useVFn,
useVComputed,
} from '@liuli-util/mobx-vue3-hooks'

const HelloWorld = defineComponent(() => {
const state = useVRef(0)
const computedValue = useVComputed(() => state.value * 2)

const onInc = useVFn(() => {
state.value++
})
return (
<div>
<button onClick={onInc}>增加</button>
<div>{computedValue.value}</div>
</div>
)
})

由于一些原因,吾辈未能完全实现 vue3 hooks 的效果,例如

  • 需要用 useVFn 包裹操作状态的函数,而在 vue3 中实际上只需要在 setup 函数中声明普通函数就好了
  • useWatch 使用计算函数,而 vue3 使用依赖状态数组
  • 必须使用 defineComponent 包裹组件,在 vue3 中它只是一个用于代码提示而已

总结

react 生态确实有各种各样的东西,生态超级丰富,但某些细节确实做的相对粗糙,尤其是官方放任自由的情况下。吾辈之前经常有这种疑惑:为什么没有人对现在的东西感觉奇怪?例如 react-router v4=>v6 瞎更新、material-ui/fluentui 的表单和表格组件相比 antd 几乎不可用、redux 的复杂性在现在是否还有必要作为默认状态管理器、react hooks 依赖管理依赖于人工很烦、css in js 几十种方案为什么官方不管管之类的。


在 react 中使用 vue hooks
https://blog.rxliuli.com/p/f7d8ad202ea4482b996264deb7bc9c5e/
作者
rxliuli
发布于
2022年1月3日
许可协议