首先,你需要做这个useStateCallback
接受代表您的状态的通用参数。您将经常使用该参数。我们称之为S
对于状态。
function useStateCallback<S>(initialState: S) { ... }
接下来是减速机。看来您只需要一个接受Partial https://www.typescriptlang.org/docs/handbook/utility-types.html#partialt of S
并入国家。所以对于两个通用参数Reducer
we use 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)
for 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>
}
操场 https://www.staging-typescript.org/play?#code/JYWwDg9gTgLgBAJQKYEMDGMA0cDecCuAzksgCb5pJTZElIBmNxAovfUhtmRVXAL5x6UCCDgByKKgxiAUDPr4AdhmARFBYgGUYKGEgDCKADZGARugDWAHk0A+ABTBFwGMGPbdSAFxxNASlwZODgguDQ1QngAbUjPbGIYDz0AXTgAXg06ckooe1Dg+1i9H01sRSQAdyTvOAAFFFg3IxtbALTbOHs8ADpeoqRsXu7yqp09fj9MfLgnFybq0L9Q8MVIsNNkenTMzat7QrGa-3SOgDcIYFIAgB84RXwTB3uTJdCFZVc1OATqwxNzNAWA6eHz1RrGFrYNCmHzA4q+NpnC5XQLBYLQzbdND4KCSRTwDLQ6Y-Q5wpBLYJ8OTBWisdgYfaI1Fo4BbewYhhYnF4mABHDTdEbTnY3FIfFkilowWYkU87bPIzTKmU7AxQ7JV7BSQwHHqNVxb5IRKHP5mSypFCEMIRGAyKkyAD0DrgABUAJ5gJDfFDsDQoADmSHkShUX30Ikg5XFfOWNrg+r08SNv2MZsBqQytBT-0sXUEEAgPjE5igYgm1OtqwgRiQ3SMEH9ZO69ALmsNxs8poBQLwLcL4nMAC8y3wypVqidmeiItXa-XGyNqs3W6E+G3tbq4FZSMBTrYcP1lxA+FYHTu93agA