Во-первых, вам нужно заставить этот useStateCallback
принять общий c параметр, который представляет ваше состояние. Вы собираетесь часто использовать этот параметр. Мы назовем это S
для состояния.
function useStateCallback<S>(initialState: S) { ... }
Далее идет редуктор. Похоже, вам нужно только одно действие, которое принимает Partial
из S
, которое объединяется с состоянием. Итак, для двух общих параметров c в Reducer
мы используем S
для состояния и Partial<S>
для действия.
const [state, setState] = useReducer<Reducer<S, Partial<S>>>(
(state, newState) => ({ ...state, ...newState }),
// state is implicitly typed as: S
// newState is implicitly typed as: Partial<S>
initialState
)
Или вы можете ввести аргументы функции редуктора и эти типы будут выведены, что выглядит немного чище, ИМХО.
const [state, setState] = useReducer(
(state: S, newState: Partial<S>) => ({ ...state, ...newState }),
initialState
)
Для создания ссылки нам нужно дать ей тип функции обратного вызова, объединенной с null
, поскольку она не всегда может содержат значение:
const cbRef = useRef<((state: S) => void) | null>(null)
для setStateCallback
, нам нужно принять Partial<S>
для слияния с полным состоянием и обратный вызов, который имеет полное состояние в качестве единственного аргумента:
function setStateCallback(state: Partial<S>, cb: (state: S) => void) {
cbRef.current = cb
setState(state)
}
Ваш эффект должен быть таким, как есть.
Последнее, что нужно сделать, это изменить ваш возврат на:
return [state, setStateCallback] as const
Это необходимо, потому что машинописный текст видит это как массив по умолчанию, но вы хотите, чтобы это был кортеж. Вместо массива (S | Callback)[]
вы хотите, чтобы это был кортеж ровно с двумя элементами типа [S, Callback]
. Добавление as const
к массиву указывает машинописному тексту рассматривать массив как константу и фиксировать эти типы в правильных позициях.
Собирая все это вместе, вы получаете:
import React, { useReducer, useRef, useEffect, Reducer } from 'react'
function useStateCallback<S>(initialState: S) {
const [state, setState] = useReducer<Reducer<S, Partial<S>>>(
(state, newState) => ({ ...state, ...newState }),
initialState
)
const cbRef = useRef<((state: S) => void) | null>(null)
function setStateCallback(state: Partial<S>, cb: (state: S) => void) {
cbRef.current = cb
setState(state)
}
useEffect(() => {
if (cbRef.current) {
cbRef.current(state)
cbRef.current = null
}
}, [state])
return [state, setStateCallback] as const
}
// Type safe usage
function Component() {
const [state, setStateCallback] = useStateCallback({ foo: 'bar' })
console.log(state.foo)
setStateCallback({ foo: 'baz' }, newState => {
console.log(newState.foo)
})
return <div>{state.foo}</div>
}
Детская площадка