手動使用typescript+webpack搭建react開發環境(一)

1、項目基礎構建

  • 一、建立一個文件,而且初始化javascript

    npm init -y
    tsc --init
    touch .gitignore
  • 二、安裝依賴包css

    # 基礎的包
    npm install react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom 
    # 安裝關於webpack的包
    npm install webpack webpack-cli webpack-dev-server html-webpack-plugin -D
    # 安裝處理ts的
    npm i typescript ts-loader source-map-loader -D
    # 安裝redux相關的
    npm install redux react-redux @types/react-redux redux-thunk  redux-logger @types/redux-logger redux-promise @types/redux-promise
    # 安裝路由與redux鏈接的庫
    npm install connected-react-router
    # 處理樣式的
    npm install style-loader css-loader less-loader less -D
    # 處理前綴的
    npm install autoprefixer postcss-loader -D
    # 處理圖片地址類的
    npm install url-loader file-loader lib-flexible -D
    # px轉換rem(看須要安裝,僅用於手機網站)
    npm install px2rem-loader
    # ajax請求庫
    npm install axios
    # react輪播圖組件庫(看須要安裝)
    npm install react-swipe @types/react-swipe
    # react動畫庫(看須要安裝)
    npm install react-transition-group @types/react-transition-group
  • 三、修改tsconfig.json文件html

    {
      "compilerOptions": {
        "outDir": "./dist", /*指定輸出的目錄*/
        "sourceMap": true, /*把ts文件編譯成js文件的時候,同時生成對應的sourceMap文件*/
        "noImplicitAny": true, /*若是爲true的話,ts編譯器沒法推斷出類型的時候,依然會編譯成js文件,可是會*/
        "module": "commonjs", /*規範*/
        "target": "es5", /*轉換爲es5*/
        "jsx": "react",
        "esModuleInterop": true,
        "baseUrl": ".", // 查找非相對路徑的模塊的起始位置
        "paths": { // 定義別名
          "@/*": [
            "src/*"
          ]
        }
      },
      "include": [ /*須要編譯的文件*/
        "./src/**/*"
      ]
    }
  • 四、配置webpack文件前端

    項目下建立一個webpack.config.js的文件java

    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: {
        // 定義別名
        alias: {
          '@': path.resolve(__dirname, 'src'),
          '~': path.resolve(__dirname, 'node_modules')
        },
        // 當你加載一個文件的時候,沒有指定擴展名的時候,會自動尋找哪些擴展名
        extensions: ['.ts', '.tsx', '.js', '.json']
      },
      module: {
        rules: [
          {
            test: /\.(j|t)sx?/,
            loader: 'ts-loader',
            options: {
              transpileOnly: true, //只編譯不檢查
              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')]
              // }
              //},
              // 若是是手機端就要配置
              // {
              // loader: 'px2remote-loader',
              // options: {
              // remUnit: 75, // 基礎尺寸
              // remPrecesion: 8 // 精確到多少位
              // }
              // },
              'less-loader',
            ]
          },
          // 處理圖片類
          {
            test: /\.(jpg|png|gif|svg|jpeg)$/,
            use: ['url-loader']
          }
        ]
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
        }),
        // 熱更新
        new webpack.HotModuleReplacementPlugin(),
      ],
      devServer: {
        hot: true,
        contentBase: path.join(__dirname, 'dist'),
        open: false,
        port: 3000,
        historyApiFallback: {
          // browserHistory的時候,刷新會報404. 自動重定向到index.html
          index: './index.html'
        }
      }
    }
  • 五、建立一個src目錄,而且建立兩個文件index.htmlindex.tsx文件node

  • 六、在package.json中配置啓動命令react

    {
      ...
      "scripts": {
        "dev": "webpack-dev-server",
        "build": "webpack"
      },
    }
  • 七、在src/index.tsx的文件中寫上react代碼webpack

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    ReactDOM.render(
      <div>我是使用react建立的</div>,
      document.getElementById('root')
    )
  • 八、在src/index.html中要寫一個基本的容器ios

    <!DOCTYPE html>
    <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>手動搭建react項目</title>
    </head>
    
    <body>
      <div id="root"></div>
    </body>
    
    </html>
  • 九、若是你是手機網站就要加上適配的(看項目需求來寫的)git

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>手動搭建react項目</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>
  • 十、啓動項目

2、react結合redux一塊兒使用

  • 一、在src下建立一個store的文件夾

  • 二、大致的目錄結構

    ➜  store tree            
    .
    ├── action-types.ts # 定義常量
    ├── actions # 一個組件對應一個action
    ├── index.ts
    └── reducers # 一個組件對應一個reducer
        └── index.ts
    
    2 directories, 3 files
  • 三、在store/index.ts文件中

    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);
    // 掛載到window上,方便查看,能夠不寫
    (<any>window).store = store;
    export default store;
  • 四、store/reducers/index.ts文件用來整合各個組件中的reducer

    import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux'
    
    // 各個組件的reducer的狀態類型,能夠單獨到一個文件中
    export interface CombinedState {
    
    }
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      // 各個組件的reducer
    };
    
    const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers);
    export default rootReducers;
  • 五、在src/index.tsx中使用store

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    import store from './store';
    
    ReactDOM.render(
      <Provider store={store}>
        <div>我是使用react建立的</div>
      </Provider>
      ,
      document.getElementById('root')
    )

3、測試store是否配置成功

  • 一、在action-types.ts中定義兩個常量

    export const ADD_COUNT = 'ADD_COUNT';
    export const MINUS_COUNT = 'MINUS_COUNT';
  • 二、建立一個文件src/store.counter.tsreducer文件

    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:
          return state;
      }
    }
  • 三、在src/store/reducers/index.ts中引入組件的reducer

    ...
    import counter, { Counter } from './counter';
    
    // 各個組件的reducer的狀態類型
    export interface CombinedState {
      counter: Counter,
    }
    
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      // 各個組件的reducer
      counter,
    };
    ...
  • 四、定義actions文件

    import * as types from './../action-types';
    
    export default {
      // 增長的方法
      // payload表示觸發該函數傳遞的參數
      addCount(payload: number) {
        console.log('我是傳遞進來的 payload', payload);
        return {
          type: types.ADD_COUNT,
          payload,
        }
      },
      // 減小的方法
      minusCount(payload: number) {
        return {
          type: types.MINUS_COUNT,
          payload,
        }
      }
    }
  • 五、建立一個組件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>;
    // 隨便定義一個,能夠不寫
    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}
            {/* 須要傳遞payload參數就要這樣寫 */}
            <button onClick={() => this.props.addCount(10)}>+</button>
          </>
        )
      }
    }
    
    const mapStateToProps = (state: any) => (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter);
  • 六、在src/index.tsx中使用該組件而且測試是否成功

4、瀏覽器配置Redux DevTools觀察狀態的變化

  • 一、谷歌瀏覽器安裝插件Redux DevTools(不能上應用市場的要本身想辦法)

  • 二、redux-devtools-extension插件地址

  • 三、在src/store/index.ts中使用

    • 直接使用
    ...
    import { composeWithDevTools } from 'redux-devtools-extension';
    // 採用另一種寫法
    const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(promise, thunk, logger)));
    ...
    • 區分開發環境和生產環境的寫法
    // 開發環境使用工具及日誌中間件
    const enhancers = process.env.NODE_ENV === "development" ? composeWithDevTools(
      applyMiddleware(promise, thunk, logger)
    ) : applyMiddleware(promise, thunk);
    const store = createStore(rootReducer, enhancers);
  • 四、運行效果圖
    在這裏插入圖片描述

5、優化代碼,定義store的類型約束

  • 一、在項目建立一個文件夾src/typings

  • 二、建立一個文件typings/counter.ts約束剛剛的counter.tsx組件的數據

    export interface CounterState {
      count: number
    }
  • 三、建立一個文件typings/state.ts的文件

    import { CounterState } from '.';
    
    // 各個組件的reducer的狀態類型
    export interface CombinedState {
      counter: CounterState,
    }
    
    export interface CounterPayload {
      count: number,
    }
  • 四、精簡store/reducers/counter.ts代碼

    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) {
      ...
    }
  • 五、精簡store/reducers/index.ts代碼

    import { ReducersMapObject, Reducer, combineReducers, AnyAction } from 'redux'
    
    import counter from './counter';
    import { CombinedState } from '@/typings';
    
    const reducers: ReducersMapObject<CombinedState, AnyAction> = {
      // 各個組件的reducer
      counter,
    };
    
    const rootReducers: Reducer<CombinedState, any> = combineReducers(reducers);
    export default rootReducers;
  • 六、優化store/actions/counter.ts文件,讓類型約束payload

    import * as types from './../action-types';
    import { CounterPayload } from '@/typings';
    
    export default {
      // 增長的方法
      // payload表示觸發該函數傳遞的參數
      addCount(payload: CounterPayload) {
        console.log('我是傳遞進來的 payload', payload);
        return {
          type: types.ADD_COUNT,
          payload,
        }
      },
      // 減小的方法
      minusCount() {
        // 若是須要傳遞參數的時候就加上payload,不傳遞的時候就不要加
        return {
          type: types.MINUS_COUNT,
        }
      }
    }
  • 七、在組件中也可使用抽取出的類型約束

    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>;
    
    // 隨便定義一個,能夠不寫
    type State = { [propsName: string]: any }
    class Counter extends React.Component<Props, State> {
      render() {
        ...
      }
    }
    
    // 使用類型約束
    const mapStateToProps = (state: CombinedState) => (state.counter)
    export default connect(
      mapStateToProps,
      action,
    )(Counter)

6、使用redux-thunkaxios請求的使用

  • 一、大致的流程

    • 頁面點擊按鈕,發送一個action
    • action中接收到事件後,發起ajax請求
    • ajax請求數據中,根據條件判斷是否派發dispatch(注意這個地方返回一個函數,函數中使用當即執行函數)
    • reducer中處理剛剛action派發出來的types將數據存放到state
    • 在組件中使用剛剛state中的數據
  • 二、先在項目的src目錄下建立一個utils的文件夾,咱們先對axios簡單的封裝下

    // 實際業務中可能更加具體,如今只是簡單的封裝
    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攔截器裏把AxiosResponse=>AxiosResponse.data
    axios.interceptors.response.use(response => response.data, error => Promise.reject(error));
    
    export { axios };
  • 三、在項目的src目錄下建立一個api專門來存放數據請求的方法

    import https from '@/utils/https';
    // 定義請求接口
    export const activityList = () => {
      return https.get('/front/activity');
    }
  • 四、在reducers/index.ts定義兩個數據類型

    ...
    export type StoreDispatch = Dispatch;
    export type StoreGetState = () => CombinedState;
    
    export default rootReducers;
  • 五、在actions/counter.ts文件中請求活動數據

    import * as types from './../action-types';
    import { CounterPayload } from '@/typings';
    import { activityList } from '@/api';
    import { StoreDispatch, StoreGetState } from '../reducers';
    
    export default {
      ...
      // 使用ajax請求返回數據,由於咱們使用redux-thunk,那麼reducer能夠返回一個函數
      activityList() {
        return function (dispatch: StoreDispatch, getState: StoreGetState) {
          // 這裏要使用async就要使用當即執行函數
          (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('獲取數據失敗', message);
            }
          })();
        }
      }
    }
  • 六、在src/typings/counter.ts文件中新增活動的

    export interface CounterState {
      count: number,
      activityListData: any[], // 新增存放活動列表的數據
    }
  • 七、處理reducers/counter.tsactionsdispath的數據存到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:
          // 獲取到請求的數據,放到store中
          console.log(action.payload);
          return { ...state, activityListData: action.payload.data }
        default:
          return state;
      }
    }
  • 八、在組件中使用

    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>;
    
    // 隨便定義一個,能夠不寫
    type State = { [propsName: string]: any }
    class Counter extends React.Component<Props, State> {
      render() {
        console.log(this.props);
        return (
          <>
            <button onClick={this.props.activityList}>獲取活動數據</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)

7、配置antd前端UI

  • 一、安裝依賴包

    npm install antd
    npm install ts-import-plugin
  • 二、修改webpack.config.js的配置

    const tsImportPluginFactory = require('ts-import-plugin');
    
    module.exports = {
      ...
      module: {
        rules: [
          {
            test: /\.(j|t)sx?/,
            loader: 'ts-loader',
            options: {
              transpileOnly: true, //只編譯不檢查
              getCustomTransformers: () => ({ // 獲取或者說定義自定義的轉換器
                before: [tsImportPluginFactory({
                  'libraryName': 'antd', // 對哪一個模塊進行按需加載
                  'libraryDirectory': 'es', // 按需加載的模塊,若是實現按需加載,必須是ES Modules
                  'style': 'css' // 自動引入它對應的CSS
                })]
              }),
              compilerOptions: {
                module: 'es2015'
              }
            }
          },
        ]
      },
      ...
    }
  • 三、在組件中使用

    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>;
    
    // 隨便定義一個,能夠不寫
    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}>獲取活動數據</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)
  • 四、配置antd的中文包(在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';
    
    // 全局樣式
    import './assets/style/common.less';
    ReactDOM.render(
      <Provider store={store}>
        <ConfigProvider locale={zh_CN}>
          <Counter />
        </ConfigProvider>
      </Provider>,
      document.getElementById('root')
    )
相關文章
相關標籤/搜索