【问题标题】:React - Hooks + Context - Is this a good way to do global state management?React - Hooks + Context - 这是进行全局状态管理的好方法吗?
【发布时间】:2019-04-10 11:09:15
【问题描述】:

我正在尝试找到一种良好、干净、几乎没有样板的方法来处理 React 的全局状态

这里的想法是有一个 HOC,利用 React 的新 Hooks & Context API,它返回一个 Context 提供者,其值绑定到它的状态。我使用 rxjs 来触发存储更改的状态更新。

我还从我的商店中导出了更多对象(特别是:原始 rxjs 主题对象和始终返回最新值的商店代理)。

这行得通。当我在全局商店中更改某些内容时,我会在应用程序的任何地方(无论是 React 组件还是 React 外部)获得更新。然而,为了实现这一点,HOC 组件会重新渲染。

这是无操作吗?

我认为可能有问题的代码/逻辑是 HOC 组件:

const Provider = ({ children }) => {
    const [store, setStore] = useState(GlobalStore.value)

    useEffect(() => {
        GlobalStore.subscribe(setStore)
    }, [])

    return <Context.Provider value={store}>{children}</Context.Provider>
}

GlobalStore 是一个 rxjs BehaviorSubject。每次更新主题时,Provider 组件的状态都会更新,从而触发重新渲染。

这里有完整的演示:https://codesandbox.io/s/qzkqrm698q

真正的问题是:这不是一种糟糕的全局状态管理方式吗?我觉得可能是因为我基本上在状态更新时重新渲染了所有内容......

编辑:我认为我已经编写了一个性能更高的版本,它不是那么轻量级(取决于 MobX),但我认为它产生的开销要少得多(演示地址:https://codesandbox.io/s/7oxko37rq) - 现在有相同的最终结果,但放弃 MobX 会很酷——这个问题不再有意义

【问题讨论】:

  • 您的演示不起作用。
  • 怎么样?有什么问题?
  • TypeError Cannot create proxy with a non-object as target or handler store.js 第 44 行
  • 糟糕,感谢您指出这一点。这是因为我也在尝试将存储保存到本地存储,并且由于我的存储中已经有一个存储,它运行良好,但它有一个错误。现在已经修好了。对不起
  • Checkout MobX 和 MST 特别是它体现了您在此处尝试实现的内容。

标签: reactjs rxjs react-hooks react-context


【解决方案1】:

我了解您需要处理全局状态。我已经发现自己处于同样的境地。我们采用了类似的解决方案,但就我而言,我决定完全放弃 ContextAPI。

ContextAPI 对我来说真的很糟糕。它似乎假装是一个基于控制器的模式,但你最终将代码包装在一个无意义的 HOC 中。也许我错过了他的观点,但在我看来,ContextAPI 只是提供基于范围的数据流的一种复杂方式。

因此,我决定使用 React Hooks 和 RxJS 实现我自己的 global 状态管理器。主要是因为我不习惯从事非常大的项目(Redux 非常适合)。

我的解决方案很简单。因此,让我们阅读一些代码,因为它们说的不仅仅是文字:

1。商店

我创建了一个仅用于 dar nome aos bois 的类(这是一个流行的巴西表达,谷歌它?)并有一种简单的方法来使用 BehaviorSubject 值的部分更新:

import { BehaviorSubject } from "rxjs";

export default class Store<T extends Object> extends BehaviorSubject<T> {
  update(value: Partial<T>) {
    this.next({ ...this.value, ...value });
  }
}

2。 createSharedStore

一个实例化Store类的函数(是的,只是因为我不喜欢输入new¯\(ツ)/¯):

import Store from "./store";

export default function <T>(initialValue: T) {
  return new Store<T>(initialValue);
}

3。使用SharedStore

我创建了一个挂钩来轻松使用与Store 连接的本地状态:

import Store from "./store";
import { useCallback, useEffect, useState } from "react";
import { skip } from "rxjs/operators";
import createSharedStore from "./createSharedStore";

const globalStore = createSharedStore<any>({});

type SetPartialSharedStateAction<S> = (state: S) => S;
type SetSharedStateAction<S> = (
  state: S | SetPartialSharedStateAction<S>
) => void;

export default function <T>(
  store: Store<T> = globalStore
): [T, SetSharedStateAction<T>] {
  const [state, setState] = useState(store.value);

  useEffect(() => {
    const subscription = store
      .pipe(skip(1))
      .subscribe((data) => setState(data));
    return () => subscription.unsubscribe();
  });

  const setStateProxy = useCallback(
    (state: T | SetPartialSharedStateAction<T>) => {
      if (typeof state === "function") {
        const partialUpdate: any = state;
        store.next(partialUpdate(store.value));
      } else {
        store.next(state);
      }
    },
    [store]
  );

  return [state, setStateProxy];
}

4。 ExampleStore

然后我为每个需要共享状态的功能导出单独的商店:

import { createSharedStore } from "hooks/SharedState";

export default createSharedStore<Models.Example | undefined>(undefined);

5。示例组件

最后,这是如何在组件中使用(就像一个常规的 React 状态):

import React from "react";
import { useSharedState } from "hooks/SharedState";
import ExampleStore from "stores/ExampleStore";

export default function () {
  // ...
  const [state, setState] = useSharedState(ExampleStore);
  // ...

  function handleChanges(event) {
    setState(event.currentTarget.value);
  }

  return (
    <>
      <h1>{state.foo}</h1>
      <input onChange={handleChange} />
    </>
  );
}

【讨论】:

【解决方案2】:

GlobalStore 主题是多余的。 RxJS observables 和 React 上下文 API 都实现了 pub-sub 模式,以这种方式一起使用它们没有任何好处。如果GlobalStore.subscribe应该在children中用于更新状态,这将导致不必要的紧耦合。

使用新对象更新全局状态将导致重新渲染整个组件层次结构。避免子级性能问题的一种常见方法是选择必要的状态部分并将它们设为纯组件以防止不必要的更新:

<Context.Consumer>
  ({ foo: { bar }, setState }) => <PureFoo bar={bar} setState={setState}/>
</Context.Provider>

只要barsetState 相同,PureFoo 就不会在状态更新时重新渲染。

【讨论】:

  • 我理解你对GlobalStore 的冗余的观点,但我这样做是因为我还想访问 React 上下文之外的全局存储。使用 rxjs 可以让我在我的应用程序的任何地方都有最新的状态,这正是我想要的。
  • 应用程序的其余部分是什么?是反应吗?一种合理的方法是将 React 全局状态提升到它将包装依赖于它的应用程序部分的程度。
  • 应用程序没有其他内容。我对所有 React 项目中的全局状态管理感到非常沮丧,并且一直依赖第三方(主要是 MobX)为我做这件事。我正在尝试找到一种更通用、更不依赖、更轻松的方法。我已经更新了我的演示,试图优化它,但我认为它比以前更糟了。我似乎无法正确弄清楚。
  • 我没有看到 我也想访问 React 上下文之外的全局存储的问题 然后,如果它是上下文使用者,您可以访问 App 组件内的存储,不需要可观察的。没错,第三方库不是必需的,但可以从他们的设计中吸取一些教训。
猜你喜欢
  • 1970-01-01
  • 2021-12-15
  • 2017-04-05
  • 2019-10-15
  • 2019-11-18
  • 2020-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多