React-Hooks + Context-这是进行全局状态管理的好方法吗?

时间:2019-04-10 11:09:16

标签: reactjs rxjs react-hooks react-context

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

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

我还从商店中导出了更多对象(值得注意的是:原始rxjs主题对象和商店的Proxy(总是返回最新值)。

这有效。当我在全局存储区中进行更改时,我会在应用程序中的任何位置(无论是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是很酷的-这个问题不再有意义

2 个答案:

答案 0 :(得分:1)

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

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

因此,我决定使用 React Hooks 和 RxJS 实现自己的全局状态管理器。主要是因为我不习惯在真正的大型项目上工作(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. useSharedStore

我创建了一个钩子来轻松使用与 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} />
    </>
  );
}

答案 1 :(得分:0)

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

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

<Context.Consumer>
  ({ foo: { bar }, setState }) => <PureFoo bar={bar} setState={setState}/>
</Context.Provider>
只要PureFoobar相同,

setState就不会在状态更新时重新呈现。