Как набрать React-компонент Typescript, который принимает контекст с супер-набором свойств - PullRequest
4 голосов
/ 16 апреля 2020

Я пытаюсь определить повторно используемые компоненты React, которые принимают поле React.Context через реквизит. Они могут не нуждаться во всей полноте свойств, доступных в родительском контексте, и, учитывая желание повторного использования, могут быть инкапсулированы в провайдерах с различными структурами контекста (но те же основные свойства, которые необходимы для повторно используемого подкомпонента). Например, родительский поставщик выше в дереве может определить тип контекста следующим образом:

type SuperSet = {
    x: number,
    y: number,
    z: number
}

let superSet = {x: 1, y: 2, z: 3}
const SuperSetContext = React.createContext<SuperSet>(superSet)

const SuperSetProvider = (props) => {
  return (
    <SuperSetContext.Provider value={superSet}>
      ...
      {/* Arbitrarily deep nested component in the tree, most likely in a different file*/}
      <SubComponent Context={SuperSetContext} />
    </SuperSetContext.Provider>
  );
}

Субкомпонент должен (я считаю) быть в состоянии определить опору контекста с меньшим количеством свойств, таких как

const SubComponent: React.FunctionComponent<{
  Context: React.Context<{x: number, y: number}>
}> = ({ Context }) => {
  const { x, y } = useContext(props./Context);

  return (<div>{x + y}</div>)
}

Или через Pick <>

Context: React.Context<Pick<SuperSet, 'x' | 'y'>>

Однако в любом случае вышеуказанный субкомпонент вызывает ошибку типа, когда в провайдере назначается реквизит

<SubComponent Context={SuperSetContext} />
Type 'Context<SuperSet>' is not assignable to type 'Context<SubSet>'.
  Types of property 'Provider' are incompatible.
    Type 'Provider<SuperSet>' is not assignable to type 'Provider<SubSet>'.
      Types of parameters 'props' and 'props' are incompatible.
        Type 'ProviderProps<SubSet>' is not assignable to type 'ProviderProps<SuperSet>'.ts(2322)
test.tsx(26, 3): The expected type comes from property 'Context' which is declared here on type 'IntrinsicAttributes & { Context: Context<SubSet>; } & { children?: ReactNode; }'

, который я создал Typescript Playground для тестирования без jsx, но это происходит независимо от использования jsx. Кроме того, я не вижу такого же поведения с наивными обобщенными c классами / функциями.

Так есть ли способ определения определения контекста субкомпонента с помощью подмножества или свойств контекста ИЛИ a различные парадигмы для достижения sh того же дизайна и избежания этого конкретного несоответствия при наборе?

Ответы [ 2 ]

4 голосов
/ 16 апреля 2020

Если вы объявите свой собственный интерфейс, который расширяет React.Context<T>, Typescript примет его.

interface MyContext<T> extends React.Context<T> {} // this does the trick

const SubComponent: React.FunctionComponent<{
  // use MyContext instead of React.Context
  Context: MyContext<{x: number, y: number}> 
}> = ({ Context }) => {
  const { x, y } = React.useContext(Context);

  return <div>{x + y}</div>;
};


// Now pass a SuperSet

type SuperSet = {
  x: number;
  y: number;
  z: number;
};
let superSet = { x: 1, y: 2, z: 3 };

const SuperSetContext = React.createContext<SuperSet>(superSet);
const SuperSetProvider = props => {
  return (
    <SuperSetContext.Provider value={superSet}>
      {/* No TS error! */}
      <SubComponent Context={SuperSetContext} />  
    </SuperSetContext.Provider>
  );
};

вы можете проверить его в этой песочнице

Для честно говоря, я не могу объяснить, что вызывает разницу в поведении.

1 голос
/ 25 апреля 2020

В этом случае вам следует использовать универсальный компонент c (SubComponent), поскольку TS здесь: вы не можете предполагать, что React.Context<SubSet> является подмножеством React.Context<SuperSet>, потому что они являются коробочными типами. Используя простой универсальный c компонент, вы можете обойти это, указав, что вы действительно хотите: чтобы контекст type был подмножеством:

function SubComponent<T extends { x: number, y: number }>(props: {
  context: React.Context<T>
}) {
  const { x, y } = React.useContext(props.context);

  return (<div>{x + y}</div>)
}

Полный пример вы можете увидеть в Детская площадка: Детская площадка Link

...