从联合类型的对象中省略共享属性会导致在使用非共享属性 (TypeScript) 时出错



我正在创建一个包装函数,该函数将refs属性传递到send函数中,如下所示。用于创建状态机的事件类型定义为基本接口{ refs: NodeRefs }和可能的事件对象的联合之间的交集,例如{ type: "EVENT_1" } | { type: "EVENT_2", context: MyContextType }.

包装函数(useMachine示例代码中(应返回我们更新的send函数,该函数需要一个省略refs键的事件对象。在此处使用Omit会导致在尝试将我的发送函数与联合类型的任何非共享属性一起使用时出错,并且我不是 100% 清楚为什么或如何以不同的方式执行此操作。

enum States {
Unchecked = "UNCHECKED",
Checked = "CHECKED",
Mixed = "MIXED"
}
enum Events {
Toggle = "TOGGLE",
Set = "SET",
UpdateContext = "UPDATE_CONTEXT"
}
// Events for the state machine will be a union type a la TEvent, but all events
// will need a `refs` property. We just won't need to explicitly pass refs in
// our event calls thanks to the useMachine hook below
interface EventBase {
refs: NodeRefs;
}
type TEvent = EventBase &
(
| { type: Events.Toggle }
| {
type: Events.Set;
state: States;
}
| {
type: Events.UpdateContext;
context: Partial<Context>;
}
);
function useMachine(stateMachine: SomeStateMachine, refs: ReactRefs) {
let [currentState, setCurrentState] = useState(stateMachine.initialState);
/* ... */
// I want to omit the refs property from our event here because we are always
// sending the same values in each event, but this is triggering the error
// in our send function below.
function send(event: Omit<TEvent, "refs">) {
let nodes = Object.keys(refs).reduce(nodeReducer);
service.send({ ...event, refs: nodes });
}
return [currentState, send];
}
function MyComponent({ disabled }) {
let inputRef = useRef<HTMLInputElement | null>(null);
let [currentState, send] = useMachine(myStateMachine, { input: inputRef });
useEffect(() => {
send({
type: Events.UpdateContext,
// Error: Object literal may only specify known properties, and 'context'
// does not exist in type 'Pick<TEvent, "type">'.
context: { disabled }
});
}, [disabled]);
/* ... */
}
type NodeRefs = {
input: HTMLInputElement | null;
};
type ReactRefs = {
[K in keyof NodeRefs]: React.RefObject<NodeRefs[K]>;
};

我认为您的问题可能是由于以下事实引起的:内置的实用程序类型Omit<T, K>,如Pick<T, K>,不会通过T中的联合进行分配。 您似乎期望(这并非不合理(,Omit<A | B, K>应该等同于Omit<A, K> | Omit<B, K>,但它不是那样工作的。 相反,Omit<A | B, K>映射keyof (A | B),这与(keyof A) & (keyof B)相同,您会发现自己只能看到共享属性。

最简单的解决方法是创建一个Omit版本,该版本通过使用分发条件类型在联合上分发。 如果你有一个泛型类型参数T,则构造T extends any ? F<T> : never最终会将F<>操作分布在T上,当T是联合类型时。定义如下:

type DistributiveOmit<T, K extends PropertyKey> = T extends any ? Omit<T, K> : never;

您可以验证DistributiveOmit<A | B, K>现在是否完全等同于DistributiveOmit<A, K> | Distributive<B, K>。 因此DistributiveOmit<TEvent, "refs">本身将是一个联盟:

function foo(event: DistributiveOmit<TEvent, "refs">) {
switch (event.type) {
case Events.Set: {
event.state; // okay
break;
}
case Events.Toggle: {
break;
}
case Events.UpdateContext: {
event.context; // okay
}
}
}

好的,希望有帮助;祝你好运!

操场链接到代码

最新更新