【问题标题】:Typescript error when using immer for a Redux' reducer将 immer 用于 Redux 的减速器时出现打字稿错误
【发布时间】:2020-04-20 13:05:57
【问题描述】:

我正在尝试在 Typescript 项目中将 Immer 与 Redux 一起使用。

Redux reducer 可以这样写:

Reducer<State, Action>

我应该能够用Immer's produce function 创建一个等效的reducer,它允许我们改变一个草稿副本。但类型不匹配。错误:“Undefined is not assignable to ...”这可能与 redux 在开始时将 undefined 作为 state 的参数传递有关。如果减速器不匹配 Reducer&lt;State, Action&gt; 我不能将它传递给 createStore

打字稿版本 3.5.3。

我该如何优雅地解决这个问题?

【问题讨论】:

    标签: typescript redux immer.js


    【解决方案1】:

    通常,produce 返回状态,因此我们不需要返回任何内容。悬停会导致打字稿出错,因为如果减速器中的任何情况不返回任何内容,打字稿将评估它,减速器的返回值类型之一可能是undefined

    解决方案是在每个案例结束时,你显式返回状态:

    return state,
    

    【讨论】:

      【解决方案2】:

      这对我有用:

       import produce from 'immer';
      
      const reducer = (state = initialState, action: AnyAction) : State  => {
         return produce(state, (draft) => {
            switch (action.type) {
              case 'Execute':
                draft.isFetching = true;
                break;
              case 'Success':
                draft.isFetching = false;
                break;
              case 'Failure':
                draft.isFetching = false;
                break;
              default:
                draft;
            }
          });
      
      export default reducer;
      

      【讨论】:

        【解决方案3】:

        我也遇到了同样的问题,但我记得官方的 Redux Toolkit 有一个 createReducer 助手,它在内部使用了 Immer。查看他们的代码可以了解他们是如何解决这个问题的。

        import createNextState, { Draft } from 'immer'
        
        export function createReducer<S>(
          initialState: S,
          ...): Reducer<S> {
          ...
        
          return function(state = initialState, action): S {
            // @ts-ignore createNextState() produces an Immutable<Draft<S>> rather
            // than an Immutable<S>, and TypeScript cannot find out how to reconcile
            // these two types.
            return createNextState(state, (draft: Draft<S>) => {
              const caseReducer = actionsMap[action.type]
              return caseReducer ? caseReducer(draft, action) : undefined
            })
          }
        }
        

        (见code here。为简洁起见,我用“...”缩短了不相关的部分)。

        您可以在这里看到他们的辅助函数返回一个正常结构的减速器,其主要工作只是将state 传递给createNextDraft 函数。有趣的是,这只是从 Immer 导入 default,所以它只是 produce 函数的别名。

        以下是我如何使用它的示例:

        import produce, { Draft } from 'immer';
        
        const reducer: Reducer<State, AnyAction> = (state: State = initialState, action: AnyAction) => {
           return produce(state, (draft: Draft<State>) => {
              switch (action.type) {
                case 'Execute':
                  draft.isFetching = true;
                  break;
                case 'Success':
                  draft.isFetching = false;
                  break;
                case 'Failure':
                  draft.isFetching = false;
                  break;
              }
            });
        

        【讨论】:

          【解决方案4】:

          当使用 produce 函数的柯里化形式(将函数作为第一个参数传递)时,您可以传递第二个参数作为默认值。当 Redux 在初始化时使用未定义的值调用您的 reducer 时,这可以解决类型不匹配问题。

          const initState = {
              text: "hello"
          }
          
          type State = {
              text: string
          } 
          type Action = {
              type: "newUrl"
              text: string 
          }
          
          const reducer: Reducer<State, Action> = produce( 
              (draftSt: Draft<State>, action: Action) => {
                  switch(action.type){
                      case "newUrl": 
                          draftSt.text = action.text
                          break 
                  }
              }, 
              initState
          )
          

          reducer 可以安全地传递给createStoreconfigureStore

          【讨论】:

          • 如果State 是一个具体类型,我似乎只能让它工作。如果将其换成泛型类型,则会返回错误。举个例子:function injectImmer = &lt;S&gt;(reducer: Reducer&lt;S&gt;, initialState: S) =&gt; produce((draft: Draft&lt;S&gt;, action) =&gt; { reducer(draft, action); }, initialState);这会导致initialState的错误,说它必须是Immutable&lt;S&gt;的类型
          • @AndrewCraswell,这很有挑战性,但 Immer 还导出了一个通用的...不可变的。将其用于参数。这应该工作:const injectImmer = &lt;S extends object&gt;(reducer: (s:Immutable&lt;S&gt;, a:any)=&gt;S, initialState: Immutable&lt;S&gt;) =&gt; produce( (draft: Draft&lt;S&gt;, action) =&gt; { reducer(draft, action); }, initialState )
          • @AndrewCraswell 基本上你可以将常规类型传递给不可变/deepReadonly 类型,但不能反过来
          【解决方案5】:

          我还没有找到让它工作的方法,目前我从减速器返回产品

          const reducer = (state, action) => produce(state, (draft) => {})
          

          【讨论】:

          • 奇怪。我希望像 Immer 这样受欢迎的库能够解决这样的问题。
          • @BenCarp 其实我只是搞砸了你的例子,似乎如果你包含一个default case with return state 它会编译,你能试试告诉我吗?
          • @BenCarp 确保为 reducer 的初始调用提供默认状态
          • @BenCarp 如果您不将草稿包装在Draft 类型中,它会起作用吗?
          猜你喜欢
          • 2021-03-20
          • 2021-03-11
          • 2021-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-09
          • 2019-02-08
          相关资源
          最近更新 更多