【问题标题】:The right way to use the Hook useReducer for a complex state复杂状态下使用 Hook useReducer 的正确方法
【发布时间】:2020-08-15 06:29:17
【问题描述】:

试图赶上React Hooks。我读到他们建议在处理复杂的state 时使用 Hook useReducer。但我的怀疑是从以下方案开始的:

使用 React + Typescript,假设我有一个包含多个字段的状态(我将举一个带有类的示例):

type Person = {
   username: string,
   email: string
}

type Pet = {
   name: string,
   age: number
}

this.state: MyState = {
    person: Person,
    pet: Pet,
    loading: boolean
}

如果我想使用基于 Hooks 的新方法来处理这种状态,我可以考虑以下几种选择:

选项 1:为每个字段使用 Hook useState

const [person, setPerson] = useState<Person>(null)
const [pet, setPet] = useState<Pet>(null)
const [loading, setLoading] = useState<boolean>(false)

这种方法的缺点是可扩展性低,而且我的一些真实状态至少有15个字段,难以管理。

选项 2:对整个对象使用单个 setState

const [state, setState] = useState<MyState>({
    person: null,
    pet: null,
    loading: false
})

这是对我来说似乎最简单的方法,我可以简单地执行setState((prev) =&gt; {...prev, person: {username: 'New', email: 'example@gmail.com'}}) 或使其适应任何字段修改。我什至可以一次更新多个字段。

选项 3: 为每个复杂字段使用 useReducer,为每个复杂字段传递一个特定的 reducer,为简单字段使用 useState

const [person, dispatchPerson] = useReducer<Person>(personReducer)
const [pet, dispatchPet] = useReducer<Pet>(petReducer)
const [loading, setLoading] = useState<boolean>(false)

我发现这个可以管理,但我看不出必须使用多行开关设置 reduce 函数的意义,除了在 Typescript 中为每个 reduce 函数设置调度类型的繁琐过程你可以只使用 setState 并完成它。

选项 4:为整个州使用一个 useReducer

const [state, dispatch] = useReducer(generalReducer)

主要问题是reducer的类型,想想15个字段,所有类型和更新它们的信息结构都不同。在 Typescript 中指定类型无法扩展或不清楚。有几篇关于此的文章,没有一篇以干净的方式解决问题 (example 1),或者它们非常简单,不适用于问题 (example 2)。

处理此类案件的最佳方法是什么?该州的字段数量很大并且可以具有多个深度级别。是否有代表这些案例的良好做法或任何官方示例?带有数字字段的示例用于处理博主或官方文档人员非常喜欢的简单计数器并不是很有帮助。

任何关于这个主题的观点都非常受欢迎!在此先感谢,对我的英语感到抱歉

【问题讨论】:

  • 对于您想从应用程序中的任何位置get/set 的状态,Redux 策略对我来说似乎是合理的,您的选项 4,可选择使用选项 3。应用程序有一个带有一个减速器的大型状态树。您可以将责任分解为子减速器,最终使用 combineReducers 组合以创建总体减速器。对于本地组件状态,选项 1 和 2 对我来说很有意义。我倾向于使用选项 1。我同意你所说的一切。我还没有看到任何新的、创新的方式来使用useReducer
  • 这是一种解脱的读物,没有一种神奇的方式可以做我错过的事情。感谢您的评论!
  • 我偶然发现了这个 useMethods 减速器并想到了你。这是一种有趣的搭建东西的方式。
  • 酷!非常感谢!

标签: reactjs typescript react-hooks


【解决方案1】:

我们最近使用自定义钩子处理了类似的情况,因为 reducer 变得太不可预测了。想法是在自定义钩子中创建我们的状态, 然后我们创建了在状态上运行的类型安全助手,然后我们公开了状态和助手。

interface State{
  count: number;
}

interface ExportType{
  state: State;
  add: (arg: number)=>void;
  subtract: (arg: number)=>void;
}

export default function useAddRemove(): ExportType {

    const [state, setState] = useState<State>({
        count: 0
    })
    
    function add(arg:number){
      setState(state=>({...state, count: state.count+arg}))
    }
    
    function subtract(arg:number){
      setState(state=>({...state, count: state.count-arg}))
    }


    return {
        state,
        add,
        subtract,
    }
}

如果您有任何建议,请告诉我。

【讨论】:

  • 这是一个很酷的方法!这是封装在自定义 Hook 中的选项 1!它会比计数器更复杂,有很多方法来更新复杂的状态,但是在组件中写入它更容易维护。我会考虑的!非常感谢!
  • 欢迎@Genarito。乐于助人。
  • 您可能希望将addsubtract 包装成useCallback 以避免在每个useAddRemove 上更改其身份,对吧?
【解决方案2】:

我认为你的观察是正确的。

我认为您应该将选项 1 用于简单状态(例如,您只有几个项目需要跟踪),将选项 2 用于复杂状态(大量项目或嵌套项目)。

选项 1 和 2 也是最具可读性和声明性的。

此处讨论选项 #2:https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

当您有多种类型的操作时,useReducer 会更有用。如果您只是更新状态(一种操作),那么操作就大材小用了。此外,如果您基于先前的状态(不仅仅是替换部分状态)执行计算或转换,那么 useReducer 也很有用。如果你熟悉 Redux,useReducer 是 Redux 原理的简化版。

【讨论】:

  • 感谢您的回答和链接!很有帮助
【解决方案3】:

对于很多州,我通常会选择选项 2 或选项 4。如果您的数据易于更新、不嵌套且字段之间不存在相互依赖关系,则选项 2 很好。

选项 4 很棒,因为您可以轻松获得很多更复杂的行为。 IE。为异步获取操作设置数据时更新获取和错误。这也很棒,因为您可以将调度函数传递给子组件,以供它们用于更新状态。

这是我使用 redux 工具包强类型化一个 reducer 的示例,该 reducer 使用 combineReducers 用于 useReducer。

https://codesandbox.io/s/redux-toolkit-with-react-usereducer-2pk6g?file=/src/App.tsx

  const [state, dispatch] = useReducer<Reducer<ReducerType>>(reducer, {
    slice1: initialState1,
    slice2: initialState2
  });


const initialState1: { a: number; b: string } = { a: 0, b: "" };
const slice1 = createSlice({
  name: "slice1",
  initialState: initialState1,
  reducers: {
    updateA(state, action: PayloadAction<number>) {
      state.a += action.payload;
    },
    updateB(state, action: PayloadAction<string>) {
      state.b = action.payload;
    }
  }
});

const initialState2: { c: number; d: number } = { c: 0, d: 0 };
const slice2 = createSlice({
  name: "slice2",
  initialState: initialState2,
  reducers: {
    updateC(state, action: PayloadAction<number>) {
      state.c += action.payload;
    },
    updateD(state, action: PayloadAction<number>) {
      state.d += action.payload;
    },
    updateCDD(state, action: PayloadAction<number>) {
      state.c += action.payload;
      state.d += action.payload * 2;
    }
  }
});

const reducer = combineReducers({
  slice1: slice1.reducer,
  slice2: slice2.reducer
});
type ReducerType = ReturnType<typeof reducer>;

【讨论】:

  • 感谢您的回答!不幸的是,我不能/不想使用 Redux,这就是为什么我更喜欢纯 React + Typescript 解决方案。但这让你的观点更清楚了。再次感谢您
猜你喜欢
  • 2022-01-10
  • 2020-01-21
  • 1970-01-01
  • 2021-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-25
相关资源
最近更新 更多