假设我具有以下两个组件,每个组件都使用属性接口来键入onChange
方法:
export interface BorderValues {
borderRadius: string;
borderWidth: number;
border: string;
}
export type BorderArguments = keyof BorderValues;
interface BorderProps {
onChange: <T extends BorderArguments>(arg: T, val: BorderValues[T]) => void;
}
const Border = (props: BorderProps) => {
props.onChange('borderRadius', '10px');
// props.onChange('borderRadius', 10); // error
props.onChange('borderWidth', 10);
// props.onChange('borderWidth', '10'); // error
return 'Border';
}
和
export interface ContainerValues {
padding: string | number;
opacity: string | number;
overflow: 'hidden' | 'visible';
}
export type ContainerArguments = keyof ContainerValues;
interface ContainerProps extends ContainerValues {
onChange: <T extends ContainerArguments>(arg: T, val: ContainerValues[T]) => void;
}
const Container = (props: ContainerProps) => {
// props.onChange('borderRadius', '10px'); // error
props.onChange('padding', 10);
props.onChange('overflow', 'hidden');
// props.onChange('overflow', 10); // error
return 'container';
}
这一切都很好。现在,假设我想将它们组合起来...我无法将onChange
设置为组合的setArgValue
函数,因为每个属性都缺少属性:
type Arguments = BorderArguments | ContainerArguments;
interface Values extends BorderValues, ContainerValues {};
interface Props {
// This works just fine.
getArgValue: <T extends Arguments>(arg: T) => Values[T];
// This obviously doesn't.
setArgValue: <T extends Arguments>(arg: T, val: Values<T>) => void;
}
const Component = (props: Props) => {
// error: onChange can't handle ContainerArguments.
Border({onChange: props.setArgValue});
// error: onChange can't handle BorderArguments.
Container({onChange: props.setArgValue});
}
我试图创建一个派生类型,就像这样:
type Test<T extends Arguments> =
T extends BorderArguments ? BorderValues :
T extends ContainerArguments ? ContainerValues :
never;
但是这也不起作用,因为我实际上无法对其进行索引:
// This is a problem: I need the argument to both derive the correct collection,
// but also index it based on the `arg` parameter
setArgValue: <T extends Arguments>(arg: T, val: Test<T>) => void;
此外,如果我更改Test
类型以返回索引处的项目,就像这样:
type Test<T extends Arguments> =
T extends BorderArguments ? BorderValues[T] :
T extends ContainerArguments ? ContainerValues[T] :
never;
Test
始终等同于string | number
。 但是,以下工作原理:
type Foo = Test<'overflow'>; // 'hidden' | 'visible'
到目前为止,我已经使用代码创建了playground。
有什么方法可以完成我要定义的类型?预先感谢!
答案 0 :(得分:1)
让我们备份并使用索引访问而不是条件类型。我还清理了一大堆与您的问题无关的(在我看来)多余的东西。鉴于此:
// BORDER COMPONENT
export interface BorderValues {
borderRadius: string;
borderWidth: number;
border: string;
}
export type BorderArguments = keyof BorderValues;
interface BorderProps {
onChange: <T extends BorderArguments>(arg: T, val: BorderValues[T]) => void;
}
const Border = (props: BorderProps) => {
props.onChange("borderRadius", "10px");
props.onChange("borderWidth", 10);
return "Border";
};
// CONTAINER COMPONENT
export type ContainerArguments = keyof ContainerValues;
interface ContainerValues {
padding: string | number;
opacity: string | number;
overflow: "hidden" | "visible";
}
interface ContainerProps {
onChange: <T extends ContainerArguments>(
arg: T,
val: ContainerValues[T]
) => void;
}
const Container = (props: ContainerProps) => {
props.onChange("padding", 10);
props.onChange("overflow", "hidden");
return "container";
};
// TEST COMBINATION:
type Arguments = BorderArguments | ContainerArguments;
interface Values extends BorderValues, ContainerValues {}
interface Props {
setArgValue: <T extends Arguments>(arg: T, val: Values[T]) => void;
}
以下代码在TypeScript 3.4中有效,并在TypeScript 3.5中中断:
const Component = (props: Props) => {
Border({ onChange: props.setArgValue }); // okay TS3.4, error TS3.5
Container({ onChange: props.setArgValue }); //okay TS3.4, error TS3.5
};
这是由于breaking change in TS3.5引起的。索引访问写入的方式已经modified成为类型安全的。如果我有一个o
类型的对象{a: A, b: B}
并且我有一个联合类型为k
的键"a" | "b"
,那么当我从{{1 }},我将获得一个联合类型o
的值。在TS3.4及以下版本中,您可以写一个类型为A | B
的值。但这并不安全,因为如果我有一个联合类型为A | B
的值v
,并且我写了A | B
,则o[k] = v
很有可能是k
和{ {1}}是"a"
,我做得不好。一种更严格但更安全的方法是要求写入联合密钥属性,我需要类型为v
的交集值。这既是B
的,又是A & B
的,并且适合写入A
和B
。这项更改使写入索引键变得安全。
令人沮丧。如果您执行类似o.a
的操作,那么您期望能够正确地编写o.b
。但是您意识到const v = o[k]
的类型和o[k] = v
的类型实际上是相互关联的,而不是彼此独立的……不幸的是,编译器是does not realize this。您想说k
的类型为v
,但是编译器从根本上认为它的类型为[k, v]
。而且它抱怨。
您正在将["a", A] | ["b", B]
分配给类型["a", A] | ["b", A] | ["a", B] | ["b", B]
的变量也遇到相同的问题。
那么,我们如何处理呢?最简单的方法是使用type assertion,您只需要告诉编译器它的警告已被注意但不适用:
props.setArgValue
这可能是我的建议。
如果您愿意的话,还有一种更为冗长和round回的方式来处理此问题。看来它仍然可以write of a value that's been read from a generic index使用。因此,如果BorderProps["onChange"]
是通用类型const ComponentAsserted = (props: Props) => {
Border({ onChange: props.setArgValue as BorderProps["onChange"] });
Container({ onChange: props.setArgValue as ContainerProps["onChange"] });
};
,则可以进行k
而不会出错。
因此,我们可以通过使您的K extends "a" | "b"
更通用然后再具体化来解决此问题:
const v = o[k]; o[k] = v;
props.setArgValue
的编译是由于这种通用的限制。请注意,type Setter<T> = <K extends keyof T>(arg: K, val: T[K]) => void;
const setterForFewerProps = <T extends U, U>(
setter: Setter<T>
): Setter<Pick<T, keyof U>> => <P extends keyof U>(arg: P, val: T[P]) =>
setter(arg, val);
是setterForFewerProps
,而类似props.setArgValue
的事物是Setter<Values>
setterForFewerProps(props.setArgValue)`:
borderProps.onChange
这可以很好地编译,并且可能比断言稍微更类型安全。但这是如此冗长,我不会打扰。
无论如何,希望能有所帮助;祝你好运!
答案 1 :(得分:0)
偶然地,我能够使用Partial
:link