feat(react): improve props and state typesafety on Component

- fix(react): remove failing expectation on TS 2.6
- chore(react): add myself to contributors
This commit is contained in:
Martin Hochel
2018-06-26 11:06:17 +02:00
parent 61a609c6f2
commit 542f3c08a5
3 changed files with 92 additions and 3 deletions
+17 -3
View File
@@ -17,6 +17,7 @@
// Ferdy Budhidharma <https://github.com/ferdaber>
// Johann Rakotoharisoa <https://github.com/jrakotoharisoa>
// Olivier Pascal <https://github.com/pascaloliv>
// Martin Hochel <https://github.com/hotell>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.6
@@ -281,13 +282,18 @@ declare namespace React {
// tslint:disable-next-line:no-empty-interface
interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
class Component<P, S> {
constructor(props: Readonly<P>);
/**
* @deprecated
* https://reactjs.org/docs/legacy-context.html
*/
constructor(props: P, context?: any);
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
@@ -299,9 +305,17 @@ declare namespace React {
// always pass children as variadic arguments to `createElement`.
// In the future, if we can define its call signature conditionally
// on the existence of `children` in `P`, then we should remove this.
props: Readonly<{ children?: ReactNode }> & Readonly<P>;
state: Readonly<S>;
readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
readonly state: null | Readonly<S>;
/**
* @deprecated
* https://reactjs.org/docs/legacy-context.html
*/
context: any;
/**
* @deprecated
* https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs
*/
refs: {
[key: string]: ReactInstance
};
+67
View File
@@ -54,6 +54,72 @@ declare const container: Element;
//
// Top-Level API
// --------------------------------------------------------------------------
{
interface State {
inputValue: string;
seconds: number;
}
/**
* This is a "pre EcmaScript class property era" style of setting state within constructor.
* This occurs also if you need to provide som logic before mounting your component.
* To mitigate this error you need to provide state property definition upfront.
*/
class SettingStateFromCtorComponent extends React.Component<Props, State, Snapshot> {
// uncomenting this fixes the error :)
// state: State;
constructor(props: Props) {
super(props);
// $ExpectError
this.state = {
inputValue: 'hello'
};
}
render() { return null; }
}
class BadlyInitializedState extends React.Component<Props, State, Snapshot> {
// ExpectError -> this throws error on TS 2.6
// state = {
// secondz: 0,
// inputValuez: 'hello'
// };
}
class BetterPropsAndStateChecksComponent extends React.Component<Props, State, Snapshot> {
render() { return null; }
componentDidMount() {
// $ExpectError
console.log(this.state.inputValue);
}
mutateState() {
// $ExpectError
this.state = {
inputValue: 'hello'
};
// Even if state is not set, this is allowed by React
this.setState({inputValue: 'hello'});
this.setState((prevState, props) => {
// $ExpectError
props = {foo: 'nope'};
// $ExpectError
props.foo = 'nope';
return { inputValue: prevState.inputValue + ' foo' };
});
}
mutateProps() {
// $ExpectError
this.props = {};
// $ExpectError
this.props = {
key: 42,
ref: "myComponent42",
hello: "world",
foo: 42
};
}
}
}
class ModernComponent extends React.Component<Props, State, Snapshot>
implements MyComponent, React.ChildContextProvider<ChildContext> {
@@ -682,6 +748,7 @@ React.createFactory(TransitionGroup)({ component: "div" });
// The SyntheticEvent.target.value should be accessible for onChange
// --------------------------------------------------------------------------
class SyntheticEventTargetValue extends React.Component<{}, { value: string }> {
state: { value: string };
constructor(props: {}) {
super(props);
this.state = { value: 'a' };
+8
View File
@@ -141,6 +141,10 @@ class ComponentWithNewLifecycles extends React.Component<NewProps, NewState, { b
return { bar: `${nextProps.foo}bar` };
}
state = {
bar: 'foo'
};
getSnapshotBeforeUpdate(prevProps: Readonly<NewProps>) {
return { baz: `${prevProps.foo}baz` };
}
@@ -160,6 +164,10 @@ class PureComponentWithNewLifecycles extends React.PureComponent<NewProps, NewSt
return { bar: `${nextProps.foo}bar` };
}
state = {
bar: 'foo'
};
getSnapshotBeforeUpdate(prevProps: Readonly<NewProps>) {
return { baz: `${prevProps.foo}baz` };
}