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

aocsa/react-redux-typescript-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This guide isNOT about"How to write type declarations for every possible variable and expression to have 100% type covered code and waste a lot of time".
This guide is about"How to write type declarations to only the minimum necessary amount of JavaScript code and still get all the benefits of Static Typing".

found it usefull, want some more?give it a ⭐

Announcements

  • All the examples ported to TypeScript v2.5 and using recent type definitions forreact &reduxTypeScript Changelog
  • create more strict type definitions for redux
  • extend HOC section with more advanced examples#5
  • investigate typing patterns for component children#7

Introduction

This guide is aimed to use--strict flag of TypeScript compiler to provide the best static-typing experience.

Benefits of this setup and static-typing in general include:

  • when making changes in your code, precise insight of the impact on the entire codebase (by showing all the references in the codebase for any given piece of code)
  • when implementing new features compiler validate all props passed to components or injected by connect from redux store, validation of action creator params, payload objects structure and state/action objects passed to a reducer - showing all possible JavaScript errors)

Additionally static-typing will make processes of improving your codebase and refactoring much easier and give you a confidence that you will not break your production code.

Playground

Code examples are generated from the source code inplayground folder. They are tested with TypeScript compiler with the most recent version of TypeScript and relevant type definitions (like@types/react or@types/react-redux) to ensure they are still working with recent definitions.Moreover playground is created is such way, that you can easily clone repository, installnpm dependencies and play around with all the examples from this guide in real project environment without any extra setup.

Goals


Table of Contents


React

Stateless Components - SFC

  • convenient alias:React.SFC<Props> === React.StatelessComponent<Props>

stateless counter example

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

import*asReactfrom'react';import{SFCCounter}from'@src/components';letcount=0;constincrementCount=()=>count++;exportdefault()=>(<SFCCounterlabel={'SFCCounter'}count={count}onIncrement={incrementCount}/>);


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

import*asReactfrom'react';import{SFCSpreadAttributes}from'@src/components';exportdefault()=>(<SFCSpreadAttributesstyle={{backgroundColor:'lightcyan'}}/>);


Stateful Components - Class

stateful counter example

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>{label}:{count}<buttontype="button"onClick={handleIncrement}>{`Increment`}</button></div>);}}
SHOW USAGE

import*asReactfrom'react';import{StatefulCounter}from'@src/components';exportdefault()=>(<StatefulCounterlabel={'StatefulCounter'}/>);


stateful counter with default props example

import*asReactfrom'react';exportinterfaceStatefulCounterWithInitialCountProps{label:string,initialCount?:number,}interfaceDefaultProps{initialCount:number,}typePropsWithDefaults=StatefulCounterWithInitialCountProps&DefaultProps;interfaceState{count:number,}exportconstStatefulCounterWithInitialCount:React.ComponentClass<StatefulCounterWithInitialCountProps>=classextendsReact.Component<PropsWithDefaults,State>{// to make defaultProps strictly typed we need to explicitly declare their type//@see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11640staticdefaultProps:DefaultProps={initialCount:0,};state:State={count:this.props.initialCount,};componentWillReceiveProps({ initialCount}:PropsWithDefaults){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>{label}:{count}<buttontype="button"onClick={handleIncrement}>{`Increment`}</button></div>);}};
SHOW USAGE

import*asReactfrom'react';import{StatefulCounterWithInitialCount}from'@src/components';exportdefault()=>(<StatefulCounterWithInitialCountlabel={'StatefulCounter'}initialCount={10}/>);


Generic Components

  • easily create typed component variations and reuse common logic
  • especially useful to create typed list components

generic list component example

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>);}}
SHOW USAGE

import*asReactfrom'react';import{IUser}from'@src/models';import{GenericList}from'@src/components';exportconstUserList=classextendsGenericList<IUser>{};exportdefault({ users}:{users:IUser[]})=>(<UserListitems={users}itemRenderer={(item)=><divkey={item.id}>{item.fullName}</div>}/>);


Connected Components

connected counter example - concise

import{connect}from'react-redux';import{RootState}from'@src/redux';import{actionCreators}from'@src/redux/counters';import{SFCCounter}from'@src/components';constmapStateToProps=(state:RootState)=>({count:state.counters.sfcCounter,});exportconstSFCCounterConnected=connect(mapStateToProps,{onIncrement:actionCreators.incrementSfc,})(SFCCounter);
SHOW USAGE

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


connected counter example - verbose

import{bindActionCreators}from'redux';import{connect}from'react-redux';import{RootState,Dispatch}from'@src/redux';import{actionCreators}from'@src/redux/counters';import{SFCCounter}from'@src/components';constmapStateToProps=(state:RootState)=>({count:state.counters.sfcCounter,});constmapDispatchToProps=(dispatch:Dispatch)=>bindActionCreators({onIncrement:actionCreators.incrementSfc,},dispatch);exportconstSFCCounterConnectedVerbose=connect(mapStateToProps,mapDispatchToProps)(SFCCounter);
SHOW USAGE

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


connected counter example - with own props

import{connect}from'react-redux';import{RootState}from'@src/redux';import{actionCreators}from'@src/redux/counters';import{SFCCounter}from'@src/components';exportinterfaceSFCCounterConnectedExtended{initialCount:number,}constmapStateToProps=(state:RootState,ownProps:SFCCounterConnectedExtended)=>({count:state.counters.sfcCounter,});exportconstSFCCounterConnectedExtended=connect(mapStateToProps,{onIncrement:actionCreators.incrementSfc,})(SFCCounter);
SHOW USAGE

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


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

basic hoc: enhance stateless counter with state

import*asReactfrom'react';import{Omit}from'@src/types/react-redux-typescript';interfaceRequiredProps{count:number,onIncrement:()=>any,}typeProps<TextendsRequiredProps>=Omit<T,keyofRequiredProps>;interfaceState{count:number,}exportfunctionwithState<WrappedComponentPropsextendsRequiredProps>(WrappedComponent:React.ComponentType<WrappedComponentProps>,){constHOC=classextendsReact.Component<Props<WrappedComponentProps>,State>{state:State={count:0,};handleIncrement=()=>{this.setState({count:this.state.count+1});};render(){const{ handleIncrement}=this;const{ count}=this.state;return(<WrappedComponentcount={count}onIncrement={handleIncrement}/>);}};returnHOC;}
SHOW USAGE

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


advanced hoc: add error handling with componentDidCatch to view component

import*asReactfrom'react';constMISSING_ERROR='Error was swallowed during propagation.';interfaceProps{}interfaceState{error:Error|null|undefined,}interfaceWrappedComponentProps{onReset:()=>any,}exportfunctionwithErrorBoundary(WrappedComponent:React.ComponentType<WrappedComponentProps>,){constHOC=classextendsReact.Component<Props,State>{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}=this.props;const{ error}=this.state;if(error){return(<WrappedComponentonReset={this.handleReset}/>);}returnchildrenasany;}};returnHOC;}
SHOW USAGE

import*asReactfrom'react';import{withErrorBoundary}from'@src/hoc';import{ErrorMessage}from'@src/components';constErrorMessageWithErrorBoundary=withErrorBoundary(ErrorMessage);exportdefault(({ children})=>(<ErrorMessageWithErrorBoundary>{children}</ErrorMessageWithErrorBoundary>))asReact.SFC<{}>;


Redux

Actions

KISS Style

This pattern is focused on a KISS principle - to stay clear of complex proprietary abstractions and follow simple and familiar JavaScript const based types:

  • classic const based types
  • very close to standard JS usage
  • standard amount of boilerplate
  • need to export action types and action creators to re-use in other places, e.g.redux-saga orredux-observable
exportconstINCREMENT_SFC='INCREMENT_SFC';exportconstDECREMENT_SFC='DECREMENT_SFC';exporttypeActions={INCREMENT_SFC:{type:typeofINCREMENT_SFC,},DECREMENT_SFC:{type:typeofDECREMENT_SFC,},};// Action CreatorsexportconstactionCreators={incrementSfc:():Actions[typeofINCREMENT_SFC]=>({type:INCREMENT_SFC,}),decrementSfc:():Actions[typeofDECREMENT_SFC]=>({type:DECREMENT_SFC,}),};
SHOW USAGE

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

DRY Style

A more DRY approach, introducing a simple factory function to automate the creation of typed action creators. The advantage here is that we can reduce boilerplate and code repetition. It is also easier to re-use action creators in other places because oftype property on action creator containing type constant:

  • using factory function to automate creation of typed action creators - (source code to be revealed)
  • less boilerplate and code repetition than KISS Style
  • action creators have readonlytype property (this make usingtype constants redundant and easier to re-use in other places e.g.redux-saga orredux-observable)
import{createActionCreator}from'react-redux-typescript';typeSeverity='info'|'success'|'warning'|'error';// Action CreatorsexportconstactionCreators={incrementCounter:createActionCreator('INCREMENT_COUNTER'),showNotification:createActionCreator('SHOW_NOTIFICATION',(message:string,severity?:Severity)=>({ message, severity}),),};// Examplesstore.dispatch(actionCreators.incrementCounter(4));// Error: Expected 0 arguments, but got 1.store.dispatch(actionCreators.incrementCounter());// OK: { type: "INCREMENT_COUNTER" }actionCreators.incrementCounter.type==="INCREMENT_COUNTER"// truestore.dispatch(actionCreators.showNotification());// Error: Supplied parameters do not match any signature of call target.store.dispatch(actionCreators.showNotification('Hello!'));// OK: { type: "SHOW_NOTIFICATION", payload: { message: 'Hello!' }}store.dispatch(actionCreators.showNotification('Hello!','info'));// OK: { type: "SHOW_NOTIFICATION", payload: { message: 'Hello!', severity: 'info }}actionCreators.showNotification.type==="SHOW_NOTIFICATION"// true

Reducers

Relevant TypeScript Docs references:

Reducer State

Declare reducerState type definition with readonly modifier fortype level immutability

exporttypeState={readonlycounter:number,};

Readonly modifier allow initialization, but will not allow rassignment highlighting an error

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

Caveat: Readonly does not provide recursive immutability on objects

This means that readonly modifier does not propagate immutability on nested properties of objects or arrays of objects. You'll need to set it explicitly on each nested property.

exporttypeState={readonlycounterContainer:{readonlyreadonlyCounter:number,mutableCounter:number,}};state.counterContainer={mutableCounter:1};// Error, cannot be mutatedstate.counterContainer.readonlyCounter=1;// Error, cannot be mutatedstate.counterContainer.mutableCounter=1;// No error, can be mutated

There are few utilities to help you achieve nested immutability. e.g. you can do it quite easily by using convenientReadonly orReadonlyArray mapped types.

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

There are some experiments in the community to make aReadonlyRecursive mapped type, but I'll need to investigate if they really works

Reducer with classicconst types

import{combineReducers}from'redux';import{RootAction}from'@src/redux';import{INCREMENT_SFC,DECREMENT_SFC,}from'./';exporttypeState={readonlysfcCounter:number,};exportconstreducer=combineReducers<State,RootAction>({sfcCounter:(state=0,action)=>{switch(action.type){caseINCREMENT_SFC:returnstate+1;caseDECREMENT_SFC:returnstate+1;default:returnstate;}},});

Reducer with statictype property from helper factory function -createActionCreator

exportconstreducer:Reducer<State>=(state=0,action:RootAction)=>{switch(action.type){caseactionCreators.increment.type:returnstate+1;caseactionCreators.decrement.type:returnstate-1;default:returnstate;}};

Store Types

  • RootAction - statically typed global action types

  • should be imported in layers dealing with redux actions like: reducers, redux-sagas, redux-observables
// RootActionsimport{RouterAction,LocationChangeAction}from'react-router-redux';import{ActionsasCountersActions}from'@src/redux/counters';import{ActionsasTodosActions}from'@src/redux/todos';import{ActionsasToastsActions}from'@src/redux/toasts';typeReactRouterAction=RouterAction|LocationChangeAction;exporttypeRootAction=|ReactRouterAction|CountersActions[keyofCountersActions]|TodosActions[keyofTodosActions]|ToastsActions[keyofToastsActions];
  • RootState - statically typed global state tree

  • should be imported in connected components providing 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,});

Create Store

  • creating store - useRootState (incombineReducers and when providing preloaded state object) to set-upstate object type guard to leverage strongly typed Store instance
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<RootState>(rootReducer,initialState!,enhancer,);}// pass an optional param to rehydrate state on app startconststore=configureStore();// export store singleton instanceexportdefaultstore;

Ecosystem


Async Flow with "redux-observable"

// import rxjs operators somewhere...import{combineEpics,Epic}from'redux-observable';import{RootAction,RootState}from'@src/redux';import{saveState}from'@src/services/local-storage-service';constSAVING_DELAY=1000;// persist state in local storage every 1sconstsaveStateInLocalStorage:Epic<RootAction,RootState>=(action$,store)=>action$.debounceTime(SAVING_DELAY).do((action:RootAction)=>{// handle side-effectssaveState(store.getState());}).ignoreElements();exportconstepics=combineEpics(saveStateInLocalStorage,);

Selectors with "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;}},);

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,"strictNullChecks":true,"pretty":true,"removeComments":true,"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules","src/**/*.spec.*"]}

tslint.json

  • Recommended setup is to extend build-in presettslint:latest (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:latest","tslint-react"],"rules": {"arrow-parens":false,"arrow-return-shorthand": [false],"comment-format": [true,"check-space"],"import-blacklist": [true,"rxjs"],"interface-over-type-literal":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-inferrable-types": [true,"ignore-params","ignore-properties"],"no-invalid-this": [true,"check-function-in-method"],"no-null-keyword":false,"no-require-imports":false,"no-switch-case-fall-through":true,"no-trailing-whitespace":true,"no-this-assignment": [true, {"allow-destructuring":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"],"semicolon": [true,"ignore-interfaces"],"quotemark": [true,"single","jsx-double"],"triple-equals": [true,"allow-null-check"],"typedef": [true,"parameter","property-declaration"],"variable-name": [true,"ban-keywords","check-format","allow-pascal-case","allow-leading-underscore"]  }}

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 named (internal) or default (public):// containers/container.tsximport{ Select}from'@src/components';orimportSelectfrom'@src/components/select';...

Fixing Vendor Type Issues

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

  • Augumenting 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;}}
  • Augumenting 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>;}}

FAQ

- how to install react & redux types?

// reactnpm i -D @types/react @types/react-dom @types/react-redux// redux has types included in it's npm package - don't install from @types

- should I still use React.PropTypes in TS?

No. When using TypeScript it 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 standarized method of documenting your component external API in the source code.

- 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/

- 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,};  ...}

- 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});};  ...}

Project Examples

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

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

  • TypeScript60.4%
  • Other38.8%
  • HTML0.8%

[8]ページ先頭

©2009-2025 Movatter.jp