我在键入以下自定义React hook
时遇到问题,我是TypeScript的新手,这引起了一些混乱。
const useStateCallback = (initialState: any) => {
const [state, setState] = useReducer<Reducer<any, any>>((state, newState) => ({ ...state, ...newState }), initialState)
const cbRef = useRef(null)
const setStateCallback = (state, cb) => {
cbRef.current = cb
setState(state)
}
useEffect(() => {
if (cbRef.current) {
cbRef.current(state)
cbRef.current = null
}
}, [state])
return [state, setStateCallback]
}
我应该在这里使用any
,如果可以的话,如何正确使用any
?
由于这是通用功能,可以在任何地方使用,如何正确输入?
我在示例中添加了一些尝试,并且您可以看到停止了,因为从我的角度来看,除了any
类型之外,结果将一无所有。
答案 0 :(得分:1)
首先,您需要使此useStateCallback
接受代表您的状态的通用参数。您将大量使用该参数。我们称该状态为S
。
function useStateCallback<S>(initialState: S) { ... }
接下来是减速器。看起来您只需要一个接受S
中的Partial
并合并到状态中的动作。因此,对于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
)
或者您可以键入reducer函数的参数,然后会推断出这些类型,这看起来更干净一些,恕我直言。
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
可以告诉Typescript将数组视为常量并将这些类型锁定在适当的位置。
将所有内容放在一起,您将得到:
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>
}