在 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 函数。

实现

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 的心智模型。

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日
许可协议