Пропуск общего свойства из объектов типа объединения приводит к ошибке при использовании не общих свойств (TypeScript) - PullRequest
0 голосов
/ 17 января 2020

Я создаю функцию-оболочку, которая передает свойство refs в функцию send, как показано ниже. Тип события, используемый для создания моего конечного автомата, определяется как пересечение между базовым интерфейсом { refs: NodeRefs } и объединением возможных объектов событий, например, { type: "EVENT_1" } | { type: "EVENT_2", context: MyContextType }.

Функция-обертка (в данном примере useMachine). code) должен возвращать нашу обновленную функцию 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]>;
};

1 Ответ

1 голос
/ 18 января 2020

Я думаю, что ваша проблема, вероятно, вызвана тем, что встроенная утилита типа 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, которая распространяет по объединениям, используя распределительный условный тип . Если у вас есть универсальный параметр типа c 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
    }
  }
}

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...