RTK для использованияReducer Hook: руководство

RTK для использованияReducer Hook: руководство

23 ноября 2022 г.

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

Будет бесполезно хранить данные в глобальном состоянии и изменять данные только из одного куска приложения, которое уже логически объединено, я имею в виду компонент формы и его дочерние элементы. В этом сценарии хук useReducer будет красиво смотреться на своем месте.

Хук useReducer — это способ хранения и управления данными в хранилище для конкретного компонента и его дочерних элементов. Итак, в базовом сценарии, когда нужды в редуксе нет, я буду использовать сам хук без тяжелых библиотек только для маленькой фичи.

const [type, dispatch] = useReducer(reducer, initializer(editedType));

Для каждого действия потребуется написать тип действия и полезную нагрузку. Он занимает довольно много строк кода в файле только для аннотации действий и их вызова.

interface ChangeNameAction {
    type: 'CHANGE_NAME';
    name: string;
}

Чтобы держать весь редьюсер в одном месте, я создам еще один хук и верну обратно только те функции, которые будут отправлять в нем требуемые действия для изменения состояния. И хук вернет состояние и функции для изменения состояния. Затем функция смены имени:

const setName = (name: string) => {
    dispatch({ type: 'CHANGE_NAME', name });
};

Тогда функция редуктора будет иметь стандартную реализацию switch-case; Я обернул его функцией product из библиотеки Immer. Это чтобы избежать ошибок, совладав с полным состоянием. Но да, в самом оригинальном редюсере, где объект состояния без множественных вложенных объектов, я буду использовать только оператор распространения.

const reducer: Reducer<TypeDraft, TypeEditorAction> = produce(
    (draft: TypeDraft, action: CustomTypeEditorAction) => {
        switch (action.type) {
            case 'CHANGE_NAME':
                draft.name = action.name;
                break;
            ...
            default:
        }
    }
);

И новый хук будет таким:

export function useTypeEditor(editedType?: EditedType) {
    const [type, dispatch] = useReducer(reducer, initializer(editedType));

    const setName = (name: string) => {
        dispatch({ type: 'CHANGE_NAME', name });
    };

    return {
        type,
        setName
    };
}

Поскольку редюсер — это просто чистая функция, вместо того, чтобы создавать редюсер вручную и оборачивать его в функцию производства Immer, гораздо удобнее использовать функцию RTK createReducer или createSlice для useReducer хук.

const reducer = createReducer(initialState, builder =>
    builder
        .addCase(changeNameAction, (state, { payload }) => {
            state.name = payload;
        })
);

Действия также создаются из функции createAction, а не описываются через интерфейс.

const name = 'typeForm';
const changeNameAction = createAction<string>(`${name}/changeName`);

Это будет тот же редуктор, что и в результате функции product, или просто чистая функция-редуктор, но в более приличном виде. Мне не нужно помнить, как правильно обрабатывать действие. И в конце для компонента это будет еще один хук, который вы можете просто использовать.

const TypeEditorForm: React.FC = () => {

  const editor = useTypeEditor(type);
  ...
  return (<>...</>)
}

Кстати, здесь type и initializer, чтобы правильно установить начальное состояние, например, если тип уже был создан, ему нужно инициализировать состояние из него.


Фото Стива Джонсона на Unsplash


Оригинал