admin管理员组

文章数量:1425871

I'm trying to define re-usable React ponents that accept a React.Context field through a prop. They may not need the entirety of the properties available in the parent's context and, given the desire for reuse, may be encapsulated in Providers with different Context structures (but the same core properties needed by the reused sub ponent). For instance a parent provider higher in the tree may define the Context type like so:

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 ponent in the tree, most likely in a different file*/}
      <SubComponent Context={SuperSetContext} />
    </SuperSetContext.Provider>
  );
}

The SubComponent should (I believe) be able to define a Context prop with less properties like so

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

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

Or via Pick<>

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

However either way the above SubComponent causes a type error when the prop is assigned within the Provider

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

I created a Typescript Playground to test it without jsx but it occurs regardless of using jsx. Additionaly I don't see the same behavior with naive generic classes/functions.

So is there a way to define the SubComponent's Context definition with a subset or Context properties OR a different paradigm to acplish the same design and escape this particular typing mismatch?

I'm trying to define re-usable React ponents that accept a React.Context field through a prop. They may not need the entirety of the properties available in the parent's context and, given the desire for reuse, may be encapsulated in Providers with different Context structures (but the same core properties needed by the reused sub ponent). For instance a parent provider higher in the tree may define the Context type like so:

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 ponent in the tree, most likely in a different file*/}
      <SubComponent Context={SuperSetContext} />
    </SuperSetContext.Provider>
  );
}

The SubComponent should (I believe) be able to define a Context prop with less properties like so

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

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

Or via Pick<>

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

However either way the above SubComponent causes a type error when the prop is assigned within the Provider

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

I created a Typescript Playground to test it without jsx but it occurs regardless of using jsx. Additionaly I don't see the same behavior with naive generic classes/functions.

So is there a way to define the SubComponent's Context definition with a subset or Context properties OR a different paradigm to acplish the same design and escape this particular typing mismatch?

Share Improve this question asked Apr 16, 2020 at 3:19 ZacharyZachary 852 silver badges11 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

If you declare you own interface that extends React.Context<T>, Typescript will accept it.

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

you can check it out in this sandbox

To be honest, I can't explain what causes the difference in behavior.

In this case you should use a generic ponent (SubComponent), since TS is right here: you can't assume React.Context<SubSet> is a subset of React.Context<SuperSet> because they're boxed types. Using a simple generic ponent, you can work around this specifying what you really want: that the context type should be a subset:

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

You can see full example in the playground: Playground Link

本文标签: