
git checkout step-4  npm install redux react-redux -S npm install @types/react-redux -D  { "compilerOptions": { "lib": [ "es5", "es6", "es7", "dom" ], "target": "es5", "module": "esnext", "jsx": "react", "moduleResolution": "node" } }  /** * State * *       , *      . */ export interface FieldState { value: string; focus: boolean; } const initialState: FieldState = { value: '', focus: false } /** * Constants * *    ,   . *       ,    *        . */ const SET = 'field/SET'; type SET = typeof SET; const FOCUS = 'field/FOCUS'; type FOCUS = typeof FOCUS; const BLUR = 'field/BLUR'; type BLUR = typeof BLUR; /** * Actions * *  Redux  TypeScript,     * ,     (FieldAction)   */ export interface SetAction { type: SET; payload: string; } export interface FocusAction { type: FOCUS; } export interface BlurAction { type: BLUR; } type FieldAction = SetAction | FocusAction | BlurAction; /** * Reducer * *    ,     ,  *      . *  action     ,  *       (FieldAction),   *      ( case SET)   *    action. */ export default function reducer(state: FieldState = initialState, action: FieldAction): FieldState { switch (action.type) { case SET: return { ...state, value: action.payload } case FOCUS: return { ...state, focus: true } case BLUR: return { ...state, focus: false } default: return state; } } /** * Action Creators * *      , *        *  ,       * . */ export const set = (payload: string): SetAction => ({ type: SET, payload }); export const focus = (): FocusAction => ({ type: FOCUS }); export const blur = (): BlurAction => ({ type: BLUR });  import { combineReducers } from 'redux'; import fieldReducer from './field'; export default combineReducers({ field: fieldReducer })  import { createStore } from 'redux'; import rootReducer from '../redux'; import { FieldState } from '../redux/field'; /** *       mapStateToProps, *    ,       * (,     redux-thunk) */ export interface IStore { field: FieldState } /** *          . */ const configureStore = (initialState?: IStore) => { return createStore( rootReducer, initialState, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) }; export default configureStore;  interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any; __REDUX_DEVTOOLS_EXTENSION__: any; }  import * as React from 'react'; import { connect, Dispatch, DispatchProp } from 'react-redux'; import { IStore } from '../store'; import { set, focus, blur } from '../redux/field'; /** *   DispatchProp ,   dispatch  *   .    connect,    *  ( mapDispatchToProps) */ interface FieldProps extends DispatchProp<IStore>, React.HTMLProps<HTMLInputElement> { value?: string; } class Field extends React.Component<FieldProps, {}> { handleChange = (event: React.FormEvent<HTMLInputElement>) => { const { dispatch } = this.props; const value = event.currentTarget.value; /** *     set  dispatch  *   . */ dispatch(set(value)); } handleFocus = () => { const { dispatch } = this.props; dispatch(focus()); } handleBlur = () => { const { dispatch } = this.props; dispatch(blur()); } render() { const { value, dispatch, ...inputProps } = this.props; return ( <input {...inputProps} type="text" value={value} onChange={this.handleChange} onFocus={this.handleFocus} onBlur={this.handleBlur} /> ); } } /** *   mapStateToProps,   (  ) *        */ const mapStateToProps = (state: IStore, ownProps: FieldProps) => ({ value: state.field.value }); /** *  mapDispatchToProps: * (dispatch: Dispatch<IStore>, ownProps: FieldProps) => ({ ... }) */ /** * connect   ,   10   *   . *  ,   3 : *  mapStateToProps,  mapDispatchToProps,  *   . *  ,       , *         . */ export default connect<{}, {}, FieldProps>(mapStateToProps)(Field);  import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import configureStore from './store'; import Field from './components/Field'; /** *   ,   initialState  * window.__INITIAL_STATE__,       *    . */ const store = configureStore(); /** *          *  DevTools */ const App = () => ( <Provider store={store}> <div> <h1>Hello, Redux!</h1> <Field placeholder='I like dev tools!' /> </div> </Provider> ); ReactDOM.render(<App />, document.getElementById('root'));  git checkout step-5  import { Middleware, MiddlewareAPI, Dispatch, Action } from 'redux'; import { IStore } from '../store'; /** *    store - MiddlewareAPI<S & IStore> -   *   ,      *    .     , *    middleware  -. */ const logger: Middleware = <S>(store: MiddlewareAPI<S & IStore>) => (next: Dispatch<S>) => //   - <A extends Action>(action: A),   . (action: any) => { //     store.getState().field.value; console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } export default logger;  import { createStore, compose, applyMiddleware } from 'redux'; import rootReducer from '../redux'; import { FieldState } from '../redux/field'; import logger from '../middlewares/logger'; export interface IStore { field: FieldState } let composeEnhancers = compose; //      middleware const middlewares = [ logger ]; if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; } const configureStore = (initialState?: IStore) => { return createStore( rootReducer, initialState, composeEnhancers( applyMiddleware(...middlewares) ) ) }; export default configureStore;  import { Reducer, Action } from 'redux'; import { IStore } from '../store'; /** *      ,  * ,   createNamedReducer */ export interface namedAction extends Action { name: string; } function createNamedReducer<S>(reducer: Reducer<S>, reducerName: string): Reducer<S> { return (state: S, action: namedAction) => { const { name } = action; const isInitializationCall = state === undefined; if (name !== reducerName && !isInitializationCall) { return state; } return reducer(state, action); } } export default createNamedReducer;  git checkout step-6  npm install axios redux-thunk -S  import axios from 'axios'; /** *        redux * ,       . */ const client = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com' }); export default client;  import { AxiosPromise } from 'axios'; import client from './client'; //       export interface IUser { id: number; name: string; username: string; email: string; address: any; phone: string; website: string; company: any; } export function get(id: number): AxiosPromise<IUser> { return client.get(`/users/${id}`); } export function getList(): AxiosPromise<IUser[]> { return client.get('/users'); }  import { Dispatch } from 'redux'; import { IStore } from '../store'; import * as client from '../services/users'; //     type Error = any; //       http  interface AsyncState<D> { isFetching: boolean; error: Error; data: D; } //    ,      interface AsyncAction<P> { status?: 'error' | 'success'; payload?: P | Error; } /** * State */ export interface UsersState { get: AsyncState<client.IUser>; getList: AsyncState<client.IUser[]>; } const initialState: UsersState = { get: { isFetching: false, error: null, data: null }, getList: { isFetching: false, error: null, data: [] } } /** * Constants */ const GET = 'users/GET'; type GET = typeof GET; const GET_LIST = 'users/GET_LIST'; type GET_LIST = typeof GET_LIST; /** * Actions */ export interface GetAction extends AsyncAction<client.IUser> { type: GET; } export interface GetListAction extends AsyncAction<client.IUser[]> { type: GET_LIST; } type UsersAction = GetAction | GetListAction; /** * Reducer * * ,  ! *   ,       *    ,    *   . *     ,     * ,  -   . */ export default function reducer(state: UsersState = initialState, action: UsersAction): UsersState { switch (action.type) { case GET: if (!action.status) { return { ...state, get: { ...state.get, isFetching: true, error: null } } } if (action.status === 'error') { return { ...state, get: { isFetching: false, error: action.payload, data: null } } } return { ...state, get: { isFetching: false, error: null, data: action.payload } } case GET_LIST: if (!action.status) { return { ...state, getList: { ...state.getList, isFetching: true, error: null } } } if (action.status === 'error') { return { ...state, getList: { isFetching: false, error: action.payload, data: [] } } } return { ...state, getList: { isFetching: false, error: null, data: action.payload } } default: return state; } } /** * Action Creators */ export const getActionCreator = ( status?: 'error' | 'success', payload?: client.IUser | Error ): GetAction => ({ type: GET, status, payload, }); export const getListActionCreator = ( status?: 'error' | 'success', payload?: client.IUser[] | Error ): GetListAction => ({ type: GET_LIST, status, payload, }); /** * Thunk Actions */ export function get(id: number) { return async (dispatch: Dispatch<IStore>, getState: () => IStore) => { dispatch(getActionCreator()); try { const response = await client.get(id); dispatch(getActionCreator('success', response.data)); } catch (e) { dispatch(getActionCreator('error', e)); throw new Error(e); } } } export function getList() { return async (dispatch: Dispatch<IStore>, getState: () => IStore) => { dispatch(getListActionCreator()); try { const response = await client.getList(); dispatch(getListActionCreator('success', response.data)); } catch (e) { dispatch(getListActionCreator('error', e)); throw new Error(e); } } }  import { combineReducers } from 'redux'; import fieldReducer from './field'; import usersReducer from './users'; export default combineReducers({ field: fieldReducer, users: usersReducer });  import { createStore, compose, applyMiddleware } from 'redux'; import ReduxThunk from 'redux-thunk' import rootReducer from '../redux'; import { FieldState } from '../redux/field'; import { UsersState } from '../redux/users'; import logger from '../middlewares/logger'; export interface IStore { field: FieldState, users: UsersState } let composeEnhancers = compose; const middlewares = [ logger, ReduxThunk ]; if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; } const configureStore = (initialState?: IStore) => { return createStore( rootReducer, initialState, composeEnhancers( applyMiddleware(...middlewares) ) ) }; export default configureStore;  import * as React from 'react'; import { connect, Dispatch, DispatchProp } from 'react-redux'; import { IStore } from '../store'; import { getList, Error } from '../redux/users'; import { IUser } from '../services/users'; interface UsersProps extends DispatchProp<IStore> { isFetching?: boolean; error?: Error; users?: IUser[]; } class Users extends React.Component<UsersProps, {}> { componentDidMount() { const { dispatch } = this.props; dispatch(getList()); } render() { const { isFetching, error, users } = this.props; if (error) { return <b> !</b> } if (isFetching) { return '...'; } return users.map((user) => <div>{user.name}</div>); } } const mapStateToProps = (state: IStore, ownProps: UsersProps) => ({ isFetching: state.users.getList.isFetching, error: state.users.getList.error, users: state.users.getList.data }); export default connect<{}, {}, UsersProps>(mapStateToProps)(Users);  git checkout step-7  npm install core-js -S npm install @types/core-js -D  import 'core-js/es6/promise'; import 'core-js/es6/map'; import 'core-js/es6/set'; if (typeof window.requestAnimationFrame !== 'function') { window.requestAnimationFrame = (callback: FrameRequestCallback) => window.setTimeout(callback, 0); } ...  npm install better-npm-run -D  { ... "scripts": { "start": "better-npm-run dev", "build": "better-npm-run build" }, "betterScripts": { "dev": { "command": "webpack-dev-server", "env": { "NODE_ENV": "development" } }, "build": { "command": "webpack", "env": { "NODE_ENV": "production" } } }, ... }  const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); //    const env = process.env.NODE_ENV; const __DEV__ = env === 'development'; const __PRODUCTION__ = env === 'production'; const paths = { src: path.resolve(__dirname, 'src'), dist: path.resolve(__dirname, 'dist') }; const config = { context: paths.src, entry: { app: './index' }, //     ,   output: { path: paths.dist, filename: __PRODUCTION__ ? '[name].bundle.[chunkhash].js' : '[name].bundle.js', chunkFilename: __PRODUCTION__ ? '[name].bundle.[chunkhash].js' : '[name].bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] }, module: { rules: [ { test: /\.tsx?$/, loader: 'awesome-typescript-loader' } ] }, plugins: [ //   NODE_ENV     new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(env) }), new HtmlWebpackPlugin({ template: './index.html' }), //      new webpack.optimize.ModuleConcatenationPlugin() ] }; if (__DEV__) { //  source map  development  config.devtool = 'inline-source-map'; } if (__PRODUCTION__) { config.plugins.push(new CleanWebpackPlugin(['dist'])); //   config.plugins.push(new webpack.optimize.UglifyJsPlugin()); } module.exports = config;  npm run build Source: https://habr.com/ru/post/341132/
All Articles