这个列表是React与TypeScript合作时的组件模式的集合。把它们看作是TypeScript + React指南的延伸,涉及到整体概念和类型。这个列表在很大程度上受到chantastic的原始React模式列表的启发。
与chantastic的指南相反,我主要使用现代的React,所以功能组件和--如果有必要--钩子。我也只关注类型。
基本的功能组件#
当使用没有任何道具的函数组件时,你没有必要使用额外的类型。一切都可以被推断出来。在老式的函数中(我更喜欢),以及箭头函数中。
function Title() {
return <h1>Welcome to this application</h1>;
}
道具#
在使用props时,我们通常根据我们正在编写的组件来命名props,用Props-suffix。不需要使用FC 组件封装器或类似的东西。
type GreetingProps = {
name: string;
};
function Greeting(props: GreetingProps) {
return <p>Hi {props.name} 👋</p>
}
解构使其更具有可读性
function Greeting({ name }: GreetingProps) {
return <p>Hi {name} 👋</p>;
}
默认道具#
与其像在基于类的React中那样设置默认的props,不如给props设置默认值来的简单。我们用默认值可选的方式标记道具(见问号操作符)。默认值可以确保name ,永远不会未定义。
type LoginMsgProps = {
name?: string;
};
function LoginMsg({ name = "Guest" }: LoginMsgProps) {
return <p>Logged in as {name}</p>;
}
儿童#
与其使用FC 或FunctionComponent 帮助器,我们更愿意明确地设置children ,因此它遵循与其他组件相同的模式。我们将children 设置为React.ReactNode 类型,因为它接受大多数(JSX元素、字符串等)。
type CardProps = {
title: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<section className="cards">
<h2>{title}</h2>
{children}
</section>
);
}
当我们明确地设置children ,我们也可以确保我们永远不会传递任何孩子。
// This throws errors when we pass children
type SaveButtonProps = {
//... whatever
children: never
}
请看我的论据,为什么我不在这个编辑器中使用FC 。
WithChildren帮助类型#
一个自定义的帮助器类型可以帮助我们更容易地设置children 。
type WithChildren<T = {}> =
T & { children?: React.ReactNode };
type CardProps = WithChildren<{
title: string;
}>;
这与FC 非常相似,但有了{} 的默认通用参数,它可以更加灵活。
// works as well
type CardProps = { title: string } & WithChildren;
如果你使用Preact,你可以使用h.JSX.Element 或VNode 作为类型,而不是React.ReactNode 。
将属性分散到HTML元素中#
将属性扩散到HTML元素是一个很好的功能,你可以确保你能够设置一个元素所具有的所有HTML属性,而不需要预先知道你要设置哪些属性。你可以把它们传递出去。这里有一个按钮包装组件,我们在这里传播属性。为了获得正确的属性,我们通过JSX.IntrinsicElements ,访问一个button's props。这包括children ,我们把它们分散开来。
type ButtonProps = JSX.IntrinsicElements["button"];
function Button({ ...allProps }: ButtonProps) {
return <button {...allProps} />;
}
预设属性#
假设我们想把type 预设为button ,因为默认行为submit 试图发送一个表单,而我们只想让事情可点击。我们可以通过从按钮道具集合中省略type 来获得类型安全。
type ButtonProps =
Omit<JSX.IntrinsicElements["button"], "type">;
function Button({ ...allProps }: ButtonProps) {
return <button type="button" {...allProps} />;
}
// 💥 This breaks, as we omitted type
const z = <Button type="button">Hi</Button>;
样式化组件#
不要与CSS-in-JS库中的styled-components相混淆。我们想根据我们定义的道具来设置CSS类。例如,一个新的type 属性,允许被设置为primary 或secondary 。
我们省略原来的type 和className ,并与我们自己的类型相交。
type StyledButton = Omit<
JSX.IntrinsicElements["button"],
"type" | "className"
> & {
type: "primary" | "secondary";
};
function StyledButton({ type, ...allProps }: StyledButton) {
return <Button className={`btn-${type}`} />;
}
必要的属性#
我们从类型定义中删除了一些道具,并将它们预设为合理的默认值。现在我们要确保我们的用户不会忘记设置一些道具。比如图片的alt属性或src 属性。
为此,我们创建了一个MakeRequired 辅助类型,删除了可选标志。
type MakeRequired<T, K extends keyof T> = Omit<T, K> &
Required<{ [P in K]: T[P] }>;
然后用它来建立我们的道具。
type ImgProps
= MakeRequired<
JSX.IntrinsicElements["img"],
"alt" | "src"
>;
export function Img({ alt, ...allProps }: ImgProps) {
return <img alt={alt} {...allProps} />;
}
const zz = <Img alt="..." src="..." />;
受控输入#
当你在React中使用常规的输入元素,并想预先填入数值时,之后你就不能再改变它们。这是因为value 属性现在是由React控制的。我们必须把value 在我们的状态中并控制它。通常情况下,只要用我们自己的类型与原始输入元素的道具相交就足够了。这是可选的,因为我们想在以后的组件中把它设置成一个默认的空字符串。
type ControlledProps =
JSX.IntrinsicElements["input"] & {
value?: string;
};
另外,我们也可以放弃旧的属性,重写它。
type ControlledProps =
Omit<JSX.IntrinsicElements["input"], "value"> & {
value?: string;
};
并使用带有默认值的useState ,使其发挥作用。我们还将我们从原来的输入道具中传递的onChange 处理程序。
function Controlled({
value = "", onChange, ...allProps
}: ControlledProps) {
const [val, setVal] = useState(value);
return (
<input
value={val}
{...allProps}
onChange={e => {
setVal(() => e.target?.value);
onChange && onChange(e);
}}
/>
);
}
有待扩展