本文最后更新于: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.js 和 react-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>())
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 几十种方案为什么官方不管管之类的。