对复杂状态使用Hook useReducer的正确方法

时间:2020-04-30 19:16:41

标签: reactjs typescript react-hooks

试图赶上React Hooks。我读到,他们建议在处理复杂的useReducer时使用挂钩state。但是我的怀疑来自以下方案:

使用 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:对每个字段使用钩子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) => {...prev, person: {username: 'New', email: 'example@gmail.com'}})或使其适应任何字段修改。我什至可以一次更新几个字段。

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

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

我觉得这很容易管理,但是除了在繁琐的过程中在Typescript中为每个reduce函数设置调度类型的过程之外,我看不到必须用多行开关来设置reduce函数的意义。您可以只使用setState并完成它。

选项4::对整个状态使用一个useReducer

const [state, dispatch] = useReducer(generalReducer)

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

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

任何对此问题的热烈欢迎!在此先感谢您,对我的英语感到抱歉

3 个答案:

答案 0 :(得分:1)

在很多情况下,我通常会选择选项2或选项4。如果您的数据易于更新,不嵌套且字段之间没有相互依存关系,那么选择2很好。

选项4很棒,因为您可以轻松地获得许多更复杂的行为。即为异步提取操作设置数据时,更新提取和错误。这也很棒,因为您可以将调度功能向下传递给子组件,以便子组件用来更新状态。

这是我使用redux工具包拼凑出的一个示例,该示例强烈地键入了一个将reducer用作reducer的reducer。

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>;

答案 1 :(得分:1)

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

我认为您应该将选项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原理的简化版本。

答案 2 :(得分:1)

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

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,
    }
}

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