reactjs - 如何动态更改属性要求
我有一个组件,我们称之为 MyText。它有两个可选的属性,x 和 y。我想如果属性中定义了 y,就让 x 变为必填,反之亦然。
这里是 MyText
interface Props {
x?: number,
y?: number,
}
class MyText extends React.Component<Props> {
render() {
return (
<View>
<Text>X is : {this.props.x}</Text>
<Text>Y is : {this.props.y}</Text>
</View>
);
}
}
当我调用这个时,我想像这样输出
<MyText x={1} /> // Error: Should say missing y
<MyText y={4} /> // Error: Should say missing x
<MyText x={1} y={4}/> // No error
<MyText/> // No error
使用 TypeScript 可能吗?
当前解决方案:
使用单个对象来存储x和y,并使其可选。
interface XY{
x : number,
y : number
}
interface Props {
xy?: XY,
}
class MyText extends React.Component<Props> {
render() {
return (
<View>
<Text>X is : {this.props.xy.x}</Text>
<Text>Y is : {this.props.xy.y}</Text>
</View>
);
}
}
并这样称呼它
<MyText xy={{x:1,y:2}} />
我想知道是否有比这更好的方法,比如内置关键字或其他技术来让一个属性依赖于另一个属性。
答案#1
一种天真的方法是指定 Props 是一个联合类型,介于具有必需属性的接口和一个空接口之间 { x: number; y: number } | {}。这样做的问题是对额外属性的检查并不能完全按预期工作:
let p : { x: number; y : number } | {} = { x: 0 } // Compiler says ok, even though the object has more properties then `{}` and does not satisfy { x: number; y : number }
为了解决这个问题,我们可以使用一个联合类型,其中第一个成员要求 x 和 y 都是必需的,而联合类型的第二个成员将这两个属性设置为可选,并且其类型既很少用于属性,又难以作为字面量指定。最好的选择是 never :
let p1 : { x: number; y : number } | { x?: never; y?: never } = { x: 0, y: 0 } // ok
let p2 : { x: number; y : number } | { x?: never; y?: never } = { x: 0 } // invalid
let p3 : { x: number; y : number } | { x?: never; y?: never } = { } // ok
将其应用到你的情况,我们得到:
type Props = {
x: number,
y: number,
} | {
x?: never,
y?: never,
}
class MyText extends React.Component<Props> {
render() {
return null;
}
}
let s1 = <MyText x={1} /> // Error: Says x is not assignable to never, not ideal but you get used to it
let s2 = <MyText y={4} /> // Error: Says y is not assignable to y
let s3 = <MyText x={1} y={4}/> // No error
let s4 = <MyText/> // No error
获得更友好错误消息的一种方法是使用带有更友好消息的字符串字面量类型。这确实存在一定风险,即有人会指定错误消息的值,这会让编译器满意,但希望你的团队成员会明白 x='ERROR: y is missing, you must specify y' 并不太合理:
type Props = {
x: number,
y: number,
} | {
x?: 'ERROR: y is missing, you must specify y',
y?: 'ERROR: x is missing, you must specify x',
}
let s1 = <MyText x={1} /> // Type 'number' is not assignable to type '"ERROR: y is missing, you must specify y" | undefined'.
let s2 = <MyText y={4} /> // Type 'number' is not assignable to type '"ERROR: x is missing, you must specify x" | undefined'
let sSilly = <MyText y='ERROR: x is missing, you must specify x' /> // valid but ilogical enough to not worry about it
let s3 = <MyText x={1} y={4}/> // No error
let s4 = <MyText/> // No error
