Я пытаюсь заставить некоторые наборы работать на реагирование useReducer .

По сути, у меня есть действие, которое имеет необязательное свойство ( data ), основанное на значении другого свойства, поэтому, если STATUS имеет значение VIEW или EDIT , у действия должно быть свойство data . У меня почти что-то работает, но есть один случай (см. Ниже), где это не удается.

Я предполагаю, что один из способов сделать это — явно установить STATUS.NEW чтобы не требовать дополнительного свойства ( { type: 'SET_STATUS'; status: STATUS.NEW } ), но мне интересно, есть ли лучший способ. Если в будущем я добавлю несколько разных статусов, мне придется указать каждый из них, чтобы не требовалось свойство данных.

Машинопись площадка

 enum STATUS { NEW = 'new', VIEW = 'view', EDIT = 'edit' } /* if status is 'view', or 'edit', action should also contain a field called 'data' */ type Action = | { type: 'SET_STATUS'; status: STATUS } | { type: 'SET_STATUS'; status: STATUS.VIEW | STATUS.EDIT; data: string; } // example actions // CORRECT - is valid action const a1: Action = { type: 'SET_STATUS', status: STATUS.NEW } // CORRECT - is a valid action const a2: Action = { type: 'SET_STATUS', status: STATUS.VIEW, data: 'foo' } // FAILS - should throw an error because `data` property should be required const a3: Action = { type: 'SET_STATUS', status: STATUS.EDIT } // CORRECT - should throw error because data is not required if status is new const a4: Action = { type: 'SET_STATUS', status: STATUS.NEW, data: 'foo' } 

И вторая часть вопроса заключается в том, как я включил бы это в useCallback ниже. Я бы подумал, что useCallback сможет правильно вывести аргументы в соответствующий тип действия.

 /* assume: const [state, dispatch] = useReducer(stateReducer, initialState) */ const setStatus = useCallback( (payload: Omit{amp}lt;Action, 'type'{amp}gt;) ={amp}gt; dispatch({ type: 'SET_STATUS', ...payload }), [], ) /* complains about: Argument of type '{ status: STATUS.EDIT; data: string; }' is not assignable to parameter of type 'Pick{amp}lt;Action, "status"{amp}gt;'. Object literal may only specify known properties, and 'data' does not exist in type 'Pick{amp}lt;Action, "status"{amp}gt;' */ setStatus({ status: STATUS.EDIT, data: 'foo' }) 

Вы можете определить объединение статуй, которые требуют data , а затем исключить их в действии, представляющих все остальные:

 enum STATUS { NEW = 'new', VIEW = 'view', EDIT = 'edit' } type WithDataStatuses = STATUS.VIEW | STATUS.EDIT; type Action = | { type: 'SET_STATUS'; status: Exclude{amp}lt;STATUS, WithDataStatuses{amp}gt; } | { type: 'SET_STATUS'; status: WithDataStatuses; data: string; } // now CORRECT - data is required const a3: Action = { type: 'SET_STATUS', status: STATUS.EDIT } 

Ответ на вторую часть вопроса :-)

Предполагая, что вы определили Actions как предложено @Aleksey L., useCallback можно набрать следующим образом

 // This is overloaded function which can take data or not depending of status interface Callback { (payload: { status: Exclude{amp}lt;STATUS, WithDataStatuses{amp}gt; }): void; (payload: { status: WithDataStatuses; data: string; } ): void; } const [state, dispatch] = React.useReducer(stateReducer, {}) // Explicitly type useCallback with Callback interface const setStatus = React.useCallback{amp}lt;Callback{amp}gt;( (payload) ={amp}gt; dispatch({ type: 'SET_STATUS', ...payload }), [], ) setStatus({ status: STATUS.EDIT, data: 'foo' }) setStatus({ status: STATUS.NEW }) 

Рабочая демка