我想获取一些能够正确确定我的类型何时不兼容的代码,并使用children
而不是prop。这是正确错误的基准:
type Option<T> = {
value: T;
text: string;
}
type SelectProps<T> = {
value: T;
options: Option<T>[];
}
const options = [
{ value: 5, text: 'Five' },
{ value: 10, text: 'Ten'}
];
return <Select value="Text" options={options} />; // ERROR: value isn't type number
但是当我使用孩子时,我似乎并没有出错:
type OptionProps<T> = {
value: T;
children: string;
}
type SelectProps<T> {
value: T;
children: React.ReactElement<OptionProps<T>>;
}
/* No errors here */
<Select value="Text">
<Option value={5}>Five</Option>
<Option value={10}>Ten</Option>
</Select>
我在此codeandbox中放了一个更完整的示例(可以从下面找到沙箱中的代码):https://codesandbox.io/s/throbbing-sun-tg48b-注意renderA
如何正确识别错误,其中renderB
错误地没有错误。
import * as React from 'react';
type OptionA<T> = {
value: T;
text: string;
}
type SelectAProps<T> = {
value: T;
options: OptionA<T>[];
onClick: (value: T) => void;
}
class SelectA<T> extends React.Component<SelectAProps<T>> {
renderOption = (option: OptionA<T>) => {
const { value, text } = option;
const onClick = () => {
this.props.onClick(value)
};
return <div onClick={onClick}>{text}</div>
}
render(): React.ReactNode {
return <div>{this.props.options.map(this.renderOption)}</div>
}
}
type OptionBProps<T> = {
value: T;
children: string;
}
class OptionB<T> extends React.Component<OptionBProps<T>> {}
type SelectBProps<T> = {
value: T;
children: React.ReactElement<OptionBProps<T>>[];
onClick: (value: T) => void;
}
class SelectB<T> extends React.Component<SelectBProps<T>> {
renderOption = (option: OptionB<T>) => {
const { value, children } = option.props;
const onClick = () => {
this.props.onClick(value)
};
return <div onClick={onClick}>{children}</div>
}
render(): React.ReactNode {
return <div>{React.Children.map(this.props.children, this.renderOption)}</div>
}
}
class Main extends React.Component {
onClick(value: string) {
console.log(value);
}
renderA(): React.ReactNode {
const options = [
{ value: 5, text: 'Five' },
{ value: 10, text: 'Ten'}
]
return <SelectA value="Text" options={options} onClick={this.onClick} />
}
renderB(): React.ReactNode {
return (
<SelectB value="Text" onClick={this.onClick}>
<OptionB value={5}>Five</OptionB>
<OptionB value={10}>Ten</OptionB>
</SelectB>
);
}
}
答案 0 :(得分:3)
不幸的是,到目前为止,您无法像示例中所示对children
道具实施类型约束。以前,我实际上认为这种类型检查 是可能的。很好奇,我研究了一下:
TypeScript将通过默认的JSX factory function将您的JSX代码转换为原始的React.createElement
语句。 React.createElement
的返回类型是JSX.Element
(全局JSX命名空间),它扩展了React.ReactElement<any>
。
这意味着,当渲染SelectB<T>
时,您的OptionB
组件将表示为JSX.Element[]
或React.ReactElement<any>[]
。
给出道具类型
type SelectBProps<T> = {
value: T;
children: React.ReactElement<OptionBProps<T>>[];
onClick: (value: T) => void;
}
,TypeScript可以将ReactElement<OptionBProps<T>>[]
与ReactElement<any>[]
进行比较,并且可以成功编译(给定T
的具体类型)。
默认情况下,JSX表达式的结果键入为any。您可以通过指定JSX.Element接口来自定义类型。但是,无法从此接口检索有关JSX的元素,属性或子级的类型信息。这是一个黑匣子。
您可以证明这种行为(JSX表达式解析为JSX.Element
):只需将SelectBProps
替换为:
type SelectBProps<T> = { ... ; children: number[]; ... };
对于每个OptionB,您现在都会收到错误:
类型“元素”不能分配给类型“数字”
从TypeScript 2.8开始,支持本地范围的JSX名称空间,这可以帮助对JSX.Element
使用自定义类型/类型检查。
TypeScript docs on locally scoped JSX namespaces
JSX类型检查由JSX命名空间中的定义驱动,例如JSX.Element用于JSX元素的类型,而JSX.IntrinsicElements用于内置元素。在TypeScript 2.8之前,JSX名称空间应位于全局名称空间中,因此只能在项目中定义它。从TypeScript 2.8开始,将在jsxNamespace(例如React)下查看JSX名称空间,从而允许在一个编译中包含多个jsx工厂。
此外,此非常有用的article中的一句话:
TypeScript支持本地范围的JSX,从而能够支持各种JSX工厂类型和每个工厂正确的JSX类型检查。尽管当前的反应类型仍使用全局JSX命名空间,但将来会有所变化。
-
最后,声明children
类型
JSX.Element
。如果您仍然想进行广泛的类型检查,也许坚持使用props以前的解决方案可能是一个有效的主意?最后,children
只是props
的另一种形式,并且Render Props已经确立为一个坚实的概念。
虽然不能解决您原来的children
类型检查用例,但我希望可以将其清除一些(对我来说,是did)!
欢呼
答案 1 :(得分:0)
我认为向下通过React.Children.map
传递类型信息是导致类型推断混乱的原因。如果您在this.props.children
中查看React.Children.map
的类型,您会发现它具有React.ReactElement<OptionBPropts<T>>
类型,但也有许多其他可能的类型,具有any
。
我会改用渲染道具方法。
import * as React from "react";
type OptionA<T> = {
value: T;
text: string;
};
type SelectAProps<T> = {
value: T;
options: OptionA<T>[];
onClick: (value: T) => void;
};
class SelectA<T> extends React.Component<SelectAProps<T>> {
renderOption = (option: OptionA<T>) => {
const { value, text } = option;
const onClick = () => {
this.props.onClick(value);
};
return <div onClick={onClick}>{text}</div>;
};
render(): React.ReactNode {
return <div>{this.props.options.map(this.renderOption)}</div>;
}
}
type OptionBProps<T> = {
value: T;
children: string;
};
type SelectBProps<T> = {
value: T;
children: ({
renderOption
}: {
renderOption: (
option: OptionBProps<T>
) => React.ReactElement<OptionBProps<T>>;
}) => React.ReactElement<OptionBProps<T>>;
onClick: (value: T) => void;
};
class SelectB<T> extends React.Component<SelectBProps<T>> {
renderOption = (option: OptionBProps<T>) => {
const { value, children } = option;
const onClick = () => {
this.props.onClick(value);
};
return <div onClick={onClick}>{children}</div>;
};
render() {
return this.props.children({ renderOption: this.renderOption });
}
}
class Main extends React.Component {
onClick(value: string) {
console.log(value);
}
renderA(): React.ReactNode {
const options = [{ value: 5, text: "Five" }, { value: 10, text: "Ten" }];
return <SelectA value="Text" options={options} onClick={this.onClick} />;
}
renderB(): React.ReactNode {
return (
<SelectB<string> value="Text" onClick={this.onClick}>
{({ renderOption }) => (
<>
{renderOption({ value: 5, children: "Five" })}
{renderOption({ value: 10, children: "Ten" })}
</>
)}
</SelectB>
);
}
}