简体   繁体   中英

React-Typescript: How do I type a default prop?

Let's say I have this component class:

interface SomeComponentProps {
  someOptionalProperty?: string;
}
interface SomeComponentState { /* ... */ }

class SomeComponent extends React.Component<SomeComponentProps, SomeComponentState> {
  static defaultProps = {
    someOptionalProp = 'some default value',
  }

  method() {
    const shouldNotBeOptional = this.props.someOptionalProp;
  }
}

How can I assert that this.props.someOptionalProp is not undefined within the class but is also optional when trying to using the component?

What I've been doing is just ignoring defaultProps altogether by creating getters that

return `this.props.someOptionalProp || 'the default value'`;

but I'd like to use defaultProps because my team uses it.

Not really possible as it stands with TS 2.8.3, there are some issues open about it already

On react types: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640

On the TS repo: https://github.com/Microsoft/TypeScript/issues/23812

Best you can achieve currently I believe is by using the ! operator to tell the compiler that you know something can't be null

const shouldNotBeOptional = this.props.someOptionalProp!;

You could use a higher order component (HOC) that will just change the type of the props to be optional where they are present in defaultProps

interface SomeComponentProps {
    required: string;
    someOptionalProperty: string;
    trulyOptional?: string;
}
interface SomeComponentState { /* ... */ }

class SomeComponent extends React.Component<SomeComponentProps, SomeComponentState> {
    static defaultProps = {
        someOptionalProperty : 'some default value',
    }

    method() {
        const shouldNotBeOptional = this.props.someOptionalProperty;
    }
}

type SameTypeOrCustomError<T1, T2> = T1 extends T2 ? T2 extends T1 ? T1 : "Error Different Types": "Error Different Types";
type FilterOptional<T> = Exclude<{ [P in keyof T]: undefined extends T[P]? P: never }[keyof T], undefined>;

type d = FilterOptional<SomeComponentProps>
type PropsWithDefault<TProps extends TDefaults, TDefaults> = 
    { [P in Exclude<keyof TProps, keyof TDefaults | FilterOptional<TProps>>]: TProps[P] } &
    { [P in keyof TDefaults | FilterOptional<TProps>]?: TProps[P] }; 

type ReactProps<T> = T extends React.Component<infer Props, any> ? Props: never;
type ReactState<T> = T extends React.Component<any, infer TState> ? TState: never;
type ChangeReactProps<TComponent extends React.Component<any, any>, TNewProps> = {
    new (props: TNewProps, context?: any) : React.Component<TNewProps, ReactState<TComponent>>
};

function withDefaults<T extends { new (...args: any[]) : any, defaultProps: Partial<ReactProps<InstanceType<T>>> }>(ctor: T) 
    : ChangeReactProps<InstanceType<T>, PropsWithDefault<ReactProps<InstanceType<T>>, T['defaultProps']>>  {
    return ctor; // we just chage type, we can return the same class 
}


const SomeComponent2 = withDefaults(SomeComponent);

let d = <SomeComponent2  required=""  /> //ok 
let x = <SomeComponent2  required="" trulyOptional="" /> //ok 

I use a higher order component with Typescript 2.8 in my codebase:

const withDefaultProps = <P extends object, DP extends Partial<P>>(
  defaultProps: DP,
  component: React.ComponentType<P>
) => {
  type ActualProps = Partial<DP> & Pick<P, Exclude<keyof P, keyof DP>>
  component.defaultProps = defaultProps
  return component as React.ComponentType<ActualProps>
}

interface IComponentProps {
  someOptionalProp: string
}

class SomeComponent extends React.Component<IComponentProps, {}> {
  method() {
    // this is always defined
    const shouldNotBeOptional = this.props.someOptionalProp;
  }
}

// when using this like <SomeComponent />, someOptionalProp is optional
export default withDefaultProps(
  { someOptionalProp: 'defaultValue' },
  SomeComponent
)

This issue has been discussed and resolved in here . Now you simply can do that:

interface Props {
  thing: string; // note: not optional within class, but is optional when writing due to the default
}

class Thing extends React.Component<Props> {
   static defaultProps = {
      thing: 'hello'
   }

  render() {
    console.log(this.props.thing) // strict type from `Props`
    // ...
 }
}
// ..
<Thing /> // works, thanks to `defaultProps`

It works for me in Typescript 3.1.6 and React 16.5.0

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