【问题标题】:Redux saga & redux toolkitRedux saga 和 redux 工具包
【发布时间】:2020-10-23 11:13:13
【问题描述】:

我一直在尝试将 redux sagas 和 redux 工具包引入我的项目。我目前遇到的问题是观察者传奇没有捕捉到takeEvery 效果中的调度动作并运行处理程序。我看不出代码有什么问题。谁能帮忙!!!

import { configureStore, getDefaultMiddleware  } from '@reduxjs/toolkit'
import createSagaMiddleware from 'redux-saga'
import logger from 'redux-logger';

import createReducer from './rootReducer';
import sagas from './rootSaga';


const configureAdminStore = (initialState = {}) => {
    const sagaMiddleware = createSagaMiddleware();
  
    // sagaMiddleware: Makes redux-sagas work
    const middlewares = [sagaMiddleware, logger];

  
    const store = configureStore({
      reducer: createReducer(),
      middleware: [...getDefaultMiddleware({thunk: false}), ...middlewares],
      preloadedState: initialState,
      devTools: process.env.NODE_ENV !== 'production',
    });
    
    sagaMiddleware.run(sagas);
  
    return store;
  }

  export default configureAdminStore;

import {put, take, takeEvery, call} from 'redux-saga/effects'
import {getAll} from './environmentSlice'
import {confApi} from '../../service/conf-api'
import { getData } from '../../lib/conf-api-response';


function* getAllEnvironments() {
    const response = yield call(confApi.admin.getEnvironments());
    const {environments} = yield call(getData(response));
    yield put(getAll(environments));
}

// eslint-disable-next-line import/prefer-default-export
export function* watchGetAllEnvironments() {
     yield takeEvery(getAll().type, getAllEnvironments);
}

import { createSlice } from '@reduxjs/toolkit'

const environmentSlice = createSlice({
    name: 'environments',
    initialState: [],
    reducers: {
        getAll: (state, action) => {
            state = action.payload
        },
    },
  })

  export const {getAll} = environmentSlice.actions

  export const { getAllSuccess } = environmentSlice.actions;
  
  export default environmentSlice.reducer

  export const environmentSelector = (state) => state.environments

import {all} from 'redux-saga/effects'
import {watchGetAllEnvironments} from './environments/environmentSaga'

export default function* rootSaga() {
    yield all([
        watchGetAllEnvironments(),
    ])
  }

【问题讨论】:

    标签: redux-saga redux-toolkit


    【解决方案1】:

    如果您有兴趣创建可以解析/拒绝异步 thunk 操作的 saga,请查看我创建并使用的 saga-toolkit 包。

    slice.js

    import { createSlice } from '@reduxjs/toolkit'
    import { createSagaAction  } from 'saga-toolkit'
    
    const name = 'example'
    
    const initialState = {
      result: null,
      loading: false,
      error: null,
    }
    
    export const fetchThings = createSagaAction(`${name}/fetchThings`)
    export const doSomeMoreAsyncStuff = createSagaAction(`${name}/doSomeMoreAsyncStuff`)
    
    const slice = createSlice({
      name,
      initialState,
      extraReducers: {
        [fetchThings.pending]: () => ({
          loading: true,
        }),
        [fetchThings.fulfilled]: ({ payload }) => ({
          result: payload,
          loading: false,
        }),
        [fetchThings.rejected]: ({ error }) => ({
          error,
          loading: false,
        }),
      },
    })
    
    export default slice.reducer
    

    sagas.js

    import { call } from 'redux-saga/effects'
    import { takeLatestAsync, takeEveryAsync, putAsync } from 'saga-toolkit'
    import API from 'hyper-super-api'
    import * as actions from './slice'
    
    function* fetchThings() {
      const result = yield call(() => API.get('/things'))
    
      const anotherResult = yield putAsync(actions.doSomeMoreAsyncStuff()) // waits for doSomeMoreAsyncStuff to finish !
    
      return result
    }
    
    function* doSomeMoreAsyncStuff() {
      ...
      return 'a value for another result'
    }
    
    export default [
      takeLatestAsync(actions.fetchThings.pending, fetchThings), // takeLatestAsync: behaves a bit like debounce
      takeEveryAsync(actions.doSomeMoreAsyncStuff.pending, doSomeMoreAsyncStuff), // each action will start a new saga thread
    ]
    

    【讨论】:

    • 嘿,这很酷,但我并不真正理解用例,因为它似乎主要完成了 Toolkit createAsyncThunk 已经完成的工作。我错过了什么?是什么用例促使您创建了这个?
    • 这使您可以在 redux-toolkit 生态系统中轻松使用 sagas,类似于 thunk。 Sagas 允许您管理更复杂的应用程序流。例如,您可以等待其他 sagas、动作开始、完成,基本上将 sagas 视为线程,redux-sagas 允许您对这些线程做任何您想做的事情。实现一个复杂的应用程序真是太棒了,相信我。 :)
    • 我在项目页面中添加了更多示例。
    • 打字稿的任何解决方案?
    • @haxpanel 我也有类似的问题,但无法使用 saga-toolkit 发出 api 请求。已经发布在 * *.com/questions/71545338/… 上。你能看看这个吗?
    【解决方案2】:

    您似乎只接受了两次getAll().type - 一次是在watchGetAllEnvironments,一次是在getAllEnvironments

    这意味着watchGetAllEnvironments 将执行getAllEnvironments,但这将立即暂停并等待另一个getAll 动作被调度,这可能永远不会发生。

    所以您可能想删除getAllEnvironments 中的第一个take

    另外,你可以take(getAll),不需要take(getAll().type)

    【讨论】:

    • 哦,对不起,我在调试时意外添加了,即使删除它也不起作用,它永远不会得到 getAllEnvironments。我什至尝试过更改 watchGetAllEnvironments 正在监听的动作类型。
    • 好的。下一件事:如果第二个 sn-p 是您的 rootSaga.js,则您正在从中导入 undefined。它正在导出命名导出watchGetAllEnvironments,但您只导入默认导出并将其命名为sagas
    • 我有一个 rootSaga.js,没有添加到堆栈溢出中,我现在已经添加了。很抱歉错过信息。
    • 我的工作非常简单。也许将您的代码与此代码框进行比较? codesandbox.io/s/blissful-keller-6h4bw?file=/src/index.js 另外,在任何地方注销你的导入,也许你在某处有一个循环导入,这会产生一些东西 undefined
    • 你的权利,我只是尝试在不同的应用程序上执行此操作,它可以工作,我可能在上面的代码中遗漏了一些东西。感谢您的所有帮助,至少我现在知道这是可能的!
    【解决方案3】:

    官方文档可能会有所帮助,

    import { applyMiddleware, createStore } from 'redux'
    import { composeWithDevTools } from 'redux-devtools-extension'
    import thunkMiddleware from 'redux-thunk'
    
    import monitorReducersEnhancer from './enhancers/monitorReducers'
    import loggerMiddleware from './middleware/logger'
    import rootReducer from './reducers'
    
    export default function configureStore(preloadedState) {
      const middlewares = [loggerMiddleware, thunkMiddleware]
      const middlewareEnhancer = applyMiddleware(...middlewares)
    
      const enhancers = [middlewareEnhancer, monitorReducersEnhancer]
      const composedEnhancers = composeWithDevTools(...enhancers)
    
      const store = createStore(rootReducer, preloadedState, composedEnhancers)
    
      if (process.env.NODE_ENV !== 'production' && module.hot) {
        module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))
      }
    
      return store
    }
    

    来源:official example

    【讨论】:

      【解决方案4】:

      如果这对任何新人有帮助的话,我发现 redux-toolkit 很酷,但仍然有点复杂,尤其是当你投入 sagas 时(起初这本质上是异步魔法和神秘)。我最终制作了类似的东西,我称之为saga slice。它是 redux 工具包的衍生产品,但从 getgo 中添加了 sagas。你基本上有你的 reducer,它操纵状态,你的 sagas,它使异步调用和调度其他状态。为了简化您设置的示例,它最终看起来像这样:

      import { put } from "redux-saga/effects";
      import { createModule } from 'saga-slice';
      
      const environmentSlice = createModule({
          name: 'environments',
          initialState: [],
          reducers: {
              getAll: () => {},
              getAllSuccess: (state, payload) => {
                  
                  state.splice(0, state.length);
      
                  payload.forEach(data => state.push(data));
              },
          },
      
          sagas: (A) => ({
              *[A.getAll]({ payload }) {
                  
                  
                  const response = yield call(confApi.admin.getEnvironments());
                  const { environments } = yield call(getData(response));
                  
      
                  yield put(A.getAllSuccess(environments));
              }
          })
      });
      

      这将基本上与您尝试做的事情相同,而无需混淆 watchGetAllEnvironmentsrootSaga 的东西(我觉得这很令人费解)。 Saga 切片可配置为使用takeEverytakeOne 或您想使用的任何其他效果,而无需太多麻烦。它是配置对象,而不是yield takeEvery()。我还包括帮助程序来促进您正在做的一些事情,包括一个axios 包装器,它与 saga 的任务取消、流线请求生命周期以及基于标准 REST 原则构建默认 CRUD 模块相关联。查看文档以获得更全面的解释,并随时打开问题!

      【讨论】: