【问题标题】:Omitting a shared property from a union type of objects results in error when using non-shared properties (TypeScript)使用非共享属性(TypeScript)时,从对象的联合类型中省略共享属性会导致错误
【发布时间】:2020-05-04 18:17:05
【问题描述】:

我正在创建一个包装函数,它将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]>;
};

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    我认为您的问题可能是由于内置实用程序类型Omit&lt;T, K&gt;Pick&lt;T, K&gt; 一样,不会distribute 超过T 中的联合。您似乎期望(这并非不合理),Omit&lt;A | B, K&gt; 应该等同于Omit&lt;A, K&gt; | Omit&lt;B, K&gt;,但它不是那样工作的。相反,Omit&lt;A | B, K&gt; 映射到 keyof (A | B),这与 (keyof A) &amp; (keyof B) 相同,您会发现自己只能看到共享属性。

    最简单的解决方法是创建一个Omit 的版本,确实通过使用distributive conditional type 在联合上分发。如果您有一个泛型类型参数T,那么当T 是联合类型时,构造T extends any ? F&lt;T&gt; : never 将最终将F&lt;&gt; 操作分配给T。这是定义:

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

    您可以验证DistributiveOmit&lt;A | B, K&gt; 现在将完全等同于DistributiveOmit&lt;A, K&gt; | Distributive&lt;B, K&gt;。因此DistributiveOmit&lt;TEvent, "refs"&gt; 本身就是一个联合体:

    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
        }
      }
    }
    

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

    Playground link to code

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-08
      • 2018-07-03
      • 2022-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多