使用默认 props 进行类型检查
你可能不需要 defaultProps
根据这条推文,defaultProps 最终将被弃用。您可以查看此处讨论
普遍意见是使用对象默认值。
函数组件
type GreetProps = { age?: number };
const Greet = ({ age = 21 }: GreetProps) => // etc
类组件
type GreetProps = {
age?: number;
};
class Greet extends React.Component<GreetProps> {
render() {
const { age = 21 } = this.props;
/*...*/
}
}
let el = <Greet age={3} />;
defaultProps
的类型检查
在TypeScript 3.0+ 中,defaultProps
的类型推断得到了极大的改进,尽管一些极端情况仍然存在问题。
函数组件
// using typeof as a shortcut; note that it hoists!
// you can also declare the type of DefaultProps if you choose
// e.g. https://github.com/typescript-cheatsheets/react/issues/415#issuecomment-841223219
type GreetProps = { age: number } & typeof defaultProps;
const defaultProps = {
age: 21,
};
const Greet = (props: GreetProps) => {
// etc
};
Greet.defaultProps = defaultProps;
对于类组件,有几种方法可以做到这一点(包括使用Pick
实用程序类型),但建议是“反转”props定义
type GreetProps = typeof Greet.defaultProps & {
age: number;
};
class Greet extends React.Component<GreetProps> {
static defaultProps = {
age: 21,
};
/*...*/
}
// Type-checks! No type assertions needed!
let el = <Greet age={3} />;
库作者的 React.JSX.LibraryManagedAttributes
细微差别
以上实现对于应用程序创建者来说工作良好,但有时您希望能够导出GreetProps
以便其他人可以使用它。这里的问题是,GreetProps
的定义方式,age
是必填属性,因为它没有因为defaultProps
而成为可选属性。
这里的关键在于GreetProps
是组件的内部契约,而不是外部面向用户的契约。您可以创建一个专门用于导出的单独类型,或者可以使用React.JSX.LibraryManagedAttributes
实用程序
// internal contract, should not be exported out
type GreetProps = {
age: number;
};
class Greet extends Component<GreetProps> {
static defaultProps = { age: 21 };
}
// external contract
export type ApparentGreetProps = React.JSX.LibraryManagedAttributes<
typeof Greet,
GreetProps
>;
这将正常工作,尽管将鼠标悬停在ApparentGreetProps
上可能会有点吓人。您可以使用下面详细介绍的ComponentProps
实用程序来减少此样板代码。
使用带有 defaultProps 的组件的 Props
带有defaultProps
的组件似乎可能有一些实际上并非必需的必填属性。
问题陈述
这是您想做的事情
interface IProps {
name: string;
}
const defaultProps = {
age: 25,
};
const GreetComponent = ({ name, age }: IProps & typeof defaultProps) => (
<div>{`Hello, my name is ${name}, ${age}`}</div>
);
GreetComponent.defaultProps = defaultProps;
const TestComponent = (props: React.ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};
// Property 'age' is missing in type '{ name: string; }' but required in type '{ age: number; }'
const el = <TestComponent name="foo" />;
解决方案
定义一个应用 React.JSX.LibraryManagedAttributes
的实用程序
type ComponentProps<T> = T extends
| React.ComponentType<infer P>
| React.Component<infer P>
? React.JSX.LibraryManagedAttributes<T, P>
: never;
const TestComponent = (props: ComponentProps<typeof GreetComponent>) => {
return <h1 />;
};
// No error
const el = <TestComponent name="foo" />;
其他讨论和知识
为什么 React.FC
会破坏 defaultProps
?
TypeScript 2.9 及更早版本
对于 TypeScript 2.9 及更早版本,有多种方法可以做到这一点,但这是我们迄今为止见到的最佳建议
type Props = Required<typeof MyComponent.defaultProps> & {
/* additional props here */
};
export class MyComponent extends React.Component<Props> {
static defaultProps = {
foo: "foo",
};
}
我们之前的建议使用了 TypeScript 中的Partial 类型
功能,这意味着当前接口将满足包装接口上的部分版本。这样我们就可以在不更改类型的情况下扩展 defaultProps!
interface IMyComponentProps {
firstProp?: string;
secondProp: IPerson[];
}
export class MyComponent extends React.Component<IMyComponentProps> {
public static defaultProps: Partial<IMyComponentProps> = {
firstProp: "default",
};
}
这种方法的问题在于它会导致类型推断与React.JSX.LibraryManagedAttributes
一起工作时出现复杂问题。基本上,它会导致编译器认为,当使用该组件创建 JSX 表达式时,其所有 props 都是可选的。