开发者问题收集

如何调用更新反应上下文状态/或调用非反应类的方法?

2019-11-02
2323

我有一个 React 上下文,它被用作跨多个组件的提供程序。

我想修改该状态并通过完全在 React 组件之外的常规方法更新 UI。即不是功能组件或类,只是一段普通的 JS 代码。

据我所知,提供程序/上下文只能从渲染方法或 React.FC 中访问

我可以传入一个函数 ,但似乎只能从夹在 <Context.Provider>

之间的某种类型的 React 组件传入,并且 useContext 只有在相关项目传递到渲染循环后才会亮起。

有没有办法可以创建某种类型的 store ,我可以在其数据上调用 setState() 方法,但这种更新在 UI 中会是反应性的?

[edit] 我想要这个的原因是我有一个外部 API 调用我需要一个长时间运行的回调。我认为没有必要将该 API 包装在各种 React 内容中,因为我希望该模块可移植(例如移植到服务器端),并且只需与应用状态交互即可更新 UI 显示。

2个回答

据我所知,如果您想使用 React 构建 UI,您仍然需要选择加入 React API。

以下是您需要执行的操作:

  • 需要一个上下文来在您的组件树中共享值
  • 需要在根目录中有一个 Provider 组件
  • 需要订阅外部 API 以接收新值并将其设置为您的上下文
  • 使用上下文使用者获取共享值

以下是 示例 ,说明如何工作

Amin Paks
2019-11-03

我猜你问这个问题是因为你没有使用像 redux 这样的状态管理器,你可以从任何地方发送操作来重置状态。

正如你已经提到的,一个选项是创建你自己的商店和商店提供商。也许像这样的东西对你有用:

const store = (initialState => {
  let value = initialState;
  let listeners = [];
  const getState = () => value;
  const setState = fn => {
    value = fn(value);
    listeners.forEach(l => l(value));
  };
  const subscribe = listener => {
    listeners.push(listener);
    return () =>
      (listeners = listeners.filter(f => f !== listener));
  };
  return { getState, setState, subscribe };
})({ counter: 1 }); //pass initial state to IIFE

const Store = React.createContext();

function Provider({ store, children }) {
  const [state, setState] = React.useState(
    store.getState()
  );
  React.useEffect(
    () =>
      store.subscribe(() => {
        const lastState = store.getState();
        //if a lot of setState calls are made synchronously
        //  do not update dom but let it batch update state
        //  before triggering a render
        Promise.resolve().then(() => {
          if (lastState === store.getState()) {
            setState(store.getState());
          }
        });
      }),
    [store]
  );
  return (
    <Store.Provider value={state}>
      {children}
    </Store.Provider>
  );
}

function App() {
  const state = React.useContext(Store);
  return <div>{state.counter}</div>;
}

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

//call store setState from anywhere
setInterval(
  () =>
    store.setState(state => ({
      ...state,
      counter: state.counter + 1,
    })),
  1000
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
HMR
2019-11-03