假设我有输入组件,我想可选地接收一个按钮组件,一个标签和/或一个图标组件。顺序不重要。
// Correct use case
<Input>
<Button />
<Label />
<Icon />
</Input>
// Correct use case
<Input>
<Button />
</Input>
// Incorrect use case
<Input>
<Button />
<Button />
</Input>
有谁知道什么样的typescript接口可以解决这个问题吗?真实用例:
<Input
placeholder="Username"
name="username"
type={InputType.TEXT}
>
<Input.Label htmlFor={"username"}>Label</Input.Label>
<Input.Max onClick={() => console.log("Max clicked!")} />
<Input.Icon type={IconType.AVATAR} />
<Input.Button
onClick={onClickHandler}
type={ButtonType.GRADIENT}
loading={isLoading}
>
<Icon type={IconType.ARROW_RIGHT} />
</Input.Button>
<Input.Status>Status Message</Input.Status>
<Input.Currency>BTC</Input.Currency>
</Input>
我有一个动态检查,如果使用列出的子组件之外的任何东西都会抛出错误(所有可能的选项都显示了),但我想以某种方式包括Typescript,并在有人使用不同类型的子组件或重复组件时显示类型错误。
看看你的代码和问题,关键因素可以被认为是:
- 每个按钮/图标/标签组件只允许出现一次。
- 使用JSX括号结构来指定特定的元素;使用反应。孩子API。
- 使用打印稿。
目前这些请求是相互矛盾的。因为使用typescript,很难实现可变数组类型。这里有两种解决方法,每种方法都突出了一些关键的优点。
解决方案1。如果这个工具不局限于typescript, React的顶级Children API可以很容易地解决这个问题。
const Button = () => {
return <button>button</button>;
}
const Label = () => {
return <label>label</label>
}
const Icon = () => {
return <div>icon</div>
}
const WithSoleChild = (props) => {
// used top-level react child API
// https://reactjs.org/docs/react-api.html#reactchildren
const childNames = React.Children.map(props.children, c => c.type.name);
// count the duplication
const childDupes = childNames.reduce((ac, cv) => ({ ...ac, [cv]: (ac[cv] || 0) + 1 }),{})
// same child should not be duplicated
const childValid = Object.values(childDupes).some(amount => amount < 2);
if (!childValid) {
// added stack trace for better dev experience.
console.error('WithSoleChild: only one Button/Icon/Label is allowed', new Error().stack);
// disable render when it does not fit to the condition.
return null;
}
return props.children;
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<div>
{/* this does not render, and throws an error to console */}
<WithSoleChild>
<Button />
<Button />
</WithSoleChild>
{/* this does render */}
<WithSoleChild>
<Button />
<Icon />
<Label />
</WithSoleChild>
</div>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
解决方案2。然而,有一个更优雅的解决方案,在打字稿中也能完美地工作;它被称为插槽组件方法,也称为包容组件在React官方文档中。
// App.tsx
import React from "react";
// extend is necessary to prevent these types getting regarded as same type.
interface TButton extends React.FC {};
interface TIcon extends React.FC {};
interface TLabel extends React.FC {};
const Button: TButton = () => {
return <button className="button">button</button>;
};
const Icon: TIcon = () => {
return <div>icon</div>;
};
const Label: TLabel = () => {
return <label>label</label>;
};
interface IWithSoleChild {
button: React.ReactElement<TButton>;
icon: React.ReactElement<TIcon>;
label: React.ReactElement<TLabel>;
}
const WithSoleChild: React.FC<IWithSoleChild> = ({ button, Icon, label }) => {
return null;
};
function App() {
return (
<div className="App">
<WithSoleChild button={<Button />} icon={<Icon />} label={<Label />} />
</div>
);
}