TypeScript 和 React:可重用的通用操作/组件
TypeScript & React: reusable generic actions / components
我将 TypeScript 与 React 和 useReducer 结合使用,我想以类型安全的方式定义 reducer Actions。
Action 的最简单近似是:
type Action = {name : string, payload : any}
更精确的版本需要联合类型:
type Action =
| {name : "setColumns", payload: string[]}
| {name : "toggleColumn", payload: string}
...
到目前为止一切顺利。然后我想定义依赖于 Action 或更确切地说是它的派生物 React.Dispatch<Action>
的组件。有两种方法可以做到这一点:
- 接受(多个)仿制药
- 定义更广泛的类型
方法 1) 在理论上更加类型安全,但在实践中更加冗长和复杂。
方法 2) 可以很好地平衡安全性和复杂性。
两种样式的 Pager
组件道具示例:
// 1)
export type PagerProps1 <Page extends number, Limit extends number> = {
page : Page // -- narrower types
limit : Limit
pagesTotal : number
}
// 2)
export type PagerProps2 = {
page : number // -- wider types
limit : number
pagesTotal : number
}
^ 现在可以定义 Pager2
并将其移动到库中,而不依赖于特定于应用程序的 Page
和 Limit
。并且没有泛型。那是提供必要上下文的介绍。
问题来自 React.Dispatch
。这是在存在更精确版本的地方模仿通用调度重用的测试用例:
type Action =
| {name : "setColumn"}
| {name : "toggleColumn"}
type OpaqueAction1 = {name : any} // will work
type OpaqueAction2 = {name : string} // will not work
type Dispatch = React.Dispatch<Action>
type OpaqueDispatch1 = React.Dispatch<OpaqueAction1> // will work
type OpaqueDispatch2 = React.Dispatch<OpaqueAction2> // will not work
export const DemoComponent = () => {
const dispatch = React.useReducer(() => null, null)[1]
const d0 : Dispatch = dispatch
const d1 : OpaqueDispatch1 = d0 // ok
const d2 : OpaqueDispatch2 = d0 // type error
}
错误如下:
TS2322: Type 'Dispatch<Action>' is not assignable to type 'Dispatch<OpaqueAction2>'.
Type 'OpaqueAction2' is not assignable to type 'Action'.
Type 'OpaqueAction2' is not assignable to type '{ name: "toggleColumn"; }'.
Types of property 'name' are incompatible.
Type 'string' is not assignable to type '"toggleColumn"'.
^ 但在上面的代码中我们实际上将 "toggleColumn"
分配给了 string
。出了点问题。
这里是沙箱:https://codesandbox.io/s/crazy-butterfly-yldoq?file=/src/App.tsx:504-544
您没有将 "toggleColumn"
分配给 string
,您正在将 Dispatch<Action>
分配给 Dispatch<OpaqueAction2>
。
问题是Dispatch<Action>
是一个函数,只能处理带有name
属性"toggleColumn"
的参数,而Dispatch<OpaqueAction2>
是一个函数可以处理具有任何 string
类型的 name
属性 的参数。该赋值意味着 Dispatch<Action>
也应该能够处理任何 string
类型,但它不能。
函数 (...args: T) => R
可分配给 (...args: U) => R
当且仅当 U
可分配给 T
。这就是错误消息的前两行颠倒类型顺序的原因:
Type 'Dispatch<Action>' is not assignable to type 'Dispatch<OpaqueAction2>'.
Type 'OpaqueAction2' is not assignable to type 'Action'.
我将 TypeScript 与 React 和 useReducer 结合使用,我想以类型安全的方式定义 reducer Actions。
Action 的最简单近似是:
type Action = {name : string, payload : any}
更精确的版本需要联合类型:
type Action =
| {name : "setColumns", payload: string[]}
| {name : "toggleColumn", payload: string}
...
到目前为止一切顺利。然后我想定义依赖于 Action 或更确切地说是它的派生物 React.Dispatch<Action>
的组件。有两种方法可以做到这一点:
- 接受(多个)仿制药
- 定义更广泛的类型
方法 1) 在理论上更加类型安全,但在实践中更加冗长和复杂。 方法 2) 可以很好地平衡安全性和复杂性。
两种样式的 Pager
组件道具示例:
// 1)
export type PagerProps1 <Page extends number, Limit extends number> = {
page : Page // -- narrower types
limit : Limit
pagesTotal : number
}
// 2)
export type PagerProps2 = {
page : number // -- wider types
limit : number
pagesTotal : number
}
^ 现在可以定义 Pager2
并将其移动到库中,而不依赖于特定于应用程序的 Page
和 Limit
。并且没有泛型。那是提供必要上下文的介绍。
问题来自 React.Dispatch
。这是在存在更精确版本的地方模仿通用调度重用的测试用例:
type Action =
| {name : "setColumn"}
| {name : "toggleColumn"}
type OpaqueAction1 = {name : any} // will work
type OpaqueAction2 = {name : string} // will not work
type Dispatch = React.Dispatch<Action>
type OpaqueDispatch1 = React.Dispatch<OpaqueAction1> // will work
type OpaqueDispatch2 = React.Dispatch<OpaqueAction2> // will not work
export const DemoComponent = () => {
const dispatch = React.useReducer(() => null, null)[1]
const d0 : Dispatch = dispatch
const d1 : OpaqueDispatch1 = d0 // ok
const d2 : OpaqueDispatch2 = d0 // type error
}
错误如下:
TS2322: Type 'Dispatch<Action>' is not assignable to type 'Dispatch<OpaqueAction2>'.
Type 'OpaqueAction2' is not assignable to type 'Action'.
Type 'OpaqueAction2' is not assignable to type '{ name: "toggleColumn"; }'.
Types of property 'name' are incompatible.
Type 'string' is not assignable to type '"toggleColumn"'.
^ 但在上面的代码中我们实际上将 "toggleColumn"
分配给了 string
。出了点问题。
这里是沙箱:https://codesandbox.io/s/crazy-butterfly-yldoq?file=/src/App.tsx:504-544
您没有将 "toggleColumn"
分配给 string
,您正在将 Dispatch<Action>
分配给 Dispatch<OpaqueAction2>
。
问题是Dispatch<Action>
是一个函数,只能处理带有name
属性"toggleColumn"
的参数,而Dispatch<OpaqueAction2>
是一个函数可以处理具有任何 string
类型的 name
属性 的参数。该赋值意味着 Dispatch<Action>
也应该能够处理任何 string
类型,但它不能。
函数 (...args: T) => R
可分配给 (...args: U) => R
当且仅当 U
可分配给 T
。这就是错误消息的前两行颠倒类型顺序的原因:
Type 'Dispatch<Action>' is not assignable to type 'Dispatch<OpaqueAction2>'.
Type 'OpaqueAction2' is not assignable to type 'Action'.