Правильный способ использования Hook useReducer для сложного состояния - PullRequest
2 голосов
/ 30 апреля 2020

Пытаясь догнать 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
}

Если бы я хотел обработать это состояние с помощью нового подхода на основе хуков, я мог бы подумать о нескольких вариантах:

Вариант 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: использовать useReducer для каждого из сложных полей, передавая указанный c редуктор для каждого, использовать useState для простых

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

Я нахожу это управляемым, но я не вижу смысла настраивать функцию уменьшения с помощью многострочного переключателя, в дополнение к утомительному процессу установка типов диспетчеризации в Typescript для каждой функции сокращения, когда вы можете просто использовать setState и покончить с этим.

Опция 4: использовать один useReducer для всего состояния

const [state, dispatch] = useReducer(generalReducer)

Основная проблема с этим - тип редуктора, - 15 полей, где все типы и структура информации для их обновления различны . Указание типов в Typescript не масштабируется или неясно. Есть несколько статей по этому поводу, и ни одна из них не решает проблему чисто ( пример 1 ), или они чрезвычайно просты и не относятся к проблеме ( пример 2 ) ).

Как лучше всего справляться с делами такого типа? Где количество полей в состоянии велико и может иметь несколько уровней глубины. Существуют ли передовые практики или официальные примеры, представляющие эти случаи? Примеры с числовым полем для работы с простым счетчиком, который так нравится блоггерам или официальным документам, не очень полезен.

Любой свет на эту тему будет более чем приветствоваться! Заранее спасибо и извините за мой Engli sh

Ответы [ 3 ]

1 голос
/ 30 апреля 2020

Я думаю, что ваши наблюдения точны.

Я думаю, что вы должны использовать Вариант 1 для простого состояния (например, у вас есть только несколько элементов для отслеживания) и Вариант 2 для сложного состояния (много элементы или вложенные элементы).

Параметры 1 и 2 также являются наиболее читаемыми и декларативными.

Здесь рассматривается вариант № 2: https://reactjs.org/docs/hooks-faq.html#should -i-use- переменные "одно или много состояний"

useReducer более полезен, когда у вас есть несколько типов действий. Если вы просто обновляете состояние (один тип действия), то действия излишни. Кроме того, useReducer полезен, если вы выполняете вычисления или преобразования на основе предыдущего состояния (а не просто заменяете часть состояния). Если вы знакомы с Redux, useReducer - это упрощенная версия принципов Redux.

1 голос
/ 30 апреля 2020

Недавно мы имели дело с подобной ситуацией, используя кастомный хук, потому что редуктор стал слишком непредсказуемым. Идея состояла в том, чтобы создать наше состояние в пользовательском хуке, затем мы создали помощники типов, работающие с состоянием, а затем раскрыли состояние и помощников.

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

Пожалуйста, дайте мне знать, если у вас есть какие-либо предложения.

1 голос
/ 30 апреля 2020

Я бы обычно go для варианта 2 или 4 для большого количества государства. Вариант 2 хорош, если ваши данные легко обновляются, не являются вложенными и не имеют взаимозависимости между полями.

Вариант 4 хорош, потому что вы можете легко получить много более сложного поведения. Т.е. обновление выборки и ошибки при настройке данных для асинхронной операции выборки. Это также здорово, потому что вы можете передать функцию диспетчеризации дочерним компонентам, чтобы они могли использовать их для обновления состояния.

Вот пример, который я собрал, используя инструментарий redux для строгого ввода редуктора, который использует combReducers для использования в 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>;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...