I. Project foundation construction

  • 1. Create a file and initialize it

    npm init -y
    tsc --init
    touch .gitignore
    Copy the code
  • 2. Install dependency packages

    #Basic package
    npm install react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom 
    #Install the package about WebPack
    npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D
    #Installed to handle TS
    npm i typescript ts-loader source-map-loader -D
    #Install redux related
    npm install redux react-redux @types/react-redux redux-thunk  redux-logger @types/redux-logger redux-promise @types/redux-promise
    #Install libraries that route to redux connections
    npm install connected-react-router
    #Process-style
    npm install style-loader css-loader less-loader less -D
    #prefixed
    npm install autoprefixer postcss-loader -D
    #Processing picture address class
    npm install url-loader file-loader lib-flexible -D
    #Rem px conversion (see need to install, only for mobile sites)
    npm install px2rem-loader
    #The ajax request library
    npm install axios
    #React Multicast map component library
    npm install react-swipe @types/react-swipe
    #React Animation library
    npm install react-transition-group @types/react-transition-group
    Copy the code
  • Modify the tsconfig.json file

    {
      "compilerOptions": {
        "outDir": "./dist"./* Specifies the output directory */
        "sourceMap": true./* When compiling ts files into js files, generate the corresponding sourceMap file */
        "noImplicitAny": true./* If true, the ts compiler will still compile to js files when it cannot infer the type, but will */
        "module": "commonjs"./ * * /
        "target": "es5"./* to es5*/
        "jsx": "react"."esModuleInterop": true."baseUrl": ".".// Find the starting position of modules that are not relative paths
        "paths": { // Define an alias
          "@ / *": [
            "src/*"]}},"include": [ /* The file to compile */
        "./src/**/*"]}Copy the code
  • 4. Configure the WebPack file

    Create a webpack.config.js file under the webpack.config.js project

    const path = require('path');
    const webpack = require('webpack');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'.entry: './src/index.tsx'.output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',},devtool: 'source-map'.resolve: {
        // Define an alias
        alias: {
          The '@': path.resolve(__dirname, 'src'),
          '~': path.resolve(__dirname, 'node_modules')},// When you load a file with no specified extension, it automatically looks for which extension
        extensions: ['.ts'.'.tsx'.'.js'.'.json']},module: {
        rules: [{test: /\.(j|t)sx? /,
            loader: 'ts-loader'.options: {
              transpileOnly: true.// Only compile without checking
              compilerOptions: {
                module: 'es2015'}}}, {test: /\.css$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader'.options: {
                  importLoaders: 0}},/ / {
              // loader: 'postcss-loader',
              // options: {
              // plugins: [require('autoprefixer')]
              / /}
              / /}] {},test: /\.less$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader'.options: {
                  importLoaders: 0}},/ / {
              // loader: 'postcss-loader',
              // options: {
              // plugins: [require('autoprefixer')]
              / /}
              / /},
              // If it is a mobile phone, configure it
              / / {
              // loader: 'px2remote-loader',
              // options: {
              // remUnit: 75, // Base size
              // Precesion: 8 // accurate to how many digits
              / /}
              // },
              'less-loader',]},// Handle image classes
          {
            test: /\.(jpg|png|gif|svg|jpeg)$/,
            use: ['url-loader']]}},plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',})./ / hot update
        new webpack.HotModuleReplacementPlugin(),
      ],
      devServer: {
        hot: true.contentBase: path.join(__dirname, 'dist'),
        open: false.port: 3000.historyApiFallback: {
          // When browserHistory is refreshed, 404 is automatically redirected to index.html
          index: './index.html'}}}Copy the code
  • Create a SRC directory and create two files index. HTML and index. TSX

  • 6. Configure the startup command in package.json

    {..."scripts": {
        "dev": "webpack-dev-server"."build": "webpack"}},Copy the code
  • 7. Write the react code in the SRC /index.tsx file

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    ReactDOM.render(
      <div>I created it using React</div>.document.getElementById('root'))Copy the code
  • 8. Write a basic container in SRC /index.html

    <! DOCTYPEhtml>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Manually set up the React project</title>
    </head>
    
    <body>
      <div id="root"></div>
    </body>
    
    </html>
    Copy the code
  • 9, if you are a mobile website to add adaptation (see the project needs to write)

    <! DOCTYPEhtml>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
      <title>Manually set up the React project</title>
    
    </head>
    
    <body>
      <script>
        const docEl = document.documentElement;
        function setRemUtils() {
          docEl.style.fontSize = docEl.clientWidth / 10 + 'px';
        }
        setRemUtils();
        window.addEventListener('resize', setRemUtils, false);
      </script>
      <div id="root"></div>
    </body>
    
    </html>
    Copy the code
  • 10. Start the project

Second,reactIn combination withreduxUsed together

  • Create a store folder under SRC

  • 2. General directory structure

    ➜ store tree. ├ ─ ─ action - types. Ts # define constants ├ ─ ─ the actions # a component corresponding to an action ├ ─ ─ index. The ts └ ─ ─ reducers # a component corresponding to a reducer └ ─ ─ index.ts 2 directories, 3 filesCopy the code
  • 3. In the store/index.ts file

    import { createStore, applyMiddleware } from 'redux';
    import logger from 'redux-logger';
    import promise from 'redux-promise';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    
    const store = applyMiddleware( promise, thunk, logger)(createStore)(rootReducer);
    // Mount to window for easy viewing
    (<any>window).store = store;
    export default store;
    Copy the code
  • 4. The Store /reducers/index.ts file is used to integrate reducer in all components

    import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux'
    
    The reducer state type of each component can be put into a separate reducer file
    export interface CombinedState {
    
    }
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      Reducer of each component
    };
    
    const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers);
    export default rootReducers;
    Copy the code
  • 5, use store in SRC /index.tsx

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    import store from './store';
    
    ReactDOM.render(
      <Provider store={store}>
        <div>I created it using React</div>
      </Provider>
      ,
      document.getElementById('root'))Copy the code

Three, test,storeCheck whether the configuration is successful

  • 1. Define two constants in action-types.ts

    export const ADD_COUNT = 'ADD_COUNT';
    export const MINUS_COUNT = 'MINUS_COUNT';
    Copy the code
  • Create a reducer file from SRC /store.counter. Ts

    import { AnyAction } from 'redux';
    import * as types from '. /.. /action-types';
    
    export interface Counter {
      count: number
    }
    const initState: Counter = {
      count: 0};export default function (state: Counter = initState, action: AnyAction) {
      switch (action.type) {
        case types.ADD_COUNT:
          return { ...state, count: state.count + 1 };
        case types.MINUS_COUNT:
          return { ...state, count: state.count - 1 };
        default:
          returnstate; }}Copy the code
  • 3, in the SRC/store/reducers/index. The ts reducer for the introduction of the component

    .import counter, { Counter } from './counter';
    
    // State type of reducer of each component
    export interface CombinedState {
      counter: Counter,
    }
    
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      Reducer of each componentcounter, }; .Copy the code
  • 4. Define actions files

    import * as types from '. /.. /action-types';
    
    export default {
      // Add method
      // Payload Specifies the parameter that triggers the function
      addCount(payload: number) {
        console.log('I'm the payload that comes in.', payload);
        return {
          type: types.ADD_COUNT,
          payload,
        }
      },
      // The reduction method
      minusCount(payload: number) {
        return {
          type: types.MINUS_COUNT,
          payload,
        }
      }
    }
    Copy the code
  • Create a component components/ counter.tsx

    import React, { PropsWithChildren } from 'react';
    import { connect } from 'react-redux';
    
    import action from '@/store/actions/counter';
    
    type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>;
    // Define an arbitrary one
    type State = { [propsName: string] :any }
    class Counter extends React.Component<Props.State> {
      render() {
        console.log(this.props);
        return (
          <>
            <button onClick={this.props.minusCount}>-</button>{this.props. Count} {/* If you want to pass the payload, you need to write */}<button onClick={()= > this.props.addCount(10)}>+</button>
          </>)}}const mapStateToProps = (state: any) = > (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter);
    Copy the code
  • 6. Use this component in SRC /index.tsx and test it successfully

4. Browser configurationRedux DevToolsObserve changes in the state

  • 1. Install Redux DevTools for Google Chrome.

  • 2. Redux-devtools-extension

  • SRC /store/index.ts

    • Direct use of
    .import { composeWithDevTools } from 'redux-devtools-extension';
    // Write it the other way
    conststore = createStore(rootReducer, composeWithDevTools(applyMiddleware(promise, thunk, logger))); .Copy the code
    • Distinguish between development environment and production environment
    // The development environment uses tools and logging middleware
    const enhancers = process.env.NODE_ENV === "development" ? composeWithDevTools(
      applyMiddleware(promise, thunk, logger)
    ) : applyMiddleware(promise, thunk);
    const store = createStore(rootReducer, enhancers);
    Copy the code
  • 4. Operation effect drawing

Optimize code, definitionstoreType constraint of

  • 1. Create a folder SRC /typings in your project

  • 2. Create a file typings/counter. Ts to constrain the counter

    export interface CounterState {
      count: number
    }
    Copy the code
  • 3, create a typings/state.ts file

    import { CounterState } from '. ';
    
    // State type of reducer of each component
    export interface CombinedState {
      counter: CounterState,
    }
    
    export interface CounterPayload {
      count: number,}Copy the code
  • 4, streamline store/reducers/counter. Ts code

    import { AnyAction } from 'redux';
    import * as types from '. /.. /action-types';
    
    import { CounterState } from '@/typings';
    
    const initState: CounterState = {
      count: 0};export default function (state: CounterState = initState, action: AnyAction) {... }Copy the code
  • 5. Simplify store/reducers/index.ts code

    import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux'
    
    import counter from './counter';
    import { CombinedState } from '@/typings';
    
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      Reducer of each component
      counter,
    };
    
    const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers);
    export default rootReducers;
    Copy the code
  • 6. Optimize store/actions/counter

    import * as types from '. /.. /action-types';
    import { CounterPayload } from '@/typings';
    
    export default {
      // Add method
      // Payload Specifies the parameter that triggers the function
      addCount(payload: CounterPayload) {
        console.log('I'm the payload that comes in.', payload);
        return {
          type: types.ADD_COUNT,
          payload,
        }
      },
      // The reduction method
      minusCount() {
        // If you need to pass parameters, add payload
        return {
          type: types.MINUS_COUNT,
        }
      }
    }
    Copy the code
  • You can also use extracted type constraints in components

    import React, { PropsWithChildren } from 'react';
    import { connect } from 'react-redux';
    
    import action from '@/store/actions/counter';
    import { CombinedState } from '@/typings';
    
    type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>;
    
    // Define an arbitrary one
    type State = { [propsName: string] :any }
    class Counter extends React.Component<Props.State> {
      render(){... }}// Use type constraints
    const mapStateToProps = (state: CombinedState) = > (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter)
    Copy the code

Six, the use ofredux-thunkandaxiosUse of requests

  • 1. General process

    • Page click button, send oneaction
    • inactionIs initiated after receiving the eventajaxrequest
    • inajaxIn the request data, determine whether to send it according to the conditiondispatch(Note that this returns a function that uses immediate execution functions.)
    • inreducerMiddle processing justactiondistributedtypesSave the data tostateIn the
    • Use just in componentsstateThe data in the
  • 2. Create an utils folder in the SRC folder of your project

    // In real business, it may be more concrete, but now it is just a simple encapsulation
    import axios, { AxiosRequestConfig } from 'axios';
    
    axios.defaults.baseURL = 'http://test.dancebox.cn/api/v1';
    axios.defaults.headers.post['Content-Type'] = 'application/json; charset=UTF-8';
    axios.interceptors.request.use((config: AxiosRequestConfig) = > {
      let access_token = sessionStorage.getItem('access_token');
      if (access_token)
        config.headers['Authorization'] = `Bearer ${access_token}`;
      return config;
    }, (error: any) = > Promise.reject(error));
    
    // Response interceptor puts AxiosResponse=> axiosResponse.data
    axios.interceptors.response.use(response= > response.data, error= > Promise.reject(error));
    
    export { axios };
    Copy the code
  • Create an API in the project’s SRC directory to hold the data request methods

    import https from '@/utils/https';
    // Define the request interface
    export const activityList = () = > {
      return https.get('/front/activity');
    }
    Copy the code
  • 4. Define two data types in reducers/index.ts

    .export type StoreDispatch = Dispatch;
    export type StoreGetState = () = > CombinedState;
    
    export default rootReducers;
    Copy the code
  • 5. Request activity data in actions/counter. Ts

    import * as types from '. /.. /action-types';
    import { CounterPayload } from '@/typings';
    import { activityList } from '@/api';
    import { StoreDispatch, StoreGetState } from '.. /reducers';
    
    export default{...// Return data using Ajax requests, because we use redux-thunk, reducer can return a function
      activityList() {
        return function (dispatch: StoreDispatch, getState: StoreGetState) {
          // To use async, use the immediate function
          (async function () {
            const response: { [propsName: string] :any } = await activityList();
            const { code, message, result } = response;
            if (Object.is(code, 0)) {
              dispatch({
                type: types.GET_ACTIVITY_DATA,
                payload: result,
              })
            } else {
              console.log('Failed to get data', message); }}) (); }}}Copy the code
  • 6, add active SRC /typings/counter

    export interface CounterState {
      count: number.activityListData: any[].// Add data to the active list
    }
    Copy the code
  • 7. Handle reducers/ Counter. Ts and save the data of Dispath in actions into state

    import { AnyAction } from 'redux';
    import * as types from '. /.. /action-types';
    
    import { CounterState } from '@/typings';
    
    const initState: CounterState = {
      count: 0.activityListData: [],};export default function (state: CounterState = initState, action: AnyAction) {
      switch (action.type) {
        ...
        case types.GET_ACTIVITY_DATA:
          // Get the requested data and put it in store
          console.log(action.payload);
          return { ...state, activityListData: action.payload.data }
        default:
          returnstate; }}Copy the code
  • 8. Use in components

    import React, { PropsWithChildren } from 'react';
    import { connect } from 'react-redux';
    
    import action from '@/store/actions/counter';
    import { CombinedState } from '@/typings';
    
    type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>;
    
    // Define an arbitrary one
    type State = { [propsName: string] :any }
    class Counter extends React.Component<Props.State> {
      render() {
        console.log(this.props);
        return (
          <>
            <button onClick={this.props.activityList}>Get active data</button>
            <ul>
              {
                this.props.activityListData.map(item => {
                  return (
                    <li key={item.id}>{item.title}</li>)})}</ul>
          </>)}}const mapStateToProps = (state: CombinedState) = > (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter)
    Copy the code

Seven, configuration,antdThe front endUIlibrary

  • 1. Install dependency packages

    npm install antd
    npm install ts-import-plugin
    Copy the code
  • 2. Modify the webpack.config.js configuration

    const tsImportPluginFactory = require('ts-import-plugin');
    
    module.exports = {
      ...
      module: {
        rules: [{test: /\.(j|t)sx? /,
            loader: 'ts-loader'.options: {
              transpileOnly: true.// Only compile without checking
              getCustomTransformers: () = > ({ // Get or define a custom converter
                before: [tsImportPluginFactory({
                  'libraryName': 'antd'.// Which module to load on demand
                  'libraryDirectory': 'es'.// The on-demand module, if implemented on demand, must be ES Modules
                  'style': 'css' // Automatically import its corresponding CSS}})]),compilerOptions: {
                module: 'es2015'}}},]},... }Copy the code
  • 3. Use in components

    import React, { PropsWithChildren } from 'react';
    import { connect } from 'react-redux';
    import { Button } from 'antd';
    
    import action from '@/store/actions/counter';
    import { CombinedState } from '@/typings';
    
    type Props = PropsWithChildren<ReturnType<typeof mapStateToProps> & typeof action>;
    
    // Define an arbitrary one
    type State = { [propsName: string] :any }
    class Counter extends React.Component<Props.State> {
      render() {
        console.log(this.props);
        return (
          <>.<Button type="primary" onClick={this.props.activityList}>Get active data</Button>
            <ul>
              {
                this.props.activityListData.map(item => {
                  return (
                    <li key={item.id}>{item.title}</li>)})}</ul>
          </>)}}const mapStateToProps = (state: CombinedState) = > (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter)
    Copy the code
  • SRC /index.tsx

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    
    import { ConfigProvider } from 'antd'
    import zh_CN from 'antd/lib/locale-provider/zh_CN';
    
    import store from './store';
    import Counter from '@/components/Counter';
    
    // Global style
    import './assets/style/common.less';
    ReactDOM.render(
      <Provider store={store}>
        <ConfigProvider locale={zh_CN}>
          <Counter />
        </ConfigProvider>
      </Provider>.document.getElementById('root'))Copy the code