TypeScript在react项目中的实践

前段时候有写过一个TypeScript在node项目中的实践
在里边有诠释了为何要运用TS,以及在Node中的一个项目构造是如何的。
然则那仅仅是一个纯接口项目,恰巧遇上近期的另一个项目重构也由我来掌管,经由上次的实践今后,尝到了TS所带来的甜头,坚决果断的挑选用TS+React来重构这个项目。
此次的重构不仅包括Node的重构(之前是Express的项目),同时还包括前端的重构(之前是由jQuery驱动的多页运用)。

项目构造

由于如今项目是没有做前后星散的盘算的(一个内部东西平台类的项目),所以大抵构造就是基于上次Node项目的构造,在其之上增加了一些FrontEnd的目次构造:

  .
  ├── README.md
  ├── copy-static-assets.ts
  ├── nodemon.json
  ├── package.json
+ ├── client-dist
+ │   ├── bundle.js
+ │   ├── bundle.js.map
+ │   ├── logo.png
+ │   └── vendors.dll.js
  ├── dist
  ├── src
  │   ├── config
  │   ├── controllers
  │   ├── entity
  │   ├── models
  │   ├── middleware
  │   ├── public
  │   ├── app.ts
  │   ├── server.ts
  │   ├── types
+ │   ├── common
  │   └── utils
+ ├── client-src
+ │   ├── components
+ │   │   └── Header.tsx
+ │   ├── conf
+ │   │   └── host.ts
+ │   ├── dist
+ │   ├── utils
+ │   ├── index.ejs
+ │   ├── index.tsx
+ │   ├── webpack
+ │   ├── package.json
+ │   └── tsconfig.json
+ ├── views
+ │   └── index.ejs
  ├── tsconfig.json
  └── tslint.json

个中标绿(也多是一个+号显现)的文件为本次新增的。
个中client-distviews都是经由历程webpack天生的,现实的源码文件都在client-src下。_就这个构造拆分前后星散实在没有什么本钱_
在下边分了也许如许的一些文件夹:

dir/filedesc
index.ejs项目的进口html文件,采纳ejs作为衬着引擎
index.tsx项目的进口js文件,后缀运用tsx,缘由有二:<br/>1. 我们会运用ts举行React递次的开辟 <br/>2. .tsx文件在vs code上的icon比较悦目 :p
tsconfig.json是用于tsc编译实行的一些设置文件
components组件寄存的目次
config种种设置项寄存的位置,相似要求接口的host或许种种状况的map映照之类的(可以明白为罗列对象们都在这里)
utils一些大众函数寄存的位置,种种可复用的代码都应该放在这里
dist种种静态资本的寄存位置,图片之类文件
webpack里边寄存了种种环境的webpack剧本敕令以及dll的天生

前后端复用代码的一个尝试

现实上边还漏掉了一个新增的文件夹,我们在src目次下新增了一个common目次,这个目次是寄存一些大众的函数和大众的config,不同于utils或许config的是,这里的代码是前后端同享的,所以这里边的函数肯定假如完全的不包括任何环境依靠,不包括任何营业逻辑的。

相似的数字千分位,日期格式化,抑或是效劳监听的端口号,这些不包括任何逻辑,也对环境没有强依靠的代码,我们都可以放在这里。
这也是没有做前后星散带来的一个小甜头吧,前后可以同享一部份代码。

要完成如许的设置,基于上述项目须要修正以下几处:

  • 1 src下的utilsconfig部份代码迁移到common文件夹下,主假如用于辨别是不是可前后通用
  • 2 为了将对之前node构造方面的影响降至最低,我们须要在common文件夹下新增一个index.ts索引文件,并在utils/index.ts下援用它,如许关于node方面运用来讲,并不须要体贴这个文件是来自utils照样common
// src/common/utils/comma.ts
export default (num: number): string => String(num).replace(/\B(?=(\d{3})+$)/g, ',')

// src/common/utils/index.ts
export { default as comma } from './comma'

// src/utils.index.ts
export * from '../common/utils'

// src/app.ts
import { comma } from './utils' // 并不须要体贴是来自common照样来自utils

console.log(comma(1234567)) // 1,234,567
  • 3 然后是设置webpackalias属性,用于webpack可以准确的找到其途径
// client-src/webpack/base.js
module.exports = {
  resolve: {
    alias: {
       '@Common': path.resolve(__dirname, '../../src/common'),
    }
  }
}
  • 4 同时我们还须要设置tsconfig.json用于vs code可以找到对应的目次,不然会在编辑器中提醒can't find module XXX
// client-src/tsconfig.json
{
  "compilerOptions": {
    "paths": {
      // 用于引入某个`module`
      "@Common/*": [
        "../src/common/*"
      ]
    }
  }
}
  • 5 末了在client-src/utils/index.ts写上相似server端的处置惩罚就可以了
// client-src/utils/index.ts
export * from '@Common/utils'

// client-src/index.tsx
import { comma } from './utils'

console.log(comma(1234567)) // 1,234,567

环境的搭建

假如运用vs code举行开辟,而且运用了ESLint的话,须要修正TS语法支撑的后缀,增加typescriptreact的一些处置惩罚,如许才会自动修复一些ESLint的划定规矩:

"eslint.validate": [
  "javascript",
  "javascriptreact",
  {
    "language": "typescript",
    "autoFix": true
  },
  {
    "language": "typescriptreact",
    "autoFix": true
  }
]

webpack的设置

由于在前端运用了React,根据如今的主流,webpack肯定是必不可少的。
并没有挑选成熟的cra(create-react-app)来举行环境搭建,缘由有下:

  1. webpack更新到4今后并没有尝试过,想本身耍一耍
  2. 结合著TS以及公司内部的东西,会有一些自定义设置状况的涌现,忧郁二次开辟太烦琐

然则实在也没有太多的设置,本次重构选用的UI框架为Google Material的完成:material-ui
而他们采纳的是jss 来举行款式的编写,所以也不会涉及到之前习用的scss的那一套loader了。

webpack分了也许以下几个文件:

filedesc
common.js大众的webpack设置,相似env之类的选项
dll.js用于将一些不会修正的第三方库举行提早打包,加速开辟时编译效力
base.js可以明白为是webpack的基本设置文件,通用的loader以及plugins在这里
pro.js临盆环境的特别设置(代码紧缩、资本上传)
dev.js开辟环境的特别设置(source-map

dll是一个很早之前的套路了,也许须要修正这么几处:

  1. 建立一个零丁的webpack文件,用于天生dll文件
  2. 在一般的webpack文件中举行援用天生的dll文件
// dll.js
{
  entry: {
    // 须要提早打包的库
    vendors: [
      'react',
      'react-dom',
      'react-router-dom',
      'babel-polyfill',
    ],
  },
  output: {
    filename: 'vendors.dll.js',
    path: path.resolve(__dirname, '../../client-dist'),
    // 输出时不要少了这个option
    library: 'vendors_lib',
  },
  plugins: [
    new webpack.DllPlugin({
      context: __dirname,
      // 向外抛出的`vendors.dll.js`代码的详细映照,援用`dll`文件的时刻经由历程它来做映照关联的
      path: path.join(__dirname, '../dist/vendors-manifest.json'),
      name: 'vendors_lib',
    })
  ]
}

// base.js
{
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../dist/vendors-manifest.json'),
    }),
  ]
}

如许在watch文件时,打包就会跳过verdors中存在的那些包了。
有一点要注重的,假如终究须要上传这些静态资本,记得连带着verdors.dll.js一并上传

在当地开辟时,vendors文件并不会自动注入到html模版中去,所以我们有效到了另一个插件,add-asset-html-webpack-plugin
同时在运用中能够还会碰到webpack无穷次数的从新打包,这个须要设置ignore来处理-.-:

// dev.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

{
  plugins: [
    // 将`ejs`模版文件放到目的文件夹,并注入进口`js`文件
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../index.ejs'),
      filename: path.resolve(__dirname, '../../views/index.ejs'),
    }),
    // 将`vendors`文件注入到`ejs`模版中
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, '../../client-dist/vendors.dll.js'),
      includeSourcemap: false,
    }),
    // 疏忽`ejs`和`js`的文件变化,防止`webpack`无穷从新打包的题目
    new webpack.WatchIgnorePlugin([
      /\.ejs$/,
      /\.js$/,
    ]),
  ]
}

TypeScript相干的设置

TS的设置分了两块,一个是webpack的设置,另一个是tsconfig的设置。

首先是webpack,针对tstsx文件我们运用了两个loader

{
  rules: [
    {
      test: /\.tsx?$/,
      use: ['babel-loader', 'ts-loader'],
      exclude: /node_modules/,
    }
  ],
  resolve: {
    // 肯定不要遗忘设置ts tsx后缀
    extensions: ['.tsx', '.ts', '.js'],
  }
}

ts-loader用于将TS的一些特征转换为JS兼容的语法,然后实行babel举行处置惩罚react/jsx相干的代码,终究天生可实行的JS代码。

然后是tsconfig的设置,ts-loader的实行是依托于这里的设置的,大抵的设置以下:

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es6",
    "allowSyntheticDefaultImports": true,
    // import的相对肇端途径
    "baseUrl": ".",
    "sourceMap": true,
    // 构建输出目次,但由于运用了`webpack`,所以这个设置并没有什么卵用
    "outDir": "../client-dist",
    // 开启`JSX`形式, 
    // `preserve`的设置让`tsc`不会去处置惩罚它,而是运用后续的`babel-loader`举行处置惩罚
    "jsx": "preserve", 
    "strict": true,
    "moduleResolution": "node",
    // 开启装潢器的运用
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    // `vs code`所须要的,在开辟时找到对应的途径,实在的援用是在`webpack`中设置的`alias`
    "paths": {
      "@Common": [
        "../src/common"
      ],
      "@Common/*": [
        "../src/common/*"
      ]
    }
  },
  "exclude": [
    "node_modules"
  ]
}

ESLint的设置

近来这段时候,我们团队基于airbnbESLint划定规矩举行了一些自定义,建立了自家的eslint-config-blued
同时还存在了reacttypescript的两个衍生版本。

关于ESLint的设置文件.eslintrc,在本项目中存在两份。一个是根目次的blued-typescript,另一个是client-src下的blued-react + blued-typescript
由于根目次的更多用于node项目,所以没必要把react什么的依靠也装进来。

# .eslintrc
extends: blued-typescript

# client-src/.eslintrc
extends: 
  - blued-react
  - blued-typescript

一个须要注重的小细节
由于我们的reacttypescript完成版本中都用到了parser
react运用的是babel-eslinttypescript运用的是typescript-eslint-parser
然则parser只能有一个,从option的定名中就可以看出extendspluginsrules,到了parser就没有复数了。
所以这两个插件在extends中的递次就变得很症结,babel如今并不能明白TS的语法,但彷佛babel开辟者有支撑TS志愿
但就如今来讲,肯定要保证react在前,typescript在后,如许parser才会运用typescript-eslint-parser来举行掩盖。

node层的修正

除了上边提到的两头公用代码之外,还须要增加一个controller用于吐页面,由于运用的是routing-controllers这个库,衬着一个静态页面被封装的异常棒,仅仅须要修正两个页面,一个用于设置render模版的根目次,另一个用来设置要吐出来的模版称号:

// controller/index.ts
import {
  Get,
  Controller,
  Render,
} from 'routing-controllers'

@Controller('/')
export default class {
  @Get('/')
  @Render('index') // 指定一个模版的名字
  async router() {
    // 衬着页面时的一些变量
    // 相似之前的 ctx.state = XXX
    return {
      title: 'First TypeScript React App',
    }
  }
}

// app.ts
import koaViews from 'koa-views'

// 增加模版地点的目次
// 以及运用的衬着引擎、文件后缀
app.use(koaViews(path.join(__dirname, '../views'), {
  options: {
    ext: 'ejs',
  },
  extension: 'ejs',
}))

假如是多个页面,那就建立多个用来Renderts文件就好了

深坑,注重

如今的routing-controller关于Koa的支撑还不是很好,(原作者对Koa并非很相识,致使Render对应的接口被要求一次今后,后续一切的其他的接口都邑直接返回该模版文件,缘由是在担任模版衬着的URL触发时,本应返回数据,然则如今的处置惩罚倒是增加了一个中间件到Koa中,所以任何要求都邑将该模版文件作为数据来返回)所以@Render并不能适用于Koa驱动。
不过我已提交了PR了,跑通了测试用例,坐等被兼并代码,然则这是一个暂时的修正计划,涉及到这个库针对外部中间件注册的递次题目,所以关于app.ts还要有分外的修正才可以完成。

// app.ts 的修正
import 'reflect-metadata'
import Koa from 'koa'
import koaViews from 'koa-views'
import { useKoaServer } from 'routing-controllers'
import { distPath } from './config'

// 手动建立koa实例,然后增加`render`的中间件,确保`ctx.render`要领会在要求的头部就被增加进去
const koa = new Koa()

koa.use(koaViews(path.join(__dirname, '../views'), {
  options: {
    ext: 'ejs',
  },
  extension: 'ejs',
}))

// 运用`useKoaServer`而不是`createKoaServer`
const app = useKoaServer(koa, {
  controllers: [`${__dirname}/controllers/**/*{.js,.ts}`],
})

// 后续的逻辑就都一样了
export default app

固然,这个是新版发出今后的逻辑了,基于现有的构造也可以绕过去,然则就不能运用@Render装潢器了,抛开koa-views直接运用内部的consolidate

// controller/index.ts
// 这个修正不须要修改`app.ts`,可以直接运用`createKoaServer`
import {
  Get,
  Controller,
} from 'routing-controllers'
import cons from 'consolidate'
import path from 'path'

@Controller()
export default class {
  @Get('/')
  async router() {
    // 直接在接口返回时猎取模版衬着后的数据
    return cons.ejs(path.resolve(__dirname, '../../views/index.ejs'), {
      title: 'Example For TypeScript React App',
    })
  }
}

如今的示例代码采纳的上边的计划

小结

至此,一个完全的TS前后端项目架构就已搭建完成了(剩下的使命就是往骨架里边填代码了)。
我已更新了之前的typescript-exmaple 在里边增加了本次重构所运用的一些前端TS+React的示例,还包括针对@Render的一些兼容。

TypeScript是一个很棒的主意,处理了N多javaScript种使人诟病的题目。
运用静态言语来举行开辟不仅可以进步开辟的效力,同时还能下降毛病涌现的概率。
结合著壮大的vs code,Enjoy it.

假如在运用TS的历程中有什么题目、或许有什么更好的主意,迎接来沟通议论。

One more things

Blued前端/Node团队招人。。初中高都有HC
坐标帝都旭日双井,有兴致的请联络我:
wechat: github_jiasm
mail: jiashunming@blued.com

迎接砸简历

    原文作者:贾顺名
    原文地址: https://segmentfault.com/a/1190000016163937
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞