react + typescript 項目的定製化過程

前言

  • 若是要使用 react 的話,對新手來講,首選腳手架大概就是使用由 facebook 官方出的腳手架 create-react-app 了(傳送門 👉create-react-app中文文檔)。
  • create-react-appwebpack 的配置,lint 的配置,babel 的配置等封裝成 react-scripts,這種作法保證了底層依賴版本升級和遷移的時候,能夠平滑遷移,不會影響到業務項目。
  • create-react-app 支持開發者對項目進行個性化配置(經過配合 react-app-rewired 使用或yarn eject 暴露 相關配置後進行修改)。
  • 下文介紹了本人在進行業務代碼開發前一般對項目進行的一些特殊配置,有利於後期的工程開發。

初始化項目

  • 在使用腳手架以前,須要使用 npm 命令全局安裝腳手架:
npm install -g create-react-app
複製代碼
  • 安裝完成後,便可經過腳手架搭建項目:
create-react-app my-app
複製代碼
  • TypeScript 是 JavaScript 的類型超集,可編譯爲純 JavaScript 。經過運行下面的命令可使用 TypeScript啓動新的 create-react-app 項目:
create-react-app my-app --typescript
複製代碼
  • 特別說明:下文介紹的項目配置均是針對 react + typescript 項目所進行的配置。

react-scripts

  • 本節主要是介紹react-scripts一些相關內容,若是隻對配置感興趣的同窗能夠跳過本節。css

  • 前面介紹到,create-react-appwebpack 上封裝了一層 react-scripts,一方面是可使得不習慣 eslintbabelwebpack 的新手只需關注於組件的編寫,另外一方面是能夠不斷的更新和改進默認選項,而不會影響到業務代碼。html

  • 可見,react-scripts 的做用就是經過將一些底層配置封裝起來,從而向上屏蔽了衆多細節,使得業務開發者只需關注業務代碼的開發。node

  • 去到項目 node_modules 目錄下,能夠看到 create-react-app + typescript 裏的react-scripts的目錄結構以下:react

    react-scripts目錄結構
    • 其中,scripts 文件夾裏面包含了項目的開發腳本和構建腳本,對應的 webpack 配置則放在在 config 文件夾裏面。
  • 若是要修改這些配置有三種辦法:webpack

    (1)經過 react-app-rewired 覆蓋默認的 webpack 配置。git

    (2)fork 對應的 react-scripts包, 本身維護這個依賴包。github

    (3)直接 eject 出整個配置在業務項目裏維護。該操做的缺點是不可逆,一旦配置文件暴露後就不可再隱藏。web

  • 因爲本人技術尚淺,本人採用第三種方案。typescript

  • 首先,進入項目目錄:npm

cd my-app
複製代碼
  • 暴露react-scripts包:
yarn eject

yarn run v1.17.3
$ react-scripts eject
NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: https://reactjs.org/blog/2018/10/01/create-react-app-v2.html

? Are you sure you want to eject? This action is permanent. (y/N)
# 輸入 y 便可
複製代碼
  • 通常是使用腳手架搭建好項目後就使用以上命令暴露react-scripts包。而若是先安裝了其餘依賴或改動項目其餘內容以後,再使用 yarn eject 命令時就會報錯
This git repository has untracked files or uncommitted changes:

Remove untracked files, stash or commit any changes, and try again.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! XXX@0.1.0 eject: `react-scripts eject`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the XXX@0.1.0 eject script.
npm ERR! This is probably not a problem with npm. There is likely additional log
ging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\Administrator\AppData\Roaming\npm-cache\_logs\2019-8-1T0
3_18_15_677Z-debug.log
複製代碼
  • 不要慌,解決方案是依次執行如下命令:
git add .
git commit -am "init"
yarn eject
複製代碼
  • 成功 eject 出配置後,能夠發現項目目錄的變化以下:

    項目目錄的變化
  • 若是須要定製化項目,通常就是在config目錄下對默認的 webpack 進行修改。

完善定製化項目

  • 下面將分別介紹如何在項目中引入 less、添加 tslintstylelint、引入 react-router、封裝 fetch 請求、引入 react-loadable 和按需加載 antd

引入less

  • 安裝 lessless-loader
yarn add less less-loader –dev
複製代碼
  • 修改 webpack 配置,即在 config/webpack.config.js 文件中新增 less 配置變量:
const lessRegex = /\.less$/;  // 新增less配置
const lessModuleRegex = /\.module\.less$/; // 新增less配置
複製代碼
  • 同時,在 config/webpack.config.js 文件中的 module 裏面增長 rule 規則:
module: {
      strictExportPresence: true,
      rules: [
        /* 省略代碼 */
        {
          oneOf: [
            /* 省略代碼 */
            /* 下面是原有代碼塊 */
            {
              test: cssModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction && shouldUseSourceMap,
                modules: true,
                getLocalIdent: getCSSModuleLocalIdent,
              }),
            },
            /* 上面是原有代碼塊 */
            /* 下面是添加代碼塊 */
            {
              test: lessRegex,
              exclude: lessModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,// 值是1
                sourceMap: isEnvProduction && shouldUseSourceMap
              },
                "less-loader"
              ),
              sideEffects: true
            },
            {
              test: lessModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction && shouldUseSourceMap,
                modules: true, // 增長這個能夠經過模塊方式來訪問less
                getLocalIdent: getCSSModuleLocalIdent
              },
                "less-loader"
              )
            },
            /* 上面是添加代碼塊 */
            /* 下面是原有代碼塊 */
            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 2,
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                },
                'sass-loader'
              ),
              sideEffects: true,
            },
            /* 上面是原有代碼塊 */
          ],
        },
      ],
    },
複製代碼
  • 經過上面的配置能夠實現模塊化的 less(以xx.module.less命名的文件) 而且和全局 less(以xx.less命名的文件)區分開。
  • 傳送門 👉CSS Modules 詳解及 React 中實踐
  • 模塊化引入:
import * as styles from ./index.module.less
複製代碼
  • 重點!若是要在項目中進行模塊化引入 less,還須要在 src/react-app-env.d.ts 文件中進行配置,不然ts會發生報 錯 Cannot find module './index.module.less',配置內容以下:
declare module '*.less' {
  const styles: any;
  export = styles;
}
複製代碼
  • 以上則完成了 lessreact + typescript 項目中的引入。

編輯器配置

  • 在平常開發中,常常會切換不一樣編輯器,總要設置一遍的配置。團隊開發,每一個人使用不一樣的編輯器和具備不一樣的配置風格。經過在項目根目錄中添加.editorconfig文件並配置必定規則,就能夠設置不一樣編輯器保持一致代碼規範。
  • 下面給出個人配置:
# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab
複製代碼

項目中添加tslint 和 stylelint

  • tslint:相似於 eslint,實際上就是約束咱們的邏輯代碼風格,可經過在項目根目錄添加.tslint.json文件進行相關的配置。通常能夠直接引用諸如 "extends": ["tslint-react"], 若是有特殊規則也能夠本身加。內容示例:
{
  "extends": ["tslint-react"],
  "rules": {
    /* 本身添加的特殊規則 */
  }
}
複製代碼
  • stylelint:能夠約束咱們的樣式的樣式代碼風格,可經過在項目根目錄添加.stylelintrc文件進行相關的配置。通常能夠直接引用諸如 "extends": ["stylelint-config-standard"], 若是有特殊規則也能夠本身加。內容示例:
{
  "extends": "stylelint-config-standard",
  "rules": {
    /* 本身添加的特殊規則 */
  }
}
複製代碼
  • 以上則完成了項目中 tslintstylelint 的添加。

引入react-router

  • 有的人會想說,不就是yarn add react-router嗎,還須要教?其實否則,看👇面:
  • React Router 如今已經被劃分紅了三個包:react-routerreact-router-domreact-router-native
  • 在開發中不該該直接安裝 react-router,這個包爲 React Router 應用提供了核心的路由組件和函數,另外兩個包提供了特定環境的組件(瀏覽器和 react-native 對應的平臺),不過他們也是將 react-router 導出的模塊再次導出。
  • 咱們應該選擇這兩個中適應開發環境的包,因爲本人須要構建一個網站(在瀏覽器中運行),因此我安裝的是 react-router-dom。同時,因爲項目中我還使用了typescript,因此還要安裝@types/react-router-dom。安裝命令:
yarn add react-router-dom
yarn add @types/react-router-dom --dev
複製代碼
  • 以上則完成了項目中 react-router 的引入。

封裝 fetch 請求

  • 若是隻是簡單的請求,不必引入 aixos,經過將fetch請求的相關代碼封裝在request.js/request.ts文件中,在使用的時候引入相關請求方法便可,好處有幾點:

    • 請求的地方代碼更少。

    • 公共的錯誤統一在一個地方添加便可。

    • 請求定製的錯誤仍是請求本身也能夠處理。

    • 擴展性好,添加功能只須要改一個地方。

  • 下面給出我在項目中封裝的 request.ts 文件具體內容:

// path:src/utils/request.ts
const request = (url: string, config: any) => {
  return fetch(url, config)
    .then((res: any) => {
      if (!res.ok) {
        // 服務器異常返回
        throw Error('接口請求異常');
      }
      return res.json();
    })
    .catch((error: any) => {
      return Promise.reject(error);
    });
};

// GET請求
export const get = (url: string) => {
  return request(url, { method: 'GET' });
};

// POST請求
export const post = (url: string, data: any) => {
  return request(url, {
    body: JSON.stringify(data),
    headers: {
      'content-type': 'application/json',
    },
    method: 'POST',
  });
};
複製代碼
  • 根據功能創建不一樣的請求模塊,如列表模塊:
// path:src/services/api/list.ts

import * as Fetch from '../../utils/request';

export async function getListData () {
  return Fetch.get('URL1');
}

export async function getListItemDetail (id: number) {
  return Fetch.get(
    `URL2/${id}`,
  ); 
}
複製代碼
  • 暴露 api:
// path:src/services/api.ts

export * from './api/list';
複製代碼
  • 組件中使用:
// path:src/components/xxx.tsx

import React from 'react';
import * as api from '../../services/api';

class HomePage extends React.Component<any> {
  /* 省略代碼 */ 

  async loadListData () {
    try {
      const res = await api.getListData();
      this.setState({
        listData: res.data.list,
      });
    } catch (error) {
      // do something
    }
  }
  
  /* 省略代碼 */ 
}

export default HomePage;

複製代碼
  • 以上則成功完成 fetch 請求的封裝。

引入 react-loadable

  • 在使用React.js單頁應用程序時,應用程序有增加的趨勢。應用程序(或路徑)的一部分可能會導入大量首次加載時沒必要要的組件。這會增長咱們應用的初始加載時間
  • 當咱們使用yarn build 打包項目時, create-react-app 將生成一個大文件,它包含咱們的應用程序所需的全部JavaScript。可是,若是用戶只是加載登陸頁面進行登陸;咱們用它加載應用程序的其他部分是沒有意義的。
  • 爲了解決這個問題, create-react-app 有一個很是簡單的內置方法來分割咱們的代碼,這個功能被稱爲代碼分割(Code Splitting)。
  • 項目設置支持經過 動態import() 進行代碼拆分。咱們可以使用一個叫react-loadable的第三方庫,考慮了組件加載失敗、加載中等多種狀況。
  • 首先,安裝react-loadable,同時,因爲項目中我還使用了typescript,因此還要安裝@types/react-loadable。安裝命令:
yarn add react-loadable
yarn add @types/react-loadable --dev
複製代碼
  • 爲了讓入口文件看起來更加簡潔,我將把路由配置分離出來放在routes.tsx文件中,在入口路由文件 App.tsx 中只須要將 routeData 引入使用便可:
// path:src/App.tsx

import { createHashHistory } from 'history';
import React from 'react';
import { Router } from 'react-router';
import routeData from './common/route';

const history = createHashHistory();

const App: React.FC = () => {
  return (
    <Router history={history}>
      <Switch>
        {routeData.map(({ path, component, exact }: IRouterItem) => (
          <Route key={path} path={path} component={component} exact={exact} />
        ))}
        <Route component={NotFound} />
      </Switch>
    </Router>
  );
};

export default App;

複製代碼
  • routes.tsx文件的內容大概以下:
// path:src/common/route.tsx

import * as React from 'react';
import Loadable from 'react-loadable';
import Loading from '../components/Loading';

const routeConfig: any = [
  {
    path: '/',
    component: asyncLoad(() => import('../views/HomePage')),
  },
  {
    path: '/detail/:id',
    component: asyncLoad(() => import('../views/DetailPage')),
  },
  /**
   * Exception 頁面
   */
  {
    path: '/exception/404',
    component: asyncLoad(() => import('../views/Exception')),
  },
];

function generateRouteConfig (route: IRouteConfig[]) {
  return route.map(item => {
    return {
      key: item.path,
      exact: typeof item.exact === 'undefined' ? true : item.exact,
      ...item,
      component: item.component,
    };
  });
}

function asyncLoad (loader: () => Promise<any>) {
  return Loadable({
    loader,
    loading: props => {
      if (props.pastDelay) {
        return <Loading />;
      } else {
        return null;
      }
    },
    delay: 500,
  });
}

export default generateRouteConfig(routeConfig);
複製代碼
  • 經過封裝動態加載路由的asyncLoad函數,能夠實現只有在切換到對應路由的時候才渲染相關組件。

按需加載 antd

  • antd:是螞蟻金服推出的一個很優秀的 react UI 庫,其中包含了不少咱們常用的組件。
  • 當咱們沒有進行任何配置直接在這個項目中使用antd庫時,會在控制檯看到以下提示:
    控制檯提示
  • antd庫大小大概有80M,全量引入該庫必然會影響咱們應用的網絡性能,按需引入顯得尤其重要。
  • 官方文檔(傳送門 👉antd 文檔)提供了兩種方式來實現antd的按需加載:
  • 本文采用 babel-plugin-import 來進行按需加載。下面介紹具體步驟。
  • 安裝 antd
yarn add antd
複製代碼
  • 安裝 babel-plugin-import
yarn add babel-plugin-import --dev
複製代碼
  • 在 config/webpack.config.js 文件中的 module 裏面增長 rule 規則:
module: {
      strictExportPresence: true,
      rules: [
        /* 省略代碼 */
        {
          oneOf: [
            /* 省略代碼 */
            /* 下面是原有代碼塊 */
            {
              test: /\.(js|mjs|jsx|ts|tsx)$/,
              include: paths.appSrc,
              loader: require.resolve('babel-loader'),
              options: {
                customize: require.resolve(
                  'babel-preset-react-app/webpack-overrides'
                ),
                plugins: [
                  [
                    require.resolve('babel-plugin-named-asset-import'),
                    {
                      loaderMap: {
                        svg: {
                          ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
                        },
                      },
                    },
                  ],
            /* 上面是原有代碼塊 */
            
                  /* 下面是添加代碼塊 */
                  [
                    'import',{  // 導入一個插件
                      libraryName: 'antd',   // 暴露的庫名
                      style: 'css' // 直接將antd樣式文件動態編譯成行內樣式插入,就不須要每次都導入
                    }
                  ],
                ],
                /* 上面是添加代碼塊 */
                
                /* 下面是原有代碼塊 */
                cacheDirectory: true,
                cacheCompression: isEnvProduction,
                compact: isEnvProduction,
                /* 上面是原有代碼塊 */
              },
            },
          ],
        },
      ],
    },
複製代碼
  • 所以就能夠實現antd組件的按需加載且無需每次都導入相關樣式文件:
import { Button } from 'antd';
複製代碼
  • 注意:因爲項目中實現了模塊化的 less,若是要在模塊內修改 antd 組件的樣式,須要使用:global,如:
:global {
  .ant-divider {
    margin: 0 0;
  }
}
複製代碼
  • 以上則是全文的介紹內容,相關配置均通過本人嘔心瀝血的親身實踐,若有問題歡迎留言。
相關文章
相關標籤/搜索