TypeScript Version: 3.3.3333
My team struggle and confuse in problem of should we use optional? when component used defaultProps, we discuss in conclusion that we should use if we consider literary,because you use
defaultProps and that mean you allow user optional, but after I research documents, I found out that's wrong, it seems like you should never use optional? marks in params which have defaultProps
Is that correct behavior from my final research result?
If my research result is correct, what's reason for implementing that, because it looks like different behavior between nomal case like pure function params, and I tried to serach all kind of reason history document, there's no formal document for describing why TS teams implement that to this API style, I only can guess and filter answer by some git issue and merge history discussion, I prefer to there's a fomal document to describe why implement that to this API style and what different between normal case for reduce confuse from typescript newer .
import React from "react"
interface Props {
name: string; // no need ?, if add ? defaultProps will fail
}
const Test = ({name}: Props) => {
return <div>Hello ${name.toUpperCase()}!</div>;
}
const defaultProps = { name: 'aa'};
Test.defaultProps = defaultProps;
export default Test
You could mark it as optional and TypeScript would be none the wiser. But it forced you to add a non-null assertion e.g.
export interface GreetingProps {
name?: string
}
export class Greeting extends React.Component<GreetingProps> {
static defaultProps = {
name: 'stranger'
}
render() {
// Notice the non-null assertion!
// | |
// | |
// \ /
// V
return (
<div>Hello, {this.props.name!.toUpperCase()}</div>
)
}
}
The whole objective of defaultProps support is to make the props inside the component be a true reflection of the fact that they will be provided.
Example taken from : https://github.com/Microsoft/TypeScript/issues/23812 :rose:
Had the same problem !
type Props = {
foo?: string;
bar: string;
};
const defaultProps = { foo: 'foo!' }
export default class FooBar extends Component<Props> {
public static defaultProps = defaultProps;
public render() {
const foo = this.props.foo; // ๐ฅ string | undefined
const fooPlz = this.props.foo!; // โ
string
const bar = this.props.bar; // โ
string
return <Text>{`${foo} ${bar}`}</Text>;
}
}
typescript doesn't understand that foo is not undefined, you have to use non-null assertion operator
type Props = {
foo?: string;
bar: string;
} & typeof defaultProps; // <-- give the type of const defaultProps
const defaultProps = { foo: 'foo!' }
export default class FooBar extends Component<Props> {
public static defaultProps = defaultProps;
public render() {
const foo = this.props.foo; // โ
string
const bar = this.props.bar; // โ
string
return <Text>{`${foo} ${bar}`}</Text>;
}
}
Works well, we use the type of defaultProps to define the final Props.
defaultProps is not 'typo-safe'
const defaultProps = { food: 'yummy!' } // no warning
type Props = {
foo?: string;
bar: string;
};
// define the type based on Props's properties
type defaultProps = Required<Pick<Props, 'foo'>>;
// implement the type
const defaultPropsError: defaultProps = {
food: 'yummy!' // ๐ฅ error
};
const defaultProps: defaultProps = {
foo: 'foo!' // โ
good
};
// Note: you can also use Object.freeze:
const defaultPropsFreezed = Object.freeze<defaultProps>({
foo: 'foo!'
})
class FooBar extends Component<Props & defaultProps> {
//..
const foo = this.props.foo; // โ
string
const bar = this.props.bar; // โ
string
//..
}
Our component now think that our props is Props & defaultProps so:
Props & defaultProps
=
{ foo?: string; bar: string; } & { foo: string }
=
{ foo: string; bar: string }
Thanks to typescript 3.0 that support defaultProps (cf What's new in TypeScript)
// โ
no errors
<FooBar bar={'bar'} foo={'foo'} />
// โ
no errors
<FooBar bar={'bar'} />
React.ComponentProps is not friendly anymore (it's expected)
type FooBarProps = React.ComponentProps<typeof FooBar>;
const props: FooBarProps = {
bar: 'hey' // ๐ฅ Property 'foo' is missing
};
type Props = {
foo?: string;
bar: string;
};
type defaultProps = Required<Pick<Props, 'foo'>>;
const defaultProps = Object.freeze<defaultProps>({
foo: 'foo!'
})
export default class FooBar extends Component<Props> {
public static defaultProps = defaultProps;
private get dProps() {
return this.props as Props & defaultProps;
}
public render() {
const foo = this.dProps.foo; // โ
string
const bar = this.dProps.bar; // โ
string
return <Text>{`${foo} ${bar}`}</Text>;
}
}
//..
// โ
no errors
<FooBar bar={'bar'} foo={'foo'} />
// โ
no errors
<FooBar bar={'bar'} />
//..
type FooBarProps = React.ComponentProps<typeof FooBar>;
const props: FooBarProps = {
bar: 'hey' // โ
foo is not a problem
};
Result: A little hacky but works well with all my cases.
Most helpful comment
Before TypeScript added support
You could mark it as optional and TypeScript would be none the wiser. But it forced you to add a non-null assertion e.g.
So
The whole objective of defaultProps support is to make the props inside the component be a true reflection of the fact that they will be provided.
More
Example taken from : https://github.com/Microsoft/TypeScript/issues/23812 :rose: