Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A comprehensive guide to static typing in "React & Redux" apps using TypeScript

License

NotificationsYou must be signed in to change notification settings

marufsiddiqui/react-redux-typescript-guide

 
 

Repository files navigation

"This guide is about to teach you how to leverageType Inference,Generics and otherAdvanced Types as much as possible to write the minimal amount of type annotations needed for your JavaScript code to be completely Type Safe" - this will make sure you get all the benefits of Static Typing and your productivity won't be slowed down by adding excess type annotations.

Found it usefull? Want more updates?Give it a 🌟

Goals

  • Complete type safety with--strict flag without failing toany type for the best static-typing experience
  • Minimize amount of manually writing type declarations by learning to leverageType Inference
  • Reduce repetition and complexity of "Redux" type annotations to a minimum withsimple functional utilities

Playground Project

Codeship Status for piotrwitek/react-redux-typescript-guide

You should check Playground Project located in the/playground folder. It is a source of all the code examples found in the guide. They are all tested with the most recent version of TypeScript and 3rd party type definitions (like@types/react or@types/react-redux) to ensure the examples are up-to-date and not broken with updated definitions.

Playground was created is such a way, that you can simply clone the repository locally and immediately play around on your own to learn all the examples from this guide in a real project environment without the need to create some complicated environment setup by yourself.


Table of Contents


Setup

Installing types

npm i -D @types/react @types/react-dom @types/react-redux

"react" -@types/react
"react-dom" -@types/react-dom
"redux" - (types included with npm package)*
"react-redux" -@types/react-redux

redux has improved types on anext branch in it's official github repo, use below instructions to add it to your project:

  • inpackage.json > devDependencies add:
    "redux-next": "reactjs/redux#next"
  • intsconfig.json > compilerOptions > paths add:
    "redux": ["node_modules/redux-next"]

⇧ back to top


React Types Cheatsheet

React.StatelessComponent<P> or aliasReact.SFC<P>

Stateless functional components

constMyComponent:React.SFC<MyComponentProps>= ...

⇧ back to top

React.Component<P, S>

Statefull class component

classMyComponentextendsReact.Component<MyComponentProps,State>{ ...

⇧ back to top

React.ComponentType<P>

Accepts sfc or class components with Generic Props Type

constwithState=<PextendsWrappedComponentProps>(WrappedComponent:React.ComponentType<P>,)=>{ ...

⇧ back to top

React.ReactNode

Accepts any react elements (component instances) and also primitive types

constelementOrPrimitive:React.ReactNode=''||0||false||null||<div/>||<MyComponent/>;

⇧ back to top

JSX.Element

Similar in usage to ReactNode but limited to accept only react elements (and not primitive types)

constelementOnly:JSX.Element=<div/>||<MyComponent/>;

⇧ back to top

React.CSSProperties

Type-safety for styles using css-in-js

conststyles:React.CSSProperties={flexDirection:'row', ...

⇧ back to top

React.ReactEventHandler<E>

Type-safe event handlers for JSX

consthandleChange:React.ReactEventHandler<HTMLInputElement>=(ev)=>{ ...

⇧ back to top


Component Typing Patterns

Stateless Components - SFC

- stateless counter

import*asReactfrom'react';exportinterfaceSFCCounterProps{label:string;count:number;onIncrement:()=>any;}exportconstSFCCounter:React.SFC<SFCCounterProps>=(props)=>{const{ label, count, onIncrement}=props;consthandleIncrement=()=>{onIncrement();};return(<div><span>{label}:{count}</span><buttontype="button"onClick={handleIncrement}>{`Increment`}</button></div>);};

⟩⟩⟩ demo

⇧ back to top

- spreading attributeslink

import*asReactfrom'react';exportinterfaceSFCSpreadAttributesProps{className?:string;style?:React.CSSProperties;}exportconstSFCSpreadAttributes:React.SFC<SFCSpreadAttributesProps>=(props)=>{const{ children, ...restProps}=props;return(<div{...restProps}>{children}</div>);};

⟩⟩⟩ demo

⇧ back to top


Stateful Components - Class

- stateful counter

import*asReactfrom'react';exportinterfaceStatefulCounterProps{label:string;}typeState={count:number;};exportclassStatefulCounterextendsReact.Component<StatefulCounterProps,State>{state:State={count:0,};handleIncrement=()=>{this.setState({count:this.state.count+1});}render(){const{ handleIncrement}=this;const{ label}=this.props;const{ count}=this.state;return(<div><span>{label}:{count}</span><buttontype="button"onClick={handleIncrement}>{`Increment`}</button></div>);}}

⟩⟩⟩ demo

⇧ back to top

- with default props

import*asReactfrom'react';exportinterfaceStatefulCounterWithDefaultProps{label:string;initialCount?:number;}interfaceDefaultProps{initialCount:number;}interfaceState{count:number;}exportclassStatefulCounterWithDefaultextendsReact.Component<StatefulCounterWithDefaultProps,State>{// to make defaultProps strictly typed we need to explicitly declare their type//@see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640staticdefaultProps:DefaultProps={initialCount:0,};props:StatefulCounterWithDefaultProps&DefaultProps;state:State={count:this.props.initialCount,};componentWillReceiveProps({ initialCount}:StatefulCounterWithDefaultProps){if(initialCount!=null&&initialCount!==this.props.initialCount){this.setState({count:initialCount});}}handleIncrement=()=>{this.setState({count:this.state.count+1});}render(){const{ handleIncrement}=this;const{ label}=this.props;const{ count}=this.state;return(<div><span>{label}:{count}</span><buttontype="button"onClick={handleIncrement}>{`Increment`}</button></div>);}}

⟩⟩⟩ demo

⇧ back to top


Generic Components

  • easily create typed component variations and reuse common logic
  • common use case is a generic list components

- generic list

import*asReactfrom'react';exportinterfaceGenericListProps<T>{items:T[];itemRenderer:(item:T)=>JSX.Element;}exportclassGenericList<T>extendsReact.Component<GenericListProps<T>,{}>{render(){const{ items, itemRenderer}=this.props;return(<div>{items.map(itemRenderer)}</div>);}}

⟩⟩⟩ demo

⇧ back to top


Higher-Order Components

  • function that takes a component and returns a new component
  • a new component will infer Props interface from wrapped Component extended with Props of HOC
  • will filter out props specific to HOC, and the rest will be passed through to wrapped component

- withState

Adds state to a stateless counter

import*asReactfrom'react';import{DiffasSubtract}from'react-redux-typescript';// These props will be subtracted from original component typeinterfaceWrappedComponentProps{count:number;onIncrement:()=>any;}exportconstwithState=<PextendsWrappedComponentProps>(WrappedComponent:React.ComponentType<P>)=>{// These props will be added to original component typeinterfaceProps{initialCount?:number;}interfaceState{count:number;}returnclassWithStateextendsReact.Component<Subtract<P,WrappedComponentProps>&Props,State>{// Enhance component name for debugging and React-Dev-ToolsstaticdisplayName=`withState(${WrappedComponent.name})`;state:State={count:(this.props.initialCount||0)!,};handleIncrement=()=>{this.setState({count:this.state.count+1});}render(){const{ ...remainingProps}=this.props;const{ count}=this.state;return(<WrappedComponent{...remainingProps}count={count}onIncrement={this.handleIncrement}/>);}};};
show usage

import*asReactfrom'react';import{withState}from'@src/hoc';import{SFCCounter}from'@src/components';constSFCCounterWithState=withState(SFCCounter);exportdefault(()=>(<SFCCounterWithStatelabel={'SFCCounterWithState'}/>))asReact.SFC<{}>;

⇧ back to top

- withErrorBoundary

Adds error handling using componentDidCatch to any component

import*asReactfrom'react';import{DiffasSubtract}from'react-redux-typescript';constMISSING_ERROR='Error was swallowed during propagation.';interfaceWrappedComponentProps{onReset?:()=>any;}exportconstwithErrorBoundary=<PextendsWrappedComponentProps>(WrappedComponent:React.ComponentType<P>)=>{interfaceProps{}interfaceState{error:Error|null|undefined;}returnclassWithErrorBoundaryextendsReact.Component<Subtract<P,WrappedComponentProps>&Props,State>{staticdisplayName=`withErrorBoundary(${WrappedComponent.name})`;state:State={error:undefined,};componentDidCatch(error:Error|null,info:object){this.setState({error:error||newError(MISSING_ERROR)});this.logErrorToCloud(error,info);}logErrorToCloud=(error:Error|null,info:object)=>{// TODO: send error report to cloud}handleReset=()=>{this.setState({error:undefined});}render(){const{ children, ...remainingProps}=this.props;const{ error}=this.state;if(error){return(<WrappedComponent{...remainingProps}onReset={this.handleReset}/>);}returnchildren;}};};
show usage

import*asReactfrom'react';import{withErrorBoundary}from'@src/hoc';import{ErrorMessage}from'@src/components';constErrorMessageWithErrorBoundary=withErrorBoundary(ErrorMessage);constErrorThrower=()=>(<buttontype="button"onClick={()=>{thrownewError(`Catch this!`);}}>{`Throw nasty error`}</button>);exportdefault(()=>(<ErrorMessageWithErrorBoundary><ErrorThrower/></ErrorMessageWithErrorBoundary>))asReact.SFC<{}>;

⇧ back to top


Redux Connected Components

Caveat withbindActionCreators

If you try to useconnect orbindActionCreators explicitly and want to type your component callback props as() => void this will raise compiler errors. I happens becausebindActionCreators typings will not map the return type of action creators tovoid, due to a current TypeScript limitations.

A decent alternative I can recommend is to use() => any type, it will work just fine in all possible scenarios and should not cause any typing problems whatsoever. All the code examples in the Guide withconnect are also using this pattern.

If there is any progress or fix in regard to the above caveat I'll update the guide and make an announcement on my twitter/medium (There are a few existing proposals already).

There is alternative way to retain type soundness but it requires an explicit wrapping withdispatch and will be very tedious for the long run. See example below:

const mapDispatchToProps = (dispatch: Dispatch) => ({  onIncrement: () => dispatch(actions.increment()),});

- redux connected counter

import{connect}from'react-redux';import{RootState}from'@src/redux';import{actions,CountersSelectors}from'@src/redux/counters';import{SFCCounter}from'@src/components';constmapStateToProps=(state:RootState)=>({count:CountersSelectors.getReduxCounter(state),});exportconstSFCCounterConnected=connect(mapStateToProps,{onIncrement:actions.increment,})(SFCCounter);
show usage

import*asReactfrom'react';import{SFCCounterConnected}from'@src/connected';exportdefault()=>(<SFCCounterConnectedlabel={'SFCCounterConnected'}/>);

⇧ back to top

- redux connected counter (verbose)

import{bindActionCreators}from'redux';import{connect}from'react-redux';import{RootState,Dispatch}from'@src/redux';import{actions}from'@src/redux/counters';import{SFCCounter}from'@src/components';constmapStateToProps=(state:RootState)=>({count:state.counters.reduxCounter,});constmapDispatchToProps=(dispatch:Dispatch)=>bindActionCreators({onIncrement:actions.increment,},dispatch);exportconstSFCCounterConnectedVerbose=connect(mapStateToProps,mapDispatchToProps)(SFCCounter);
show usage

import*asReactfrom'react';import{SFCCounterConnectedVerbose}from'@src/connected';exportdefault()=>(<SFCCounterConnectedVerboselabel={'SFCCounterConnectedVerbose'}/>);

⇧ back to top

- with own props

import{connect}from'react-redux';import{RootState}from'@src/redux';import{actions,CountersSelectors}from'@src/redux/counters';import{SFCCounter}from'@src/components';exportinterfaceSFCCounterConnectedExtended{initialCount:number;}constmapStateToProps=(state:RootState,ownProps:SFCCounterConnectedExtended)=>({count:CountersSelectors.getReduxCounter(state)+ownProps.initialCount,});exportconstSFCCounterConnectedExtended=connect(mapStateToProps,{onIncrement:actions.increment,})(SFCCounter);
show usage

import*asReactfrom'react';import{SFCCounterConnectedExtended}from'@src/connected';exportdefault()=>(<SFCCounterConnectedExtendedlabel={'SFCCounterConnectedExtended'}initialCount={10}/>);

⇧ back to top


Redux

Action Creators

Using Typesafe Action Creators helpers for Reduxtypesafe-actions

A recommended approach is to use a simple functional helper to automate the creation of type-safe action creators. The advantage is that we can reduce a lot of code repetition and also minimize surface of errors by using type-checked API.

There are more specialized functional helpers available that will help you to further reduce tedious boilerplate and type-annotations in common scenarios like reducers (usinggetType) or epics (usingisActionOf).
All that without losing type-safety! Please check this very shortTutorial

import{createAction}from'typesafe-actions';exportconstactions={increment:createAction('INCREMENT'),add:createAction('ADD',(amount:number)=>({type:'ADD',payload:amount,})),};
show usage

importstorefrom'@src/store';import{actions}from'@src/redux/counters';// store.dispatch(actionCreators.increment(1)); // Error: Expected 0 arguments, but got 1.store.dispatch(actions.increment());// OK => { type: "INCREMENT" }

⇧ back to top


Reducers

State with Type-level Immutability

Declare reducerState type withreadonly modifier to get "type level" immutability

exporttypeState={readonlycounter:number,};

Readonly modifier allow initialization, but will not allow rassignment by highlighting compiler errors

exportconstinitialState:State={counter:0,};// OKinitialState.counter=3;// Error, cannot be mutated

Caveat: Readonly does not provide a recursive immutability on objects

This means that thereadonly modifier doesn't propagate immutability down to "properties" of objects. You'll need to set it explicitly on each nested property that you want.

Check the example below:

exporttypeState={readonlycontainerObject:{readonlyimmutableProp:number,mutableProp:number,}};state.containerObject={mutableProp:1};// Error, cannot be mutatedstate.containerObject.immutableProp=1;// Error, cannot be mutatedstate.containerObject.mutableProp=1;// OK! No error, can be mutated

Best-practices for nested immutability

useReadonly orReadonlyArrayMapped types

exporttypeState=Readonly<{counterPairs:ReadonlyArray<Readonly<{immutableCounter1:number,immutableCounter2:number,}>>,}>;state.counterPairs[0]={immutableCounter1:1,immutableCounter2:1};// Error, cannot be mutatedstate.counterPairs[0].immutableCounter1=1;// Error, cannot be mutatedstate.counterPairs[0].immutableCounter2=1;// Error, cannot be mutated

There are some experiments in the community to make aReadonlyRecursive mapped type. I'll update this section of the guide as soon as they are stable

⇧ back to top

Reducer Example

usinggetType helper andDiscriminated Union types

import{combineReducers}from'redux';import{getType}from'typesafe-actions';import{RootAction}from'@src/redux';import{actions}from'./';exporttypeState={readonlyreduxCounter:number;};exportconstreducer=combineReducers<State,RootAction>({reduxCounter:(state=0,action)=>{switch(action.type){casegetType(actions.increment):returnstate+1;casegetType(actions.add):returnstate+action.payload;default:returnstate;}},});

⇧ back to top


Store Configuration

Create Root State and Root Action Types

RootState - interface representing redux state tree

Can be imported in connected components to provide type-safety to Reduxconnect function

import{combineReducers}from'redux';import{routerReducerasrouter,RouterState}from'react-router-redux';import{reducerascounters,StateasCountersState}from'@src/redux/counters';import{reducerastodos,StateasTodosState}from'@src/redux/todos';interfaceStoreEnhancerState{}exportinterfaceRootStateextendsStoreEnhancerState{router:RouterState;counters:CountersState;todos:TodosState;}import{RootAction}from'@src/redux';exportconstrootReducer=combineReducers<RootState,RootAction>({  router,  counters,  todos,});

⇧ back to top

RootAction - union type of all action objects

Can be imported in various layers receiving or sending redux actions like: reducers, sagas or redux-observables epics

// RootActionsimport{RouterAction,LocationChangeAction}from'react-router-redux';import{getReturnOfExpression}from'react-redux-typescript';import{actionsascountersAC}from'@src/redux/counters';import{actionsastodosAC}from'@src/redux/todos';import{actionsastoastsAC}from'@src/redux/toasts';exportconstallActions={  ...countersAC,  ...todosAC,  ...toastsAC,};constreturnOfActions=Object.values(allActions).map(getReturnOfExpression);typeAppAction=typeofreturnOfActions[number];typeReactRouterAction=RouterAction|LocationChangeAction;exporttypeRootAction=|AppAction|ReactRouterAction;

⇧ back to top

Create Store

When creating the store, use rootReducer. This will set-up astrongly typed Store instance with type inference.

The resulting store instance methods likegetState ordispatch will be type checked and expose type errors

import{createStore,applyMiddleware,compose}from'redux';import{createEpicMiddleware}from'redux-observable';import{rootReducer,rootEpic,RootState}from'@src/redux';constcomposeEnhancers=(process.env.NODE_ENV==='development'&&window&&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)||compose;functionconfigureStore(initialState?:RootState){// configure middlewaresconstmiddlewares=[createEpicMiddleware(rootEpic),];// compose enhancersconstenhancer=composeEnhancers(applyMiddleware(...middlewares));// create storereturncreateStore(rootReducer,initialState!,enhancer);}// pass an optional param to rehydrate state on app startconststore=configureStore();// export store singleton instanceexportdefaultstore;

Async Flow

"redux-observable"

import{combineEpics,Epic}from'redux-observable';import{isActionOf}from'typesafe-actions';import{Observable}from'rxjs/Observable';import{v4}from'uuid';import{RootAction,RootState,allActions}from'@src/redux';import{actions}from'./';constTOAST_LIFETIME=2000;constaddTodoToast:Epic<RootAction,RootState>=(action$,store)=>action$.filter(isActionOf(allActions.addTodo)).concatMap((action)=>{consttoast={id:v4(),text:action.payload};constaddToast$=Observable.of(actions.addToast(toast));constremoveToast$=Observable.of(actions.removeToast(toast.id)).delay(TOAST_LIFETIME);returnaddToast$.concat(removeToast$);});exportconstepics=combineEpics(addTodoToast);

⇧ back to top


Selectors

"reselect"

import{createSelector}from'reselect';import{RootState}from'@src/redux';exportconstgetTodos=(state:RootState)=>state.todos.todos;exportconstgetTodosFilter=(state:RootState)=>state.todos.todosFilter;exportconstgetFilteredTodos=createSelector(getTodos,getTodosFilter,(todos,todosFilter)=>{switch(todosFilter){case'completed':returntodos.filter((t)=>t.completed);case'active':returntodos.filter((t)=>!t.completed);default:returntodos;}},);

⇧ back to top


Action Creators - Alternative Pattern

This pattern is focused on a KISS principle - to stay clear of abstractions and to follow a more complex but familiar JavaScript "const" based approach:

Advantages:

  • familiar to standard JS "const" based approach

Disadvantages:

  • significant amount of boilerplate and duplication
  • more complex compared tocreateAction helper library
  • necessary to export both action types and action creators to re-use in other places, e.g.redux-saga orredux-observable
exportconstINCREMENT='INCREMENT';exportconstADD='ADD';exporttypeActions={INCREMENT:{type:typeofINCREMENT,},ADD:{type:typeofADD,payload:number,},};exportconstactions={increment:():Actions[typeofINCREMENT]=>({type:INCREMENT,}),add:(amount:number):Actions[typeofADD]=>({type:ADD,payload:amount,}),};

Tools

Living Style Guide

⟩⟩⟩ styleguide.config.js

⟩⟩⟩ demo

⇧ back to top


Extras

tsconfig.json

  • Recommended setup for best benefits from type-checking, with support for JSX and ES2016 features
  • Addtslib to minimize bundle size:npm i tslib - this will externalize helper functions generated by transpiler and otherwise inlined in your modules
  • Include absolute imports config working with Webpack
{"compilerOptions":{"baseUrl":"./",// enables absolute path imports"paths":{// define absolute path mappings"@src/*":["src/*"]// will enable -> import { ... } from '@src/components'// in webpack you need to add -> resolve: { alias: { '@src': PATH_TO_SRC }}},"outDir":"dist/",// target for compiled files"allowSyntheticDefaultImports":true,// no errors on commonjs default import"allowJs":true,// include js files"checkJs":true,// typecheck js files"declaration":false,// don't emit declarations"emitDecoratorMetadata":true,"experimentalDecorators":true,"forceConsistentCasingInFileNames":true,"importHelpers":true,// importing helper functions from tslib"noEmitHelpers":true,// disable emitting inline helper functions"jsx":"react",// process JSX"lib":["dom","es2016","es2017.object"],"target":"es5",// "es2015" for ES6+ engines"module":"commonjs",// "es2015" for tree-shaking"moduleResolution":"node","noEmitOnError":true,"noFallthroughCasesInSwitch":true,"noImplicitAny":true,"noImplicitReturns":true,"noImplicitThis":true,"noUnusedLocals":true,"strict":true,"pretty":true,"removeComments":true,"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules","src/**/*.spec.*"]}

⇧ back to top

tslint.json

  • Recommended setup is to extend build-in presettslint:recommended (for all rules usetslint:all)
  • Add tslint react rules:npm i -D tslint-reacthttps://github.com/palantir/tslint-react
  • Amended some extended defaults for more flexibility
{"extends": ["tslint:recommended","tslint-react"],"rules": {"arrow-parens":false,"arrow-return-shorthand": [false],"comment-format": [true,"check-space"],"import-blacklist": [true,"rxjs"],"interface-over-type-literal":false,"interface-name":false,"max-line-length": [true,120],"member-access":false,"member-ordering": [true, {"order":"fields-first" }],"newline-before-return":false,"no-any":false,"no-empty-interface":false,"no-import-side-effect": [true],"no-inferrable-types": [true,"ignore-params","ignore-properties"],"no-invalid-this": [true,"check-function-in-method"],"no-null-keyword":false,"no-require-imports":false,"no-submodule-imports": [true,"@src","rxjs"],"no-this-assignment": [true, {"allow-destructuring":true }],"no-trailing-whitespace":true,"no-unused-variable": [true,"react"],"object-literal-sort-keys":false,"object-literal-shorthand":false,"one-variable-per-declaration": [false],"only-arrow-functions": [true,"allow-declarations"],"ordered-imports": [false],"prefer-method-signature":false,"prefer-template": [true,"allow-single-concat"],"quotemark": [true,"single","jsx-double"],"semicolon": [true,"always"],"trailing-comma": [true, {"singleline":"never","multiline": {"objects":"always","arrays":"always","functions":"never","typeLiterals":"ignore"      },"esSpecCompliant":true    }],"triple-equals": [true,"allow-null-check"],"type-literal-delimiter":true,"typedef": [true,"parameter","property-declaration"],"variable-name": [true,"ban-keywords","check-format","allow-pascal-case","allow-leading-underscore"],// tslint-react"jsx-no-lambda":false  }}

⇧ back to top

jest.config.json

  • Recommended setup for Jest with TypeScript
  • Install withnpm i -D jest-cli ts-jest @types/jest
{"verbose":true,"transform": {".(ts|tsx)":"./node_modules/ts-jest/preprocessor.js"  },"testRegex":"(/spec/.*|\\.(test|spec))\\.(ts|tsx|js)$","moduleFileExtensions": ["ts","tsx","js"  ],"globals": {"window": {},"ts-jest": {"tsConfigFile":"./tsconfig.json"    }  },"setupFiles": ["./jest.stubs.js","./src/rxjs-imports.tsx"  ]}

⇧ back to top

Default and Named Module Exports

Most flexible solution is to use module folder pattern, because you can leverage both named and default import when you see fit.
Using this solution you'll achieve better encapsulation for internal structure/naming refactoring without breaking your consumer code:

// 1. in `components/` folder create component file (`select.tsx`) with default export:// components/select.tsxconstSelect:React.SFC<Props>=(props)=>{...exportdefaultSelect;// 2. in `components/` folder create `index.ts` file handling named imports:// components/index.tsexport{ defaultasSelect}from'./select';...// 3. now you can import your components in both ways, with named export (better encapsulation) or using default export (internal access):// containers/container.tsximport{ Select}from'@src/components';orimportSelectfrom'@src/components/select';...

⇧ back to top

Vendor Types Augmentation

Strategies to fix issues coming from broken "vendor type declarations" files (*.d.ts)

Augmenting library internal type declarations - using relative import resolution

// added missing autoFocus Prop on Input component in "antd@2.10.0" npm packagedeclare module'../node_modules/antd/lib/input/Input'{exportinterfaceInputProps{autoFocus?:boolean;}}

⇧ back to top

Augmenting library public type declarations - using node module import resolution

// fixed broken public type declaration in "rxjs@5.4.1" npm packageimport{Operator}from'rxjs/Operator';import{Observable}from'rxjs/Observable';declare module'rxjs/Subject'{interfaceSubject<T>{lift<R>(operator:Operator<T,R>):Observable<R>;}}

⇧ back to top

To quick-fix missing type declarations for vendor modules you can "assert" a module type withany usingShorthand Ambient Modules

//@src/types/modules.d.tsdeclare module'react-test-renderer';declare module'enzyme';

More advanced scenarios for working with vendor module declarations can be found hereOfficial TypeScript Docs

⇧ back to top

Npm Scripts

Common TS-related npm scripts shared across projects

"check": "npm run lint & npm run tsc","lint": "tslint --project './tsconfig.json'","tsc": "tsc -p . --noEmit","tsc:watch": "tsc -p . --noEmit -w","test": "jest --config jest.config.json","test:watch": "jest --config jest.config.json --watch",

⇧ back to top


FAQ

- should I still use React.PropTypes in TS?

No. With TypeScript, using PropTypes is an unnecessary overhead. When declaring IProps and IState interfaces, you will get complete intellisense and compile-time safety with static type checking. This way you'll be safe from runtime errors and you will save a lot of time on debugging. Additional benefit is an elegant and standardized method of documenting your component external API in the source code.

⇧ back to top

- when to useinterface declarations and whentype aliases?

From practical side, usinginterface declaration will display identity (interface name) in compiler errors, on the contrarytype aliases will be unwinded to show all the properties and nested types it consists of. This can be a bit noisy when reading compiler errors and I like to leverage this distinction to hide some of not so important type details in errors
Relatedts-lint rule:https://palantir.github.io/tslint/rules/interface-over-type-literal/

⇧ back to top

- how to best initialize class instance or static properties?

Prefered modern style is to use class Property Initializers

classStatefulCounterWithInitialCountextendsReact.Component<Props,State>{// default props using Property InitializersstaticdefaultProps:DefaultProps={className:'default-class',initialCount:0,};// initial state using Property Initializersstate:State={count:this.props.initialCount,};  ...}

⇧ back to top

- how to best declare component handler functions?

Prefered modern style is to use Class Fields with arrow functions

classStatefulCounterextendsReact.Component<Props,State>{// handlers using Class Fields with arrow functionshandleIncrement=()=>{this.setState({count:this.state.count+1});};  ...}

⇧ back to top


Roadmap

  • extend HOC section with more advanced examples#5
  • investigate typing patterns for generic component children#7

⇧ back to top

Contribution Guide

  • Don't editREADME.md - it is built withgenerator script from separate.md files located in the/docs/markdown folder, edit them instead
  • For code snippets, they are also injected bygenerator script from the source files located in the playground folder (this step make sure all examples are type-checked and linted), edit them instead

look for include directives in.md files that look like this:::[example|usage]='../../playground/src/components/sfc-counter.tsx'::

Before opening PR please make sure to check:

# run linter in playgroundyarn run lint# run type-checking in playgroundyarn run tsc# re-generate `README.md` from repo rootsh ./generate.sh# ornode ./generator/bin/generate-readme.js

⇧ back to top

Project Examples

https://github.com/piotrwitek/react-redux-typescript-starter-kit
https://github.com/piotrwitek/react-redux-typescript-webpack-starter

⇧ back to top

About

A comprehensive guide to static typing in "React & Redux" apps using TypeScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript92.8%
  • JavaScript5.6%
  • HTML1.2%
  • Shell0.4%

[8]ページ先頭

©2009-2025 Movatter.jp