简体   繁体   中英

TypeScript is not enforcing generic intersection type (React component props)

I have a component that accepts a generic type parameter TChildProps :

type GenericFieldHTMLAttributes =
    | InputHTMLAttributes<HTMLInputElement>
    | SelectHTMLAttributes<HTMLSelectElement>
    | TextareaHTMLAttributes<HTMLTextAreaElement>

interface FieldProps {
    name: string
    label?: string | React.ReactNode
    component?: string | React.ComponentType
    initialValue?: any
    children?: React.ReactNode | FieldRenderFunction
}

class Field<TChildProps = GenericFieldHTMLAttributes> extends React.PureComponent<FieldProps & TChildProps> {
    ...
}

When I use this component, I would expect it to prevent me from passing in unrecognized props, for example:

render() {
    return (
        <Form onSubmit={this.save}>
            <Field foo="test" label="email" name="email" type="email" component={TextField} />
        </Form>
    )
}

Surprisingly, the above code compiles without even any warnings, despite the fact that the foo prop is not defined anywhere. I tried simplifying the example and got the same result:

class Field<TChildProps = {}> extends React.PureComponent<FieldProps & TChildProps> {
    ...
}

// this still compiles without errors
<Field foo="test" label="email" name="email" type="email" component={TextField} />

Is TypeScript functioning the way it's supposed to here according to the type definition for React.PureComponent (by the way I tested it on React.Component and got the same result), or is this a bug?

Original Answer

Because <TChildProps = {}> means that the default value of TChildProps is {} if the consumer does not assigns any value to TChilProps . So TChildProps can actually be anything if the consumer decides to pass value to it. That's why typescript allows any props passed to Field . What you want to do is to enforce the type of TChildProps using extends keyword. So try class Field<TChildProps extends GenericFieldHTMLAttributes> instead. As a result, the consumer needs to pass TChildProps as GenericFieldHTMLAttributes type. You can find more information on https://www.typescriptlang.org/docs/handbook/generics.html .

UPDATE

Here's the simplified situation ( UPDATE2 : I tend to think this is either a bug or a missing feature, here's why)

This Animal class below accepting food as generics reflects React.Component class that accepts props as generics.

class Animal<F> {
  eat(food: F) {
    return;
  }
}

And the Lion class below extending Animal reflects your Field class which extends React.Component

class Lion<F = {pizza: string}> extends Animal<F> {

}

And when the consumers consume it, if they do not specify what food a lion should eat, the lion can eat anything like so we should expect the lion to eat only pizza

const lion1 = new Lion();
lion1.eat(""); // Argument of  type '""' is not assignable to parameter of type '{pizza: string}'

However this type check does not happen in case of tsx.

The workaround is to "alias both the type and interface to a non-generic specialization."

const TextField = Field as { new (): Field<{value: string}>};

<TextField foo="test" label="email" name="email" type="email" value="asdf"/> // error: Property 'foo' does not exist

These two links should give you more info on how to use generic components as of now.

https://github.com/Microsoft/TypeScript/issues/3960 https://basarat.gitbooks.io/typescript/docs/types/generics.html#generics-in-tsx

And apparently they working on this.

UPDATE April 2018

This should be possible in Typescript 2.9 as seen in https://github.com/Microsoft/TypeScript/pull/22415 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM