如何使用条件 props 接口定义 React 组件?
How to define React component with conditional props interface?
我需要定义呈现 textarea
或 input
的“字段”组件取决于道具 multiline
我试着这样做:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline: T } & T extends true
? React.HTMLProps<HTMLTextAreaElement>
: React.HTMLProps<HTMLInputElement>
export const Field: React.FC<Props> = ({ multiline, ...props }) => { // error here
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} onInput={e => {}} />; // error here
}
// usage
const result = (
<Field onChange={e => console.log(e.target.value)} /> // error here
);
但是打字稿提供了几个错误,例如:
1 Property 'multiline' does not exist on type 'HTMLProps<HTMLInputElement> & { children?: ReactNode; }'.(2339)
2 [large error, more in playground]
3 Property 'value' does not exist on type 'EventTarget'.(2339)
如何定义这样的组件?
我相信你应该使用这两种类型的交集,所以它可以是 either/or。因此,对于 Props
类型,使用 '&' 运算符来涵盖两种输入类型,然后在 onChange
事件中为每个输入类型事件再次使用它(因为它可以是任何一种)。像这样:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline?: T } &
React.HTMLProps<HTMLTextAreaElement> &
React.HTMLProps<HTMLInputElement>;
export const Field: React.FC<Props> = ({ multiline, ...props }) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} />;
}
// usage
const result = (
<Field onChange={(e: React.ChangeEvent<HTMLInputElement> & React.ChangeEvent<HTMLTextAreaElement>) => (
console.log(e.target.value)
)} />
);
问题:字段
中没有T
您定义了一个依赖于 T
的通用类型 Props
,但您的组件不是通用的。它总是需要 Props<boolean>
解析为 HTMLInputElement
道具,因为 boolean extends true
是 false
。 {multiline: boolean}
丢失的原因是您需要在其余类型周围加上括号。
React.HTMLProps
当使用你的 React.HTMLProps
类型时,我没有在分配不匹配的属性时遇到错误,例如 type="number"
到 textarea
或 rows={5}
到 input
.限制性更强的类型是 JSX.IntrinsicElements['textarea']
和 JSX.IntrinsicElements['input']
(解析为类似 React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
的类型)。如果你想要严格执行,那么使用这些!这也使得 onChange
回调中的 e
值根据元素获得正确的类型。
实施
当使用具有限制类型的通用组件时,我们现在在 return <Element {...props} />;
上的实现中出现错误我认为将其分成两部分 (return multiline ? <textarea {...props} /> : <input {...props}/>;
) 会有所帮助,但我们仍然会遇到错误。条件很粗糙。您可以使用 as
断言来解决问题。当函数的用法保持严格键入时,我通常可以在函数的实现中进行断言。所以你可以这样做:
type Props<T extends boolean = boolean> = { multiline: T } & (T extends true
? JSX.IntrinsicElements['textarea']
: JSX.IntrinsicElements['input'])
export const Field = <T extends boolean>({ multiline, ...props }: Props<T>) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props as any} />;
}
联合类型
我们可以通过键入 Props
作为两种情况的并集来避免必须进行断言。这允许我们通过查看 props.multiline
来检查联合中的类型。这会变得很混乱,因为在 之后 你已经区分了联合,你不能解构,但我们不想将多行传递给 DOM.
此代码通过了所有类型检查,但它需要额外的工作来防止将 multiline
传递给 DOM。
type Props = (
{ multiline: true } & JSX.IntrinsicElements['textarea'] |
{ multiline: false } & JSX.IntrinsicElements['input']
);
export const Field = ({ ...props }: Props) => {
return props.multiline ? <textarea {...props} /> : <input {...props}/>
}
用法
无论哪种方式,用法都是强类型的!我们的 onChange
回调获得正确的类型,如 React.ChangeEvent<HTMLTextAreaElement>
,如果在 multiline={false}
时传递 textarea
道具,我们会得到错误,反之亦然。
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLTextAreaElement>
multiline={true}
rows={5} // ok
type="number" // error
/>
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLInputElement>
multiline={false}
type="number" // ok
rows={5} // error
/>
我需要定义呈现 textarea
或 input
的“字段”组件取决于道具 multiline
我试着这样做:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline: T } & T extends true
? React.HTMLProps<HTMLTextAreaElement>
: React.HTMLProps<HTMLInputElement>
export const Field: React.FC<Props> = ({ multiline, ...props }) => { // error here
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} onInput={e => {}} />; // error here
}
// usage
const result = (
<Field onChange={e => console.log(e.target.value)} /> // error here
);
但是打字稿提供了几个错误,例如:
1 Property 'multiline' does not exist on type 'HTMLProps<HTMLInputElement> & { children?: ReactNode; }'.(2339)
2 [large error, more in playground]
3 Property 'value' does not exist on type 'EventTarget'.(2339)
如何定义这样的组件?
我相信你应该使用这两种类型的交集,所以它可以是 either/or。因此,对于 Props
类型,使用 '&' 运算符来涵盖两种输入类型,然后在 onChange
事件中为每个输入类型事件再次使用它(因为它可以是任何一种)。像这样:
import React from 'react';
type Props<T extends boolean = boolean> = { multiline?: T } &
React.HTMLProps<HTMLTextAreaElement> &
React.HTMLProps<HTMLInputElement>;
export const Field: React.FC<Props> = ({ multiline, ...props }) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props} />;
}
// usage
const result = (
<Field onChange={(e: React.ChangeEvent<HTMLInputElement> & React.ChangeEvent<HTMLTextAreaElement>) => (
console.log(e.target.value)
)} />
);
问题:字段
中没有T
您定义了一个依赖于 T
的通用类型 Props
,但您的组件不是通用的。它总是需要 Props<boolean>
解析为 HTMLInputElement
道具,因为 boolean extends true
是 false
。 {multiline: boolean}
丢失的原因是您需要在其余类型周围加上括号。
React.HTMLProps
当使用你的 React.HTMLProps
类型时,我没有在分配不匹配的属性时遇到错误,例如 type="number"
到 textarea
或 rows={5}
到 input
.限制性更强的类型是 JSX.IntrinsicElements['textarea']
和 JSX.IntrinsicElements['input']
(解析为类似 React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
的类型)。如果你想要严格执行,那么使用这些!这也使得 onChange
回调中的 e
值根据元素获得正确的类型。
实施
当使用具有限制类型的通用组件时,我们现在在 return <Element {...props} />;
上的实现中出现错误我认为将其分成两部分 (return multiline ? <textarea {...props} /> : <input {...props}/>;
) 会有所帮助,但我们仍然会遇到错误。条件很粗糙。您可以使用 as
断言来解决问题。当函数的用法保持严格键入时,我通常可以在函数的实现中进行断言。所以你可以这样做:
type Props<T extends boolean = boolean> = { multiline: T } & (T extends true
? JSX.IntrinsicElements['textarea']
: JSX.IntrinsicElements['input'])
export const Field = <T extends boolean>({ multiline, ...props }: Props<T>) => {
const Element = multiline ? 'textarea' : 'input';
return <Element {...props as any} />;
}
联合类型
我们可以通过键入 Props
作为两种情况的并集来避免必须进行断言。这允许我们通过查看 props.multiline
来检查联合中的类型。这会变得很混乱,因为在 之后 你已经区分了联合,你不能解构,但我们不想将多行传递给 DOM.
此代码通过了所有类型检查,但它需要额外的工作来防止将 multiline
传递给 DOM。
type Props = (
{ multiline: true } & JSX.IntrinsicElements['textarea'] |
{ multiline: false } & JSX.IntrinsicElements['input']
);
export const Field = ({ ...props }: Props) => {
return props.multiline ? <textarea {...props} /> : <input {...props}/>
}
用法
无论哪种方式,用法都是强类型的!我们的 onChange
回调获得正确的类型,如 React.ChangeEvent<HTMLTextAreaElement>
,如果在 multiline={false}
时传递 textarea
道具,我们会得到错误,反之亦然。
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLTextAreaElement>
multiline={true}
rows={5} // ok
type="number" // error
/>
<Field
onChange={e => console.log(e.target.value)} // e: React.ChangeEvent<HTMLInputElement>
multiline={false}
type="number" // ok
rows={5} // error
/>