【问题标题】:How to pass a value from React Context into a Redux-Saga function?如何将 React Context 中的值传递给 Redux-Saga 函数?
【发布时间】:2022-01-13 03:22:06
【问题描述】:

如何将 React 上下文中的内容传递到我的 Redux Saga 函数中?

我有一个上下文值,我可以用类似这样的东西在反应组件中检索它

const { connectionObject } = useMyContext();

这将允许我从上下文中获取 connectionObject 实例。

不过,我还在我的应用程序的其他一些部分使用了 redux saga。这些 redux sagas 之一将需要使用上下文中的 personObject

由于 Redux-Sagas 本身在技术上只是常规的生成器函数,因此我不能在其中使用 useMyContext() 来检索 connectionObject。例如,这是行不通的

export function* saveData() {
   const { connectionObject } = useMyContext();  // This will not work
   yield performSaveSomething(connectionObject);
}

Redux Saga 是由我从 react 组件中调度 redux 操作触发的。一种可能的解决方法当然是从 react 组件的上下文中获取 connectionObject,然后使用 connectionObject 作为有效负载的一部分分派操作,然后将其传递给 saga。但这感觉完全是反模式并且并不优雅,尤其是当有效载荷应该更新到状态但在这种情况下仅用于传递给 saga 时。下面是一个例子来说明尴尬:

// In the React component
export const myReactComponent = (props) => {
   const { connectionObject } = useMyContext();
   dispatch(saveDataAction({ 
      data: 'some data here to be put in the redux store',
      connection: connectionObject 
   }));
}

// In the reducer
function reducer (state, action) {
   switch (action.type) {
      case SAVE_DATA_ACTION_TYPE:
         // action.payload has 2 attributes in this case: data and connection
         // But I am throwing away the connection one here in this reducer because that is only meant for the saga. Feels weird here.
         return {
            ...state,
            data: action.payload.data.  // Instead of using the whole payload, I've to use only the "data" attribute of the payload because only this mattered to the store. I can't use the whole payload because it has the connectionObject in there. Doesn't look elegant.
         }
   }
}

// And finally in the saga function
export function* saveData({ payload: { connection } }) {
   // Good thing here is I could finally access the connectionObject which came from the react context
   // However, it's also doesn't seem right that I'm passing the connectionObject through the payload which isn't a piece of data but as a dependency
   yield performSaveSomething(connection);
}
   

有没有一种方法可以优雅而轻松地传递从 React 上下文中检索到的值,并以某种方式将其传递给我的 Redux-Saga 函数以便它可以使用它?

【问题讨论】:

  • 两个旁注:首先,基于那个 reducer 示例,看起来您正在“手动”编写 Redux 逻辑。我们强烈建议改用our official Redux Toolkit package,这将大大简化您的 Redux 代码。其次,我们实际上建议反对在大多数用例中使用 saga。它们是复杂异步工作流的绝佳工具,但大多数 Redux 应用不需要它们——尤其是对于基本的数据获取场景。
  • @markerikson 感谢您的提示!只是想知道,对于大多数用例,您建议使用什么来代替 saga?那会是 redux thunk 吗?
  • 是的,我们通常建议使用 thunk 作为 API 调用的标准方法,或者我们新的 RTK 查询数据获取 API。请参阅the "Redux Essentials" docs tutorial 了解这两种情况的示例。

标签: javascript reactjs redux redux-saga react-context


【解决方案1】:

我的第一个倾向是按照您的建议去做:将其包含在操作的有效负载中。组件知道值,saga 需要值,因此它可以将值传入。这可能是最简单的解决方案。

除此之外...这有点取决于上下文值的性质。让我们从最简单的情况开始,然后转向更困难的情况。最简单的情况:上下文只有一个副本,其值永远不会改变。如果它永远不会改变,那么它实际上不需要在上下文中。您可以简单地将 connectionObject 移动到一个文件中,将其导出,然后在需要的任何地方导入它。


但是您使用的是上下文,所以它可能是一个确实会改变的值。所以这把我们带到了下一个案例:只有一个副本,它的值确实改变了,但只有组件关心它何时改变(saga 只会在需要时查找它,而不需要通知)。为此,我可能会让上下文提供者将其状态复制到一个全局变量,由 saga 检查。

let globalValue = {};
export const getGlobalValue = () => globalValue;

export const MyContextProvider = ({ children }) => {
  const [stateValue, setStateValue] = useState({});
  useEffect(() => {
    globalValue = stateValue;
  }, [stateValue]);

  return (
    <MyContext.Provider value={stateValue}>
      {children}
    </MyContext.Provider>
  )
}

// In the saga:
import { getGlobalValue } from './path/to/context/provider/file

export function* saveData() {
   const connectionObject = getGlobalValue();
   yield performSaveSomething(connectionObject);
}

接下来的困难是只有一个副本,值发生变化,组件和 sagas 都需要能够监听其值的变化。例如,也许 saga 启动,检查值,如果它发现值为 false,则需要休眠,直到值变为 true。

为了支持这一点,您将需要有某种 saga 可以订阅的事件发射器。此类代码有许多现有的库,您可以自己滚动,例如:

let listeners = [];
export const subscribe = (callback) => {
  listeners.push(callback);
  const unsubscribe = () => {
    let i = listeners.indexOf(callback);
    listeners.splice(i, 1);
  }
  return unsubscribe;
}

export const MyContextProvider = ({ children }) => {
  const [stateValue, setStateValue] = useState({});
  useEffect(() => {
    for (const listener of [...listeners]) {
      callback(stateValue);
    }
  }, [stateValue]);
  // ...
}

最后,最难的情况是:有多个上下文提供者。如果是这种情况,那么就有多个可能的值,并且 saga 无法知道使用哪一个。当然只能告诉它,你会通过 action payload 做什么,然后让我们回到最初的想法。

我想可以不传入整个对象,而只传递一个 id,然后使用上述技术之一来查找对象。但我不认为这比传递对象本身更好。

【讨论】:

  • 感谢您的回答!至于通过在其中包含一个额外的属性只是为了传递上下文值来传递该有效负载的最初想法,这是一种反模式,因为感觉就像我将有效负载用于不同的目的?
  • 我不认为它是一种反模式。操作负载的目的是为您想要发生的事情提供说明。在简单的情况下,这些指令仅由 reducer 使用,但您也可以为 sagas 或其他自定义中间件提供指令。如果减速器最终没有使用所有指令,那很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-08
  • 2019-04-13
  • 1970-01-01
  • 2018-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多