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;
  }
}
复制代码
  • 以上则是全文的介绍内容,相关配置均通过本人呕心沥血的亲身实践,若有问题欢迎留言。
相关文章
相关标签/搜索