如何将 React Context 中的值传递到 Redux-Saga 函数中?

Car*_*ven 4 javascript reactjs redux redux-saga react-context

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

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

const { connectionObject } = useMyContext();
Run Code Online (Sandbox Code Playgroud)

这将使我能够connectionObject从上下文中获取实例。

但是,我还在应用程序的其他部分使用 redux saga。这些 redux sagas 之一需要使用personObjectfrom 上下文。

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

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

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

// 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);
}
   
Run Code Online (Sandbox Code Playgroud)

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

Nic*_*wer 8

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

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


但您正在使用上下文,因此它的值可能会发生变化。因此,这给我们带来了下一种情况:只有一个副本,它的值确实会发生变化,但只有组件关心它何时发生变化(传奇只会在需要时查找它,并且不需要通知)。为此,我可能会让上下文提供程序将其状态复制到全局变量,由传奇检查。

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);
}
Run Code Online (Sandbox Code Playgroud)

接下来的困难是只有一个副本,值发生变化,并且组件和传奇都需要能够监听其值的变化。例如,也许传奇开始,检查值,如果它发现该值是假的,它需要休眠直到该值变成真。

为了支持这一点,您将需要有某种 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]);
  // ...
}
Run Code Online (Sandbox Code Playgroud)

最后,最难的情况是:有多个上下文提供者。如果是这种情况,那么就有多个可能的值,并且 saga 无法知道使用哪一个。没有办法,只能告诉它当然,你可以通过操作有效负载来执行此操作,然后这将我们带回到最初的想法。

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