react + ts 学习历程

react + ts 学习历程

1. 安装初始化一个react + ts 的项目

以前安装的方法:

npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts

现在安装的方法:

npx create-react-app my-app --typescript

此时的工程结构应如下所示:

my-app/
├─ .gitignore
├─ node_modules/
├─ public/
├─ src/
│  └─ ...
├─ package.json
├─ tsconfig.json
└─ tslint.json

注意:

  • tsconfig.json包含了工程里TypeScript特定的选项。
  • tslint.json保存了要使用的代码检查器的设置,TSLint
  • package.json包含了依赖,还有一些命令的快捷方式,如测试命令,预览命令和发布应用的命令。
  • public包含了静态资源如HTML页面或图片。除了index.html文件外,其它的文件都可以删除。
  • src包含了TypeScript和CSS源码。index.tsx是强制使用的入口文件。

待拓展

2. 增加ignore文件

   # See https://help.github.com/ignore-files/ for more about ignoring files.
   
   # dependencies
   /node_modules
   
   # testing
   /coverage
   
   # production
   /build
   
   # misc
   .DS_Store
   .env.local
   .env.development.local
   .env.test.local
   .env.production.local
   .idea
   
   npm-debug.log*
   yarn-debug.log*
   yarn-error.log*
   

3. 增加目录结构

增加src目录下的结构

   src/
   ├─ actions/
   │  └─index.ts
   ├─ assets/
   │  └─logo.svg
   ├─ components/
   │  └─ Menu.tsx
   ├─ containers/
   │  └─ Menu.ts
   ├─ interface/
   │  └─index.ts
   ├─ layouts/
   		├─ PageLayout/
   		│  └─index.tsx
   		├─ BasicLayout.tsx
   ├─ pages/
   │  └─ index.tsx
   ├─ reducers/
   │  └─ index.ts
   ├─ sagas/
   │  └─index.ts
   ├─ services/
   │  └─api.ts
   ├─ utils/
   │  └─Logger.ts
   ├─ webpack/
   └─ App.tsx

4. 增加Logger

  export interface Logger {
      trace(message?: any, ...param: any[]): void;
  
      debug(message?: any, ...param: any[]): void;
  
      info(message?: any, ...param: any[]): void;
  
      warn(message?: any, ...param: any[]): void;
  
      error(message?: any, ...param: any[]): void;
  }
  
  class LoggerClass implements Logger {
  
      protected moduleName: string = '';
      protected TIMESTAMP: string = 'YYYY-MM-DD HH:mm:ss.sss';
  
      constructor(moduleName: string) {
          this.moduleName = moduleName;
      }
  
      public trace(message?: any, ...param: any[]): void {
          // console.trace(`[${moment().format(this.TIMESTAMP)}] [${this.moduleName}]`, message, ...param);
          console.trace(`[${this.moduleName}]`, message, ...param);
  
      }
  
      public debug(message?: any, ...param: any[]): void {
          // console.debug(`[${moment().format(this.TIMESTAMP)}] [${this.moduleName}]`, message, ...param);
          console.trace(`[${this.moduleName}]`, message, ...param);
  
      }
  
      public info(message?: any, ...param: any[]): void {
          // console.info(`[${moment().format(this.TIMESTAMP)}] [${this.moduleName}]`, message, ...param);
          console.trace(`[${this.moduleName}]`, message, ...param);
  
      }
  
      public warn(message?: any, ...param: any[]): void {
          // console.warn(`[${moment().format(this.TIMESTAMP)}] [${this.moduleName}]`, message, ...param);
          console.trace(`[${this.moduleName}]`, message, ...param);
  
      }
  
      public error(message?: any, ...param: any[]): void {
          // console.error(`[${moment().format(this.TIMESTAMP)}] [${this.moduleName}]`, message, ...param);
          console.trace(`[${this.moduleName}]`, message, ...param);
  
      }
  }
  
  export function createLogger(moduleName: string): Logger {
      return new LoggerClass(moduleName);
  }
  
  export function LoggerDecorator(target: string): any;
  export function LoggerDecorator(target: object, propertyKey: string): void;
  export function LoggerDecorator(target: any, propertyKey?: any): any {
      if (typeof target === "object") {
          target[propertyKey] = createLogger(target.constructor.name);
      } else {
          return function (tar: any, pKey: string): any {
              tar[pKey] = createLogger(target);
          };
      }
  }

5. 添加路由

使用react-router-dom

 npm install react-router-dom --save
 npm install @types/react-router-dom --save-dev

这里为了满足需求,增加一个history插件

npm install --save history

6. 添加老版antd (3.16.3)

npm install antd --save

按需引入组件

npm install react-app-rewired --save-dev

修改package.json的配置

/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start --scripts-version react-scripts-ts",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build --scripts-version react-scripts-ts",
-   "test": "react-scripts test --env=jsdom",
+   "test": "react-app-rewired test --env=jsdom --scripts-version react-scripts-ts",
}

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

module.exports = function override(config, env) {
  // do stuff with the webpack config...
  return config;
};

使用 ts-import-plugin

ts-import-plugin 是一个用于按需加载组件代码和样式的 TypeScript 插件(原理),现在我们尝试安装它并修改 config-overrides.js 文件。

  $npm install ts-import-plugin --save-dev
  /* config-overrides.js */
  const tsImportPluginFactory = require('ts-import-plugin')
  const { getLoader } = require("react-app-rewired");

  module.exports = function override(config, env) {
    const tsLoader = getLoader(
      config.module.rules,
      rule =>
        rule.loader &&
        typeof rule.loader === 'string' &&
        rule.loader.includes('ts-loader')
    );

    tsLoader.options = {
      getCustomTransformers: () => ({
        before: [ tsImportPluginFactory({
          libraryName: 'antd',
          libraryDirectory: 'es',
          style: 'css',
        }) ]
      })
    };

    return config;
  }

然后移除前面在 src/App.css 里全量添加的 @import '~antd/dist/antd.css'; 样式代码,并且按下面的格式引入模块。

  import * as React from 'react';
  import { Button } from 'antd';
  import './App.css';

  class App extends React.Component {
    render() {
      return (
        <div className="App">
          <Button type="primary">Button</Button>
        </div>
      );
    }
  }

  export default App;

报错:

Cannt find module react-scripts-ts/config/webpack.config

解决:

使用yarn add react-app-rewired@1.6.2npm install react-app-rewired@1.6.2退回到react-app-rewired的1.x版本的最后一个版本

原因:

This has been caused by recent changes to CRA (2.1.2) where they merged the webpack.config.dev.js and webpack.config.prod.js into a single file webpack.config.js. See #343 and #345 for more details on the change.

The adjustment to paths made in react-app-rewired in order to continue to be able to be used with CRA looks for a react-scripts version number greater than or equal to 2.1.2. The react-scripts-tsversion number has been higher than that for the last ~18 months, so react-app-rewired is treating it according to the merged webpack config instead of the split one.

To solve it, you’ll need to install an older version of react-app-rewired that doesn’t have this change. I believe the last version of react-app-rewired that will be compatible with react-scripts-ts is version 1.6.2. The versions for 2.x have the breaking change to support CRA 2.1.2 and beyond that is misfiring on react-scripts-ts.

Use yarn add react-app-rewired@1.6.2 or npm install react-app-rewired@1.6.2 to step back to the last of the 1.x versions of react-app-rewired - it will install the older version and lock the version number to exactly that version so that it doesn’t get upgraded to a newer version accidentally in future.

参考: https://github.com/timarney/react-app-rewired/issues/358

报错:

The key 'libraryDirectory' is not sorted alphabetically

解决:

config-overrides.js 文件里交换下libraryName和libraryDirectory的位置。

参考: https://github.com/ant-design/ant-design/issues/10533

报错:

interface name must not have an "I" prefix

解决:

根据参考网址添加

"interface-name" : [true, "never-prefix"]

对我的项目而言无效,需要修改成

"interface-name": false

即是在tslint.json的rules中添加

{
  "extends": [
    "tslint:recommended",
    "tslint-react",
    "tslint-config-prettier"
  ],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts",
      "coverage/lcov-report/*.js"
    ]
  },
  "rules": {
    // 添加这一句
    "interface-name": false,
    ...
  }
}

参考: https://github.com/palantir/tslint/issues/3265

安装新版 ant3.20.3

npm install antd --save

全局引用

修改 src/App.tsx,引入 antd 的按钮组件。

import React, { Component } from 'react';
import Button from 'antd/es/button';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Button type="primary">Button</Button>
      </div>
    );
  }
}

export default App;

修改 src/App.css 引入 antd 的样式。

@import '~antd/dist/antd.css';

.App {
  text-align: center;
}

...

重新启动 yarn start,现在你应该能看到页面上已经有了 antd 的蓝色按钮组件,接下来就可以继续选用其他组件开发应用了。其他开发流程你可以参考 create-react-app 的官方文档

高级配置,按需引入

此时我们需要对 create-react-app 的默认配置进行自定义,这里我们使用 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案)。

引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 react-app-rewired@2.x 版本的关系,你还需要安装 customize-cra

npm install react-app-rewired customize-cra --save
/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
}

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

module.exports = function override(config, env) {
  // do stuff with the webpack config...
  return config;
};

使用 babel-plugin-import

npm install babel-plugin-import --save

babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件(原理),现在我们尝试安装它并修改 config-overrides.js 文件。

const { override, fixBabelImports } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
);

然后移除前面在 src/App.css 里全量添加的 @import '~antd/dist/antd.css'; 样式代码,并且按下面的格式引入模块。

  // src/App.tsx
  import React, { Component } from 'react';
- import Button from 'antd/es/button';
+ import { Button } from 'antd';
  import './App.css';

  class App extends Component {
    render() {
      return (
        <div className="App">
          <Button type="primary">Button</Button>
        </div>
      );
    }
  }

  export default App;

最后重启 yarn start 访问页面,antd 组件的 js 和 css 代码都会按需加载,你在控制台也不会看到这样的警告信息。关于按需加载的原理和其他方式可以阅读这里

自定义主题 需要安装less

npm install less less-loader --save

按照 配置主题 的要求,自定义主题需要用到 less 变量覆盖功能。我们可以引入 customize-cra 中提供的 less 相关的函数 addLessLoader 来帮助加载 less 样式,同时修改 config-overrides.js 文件如下。

- const { override, fixBabelImports } = require('customize-cra');
+ const { override, fixBabelImports, addLessLoader } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
-   style: 'css',
+   style: true,
  }),
+ addLessLoader({
+   javascriptEnabled: true,
+   modifyVars: { '@primary-color': '#1DA57A' },
+ }),
);

这里利用了 less-loadermodifyVars 来进行主题配置,变量和其他配置方式可以参考 配置主题 文档。

修改后重启 yarn start,如果看到一个绿色的按钮就说明配置成功了。

7. 安装lodash

   npm install lodash --save
   npm install @types/lodash --save-dev

8. 安装redux

   npm install react-redux --save
   npm install redux --save
   npm install @types/react-redux --save-dev

9. 安装redux-actions

GitHub地址:https://github.com/redux-utilities/redux-actions

当在开发大型应用的时候,对于大量的action,reducer需要些大量的swich来对action.type进行判断。

redux-actions可以简化这一烦琐的过程,它可以是actionCreator,也可以用来生成reducer,其作用都是用来简化action、reducer。主要函数有createAction、createActions、handleAction、handleActions、combineActions。

   npm install redux-actions --save
   npm install @types/redux-actions --save-dev

10. 安装humps

用于JavaScript中的字符串和对象键的下划线到驼峰写法的转换器(反之亦然)。

GitHub地址:https://github.com/domchristie/humps

  npm install humps --save
  npm install @types/humps --save-dev

基本用法:

转换字符串

  humps.camelize('hello_world') // 'helloWorld'
  humps.decamelize('fooBar') // 'foo_bar'
  humps.decamelize('fooBarBaz', { separator: '-' }) // 'foo-bar-baz'

转换对象键

  var object = { attr_one: 'foo', attr_two: 'bar' }
  humps.camelizeKeys(object); // { attrOne: 'foo', attrTwo: 'bar' }

对象数组也被转换

  var array = [{ attr_one: 'foo' }, { attr_one: 'bar' }]
  humps.camelizeKeys(array); // [{ attrOne: 'foo' }, { attrOne: 'bar' }]

11. 增加 redux-devtools-extension

Redux DevTools的扩展,更好看的页面

```bash
npm install --save-dev redux-devtools-extension
```

12. 增加action和reducers

13. 增加less

```bash
npm install --save-dev less less-loader
```

14. 增加sass

```bash
$ npm install node-sass --save
```

15. history

history是一个JavaScript库,可以让您轻松地在JavaScript运行的任何地方管理会话历史。history抽象了不同环境中的差异,并提供了一个最小的API,允许您管理历史堆栈、导航、确认导航和会话之间的持久状态。

```bash
npm install --save history
```

16. 增加ts配置文件

tsconfig.json

```json
// https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Compiler%20Options.html
{
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "build/dist",
    "module": "esnext",
    "target": "es5",
    "lib": ["es6", "dom"],
    "sourceMap": true,
    "allowJs": true,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDir": "src",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": false,
    "importHelpers": true,
    "strictNullChecks": true,
    "allowSyntheticDefaultImports":true,
    "suppressImplicitAnyIndexErrors": true,
    "experimentalDecorators": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "build",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest"
  ]
}
```