成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

TypeScript在react項目中的實踐

Hwg / 2967人閱讀

摘要:前段時間有寫過一個在項目中的實踐。在里邊有解釋了為什么要使用,以及在中的一個項目結(jié)構(gòu)是怎樣的。關(guān)于的配置文件,在本項目中存在兩份。一個需要注意的小細(xì)節(jié)因為我們的與實現(xiàn)版本中都用到了。

前段時間有寫過一個TypeScript在node項目中的實踐。
在里邊有解釋了為什么要使用TS,以及在Node中的一個項目結(jié)構(gòu)是怎樣的。
但是那僅僅是一個純接口項目,碰巧趕上近期的另一個項目重構(gòu)也由我來主持,經(jīng)過上次的實踐以后,嘗到了TS所帶來的甜頭,毫不猶豫的選擇用TS+React來重構(gòu)這個項目。
這次的重構(gòu)不僅包括Node的重構(gòu)(之前是Express的項目),同時還包括前端的重構(gòu)(之前是由jQuery驅(qū)動的多頁應(yīng)用)。

項目結(jié)構(gòu)

因為目前項目是沒有做前后分離的打算的(一個內(nèi)部工具平臺類的項目),所以大致結(jié)構(gòu)就是基于上次Node項目的結(jié)構(gòu),在其之上添加了一些FrontEnd的目錄結(jié)構(gòu):

  .
  ├── 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

其中標(biāo)綠(也可能是一個+號顯示)的文件為本次新增的。
其中client-distviews都是通過webpack生成的,實際的源碼文件都在client-src下。_就這個結(jié)構(gòu)拆分前后分離其實沒有什么成本_
在下邊分了大概這樣的一些文件夾:

dir/file desc
index.ejs 項目的入口html文件,采用ejs作為渲染引擎
index.tsx 項目的入口js文件,后綴使用tsx,原因有二:
1. 我們會使用ts進(jìn)行React程序的開發(fā)
2. .tsx文件在vs code上的icon比較好看 :p
tsconfig.json 是用于tsc編譯執(zhí)行的一些配置文件
components 組件存放的目錄
config 各種配置項存放的位置,類似請求接口的host或者各種狀態(tài)的map映射之類的(可以理解為枚舉對象們都在這里)
utils 一些公共函數(shù)存放的位置,各種可復(fù)用的代碼都應(yīng)該放在這里
dist 各種靜態(tài)資源的存放位置,圖片之類文件
webpack 里邊存放了各種環(huán)境的webpack腳本命令以及dll的生成
前后端復(fù)用代碼的一個嘗試

實際上邊還漏掉了一個新增的文件夾,我們在src目錄下新增了一個common目錄,這個目錄是存放一些公共的函數(shù)和公共的config,不同于utils或者config的是,這里的代碼是前后端共享的,所以這里邊的函數(shù)一定要是完全的不包含任何環(huán)境依賴,不包含任何業(yè)務(wù)邏輯的。

類似的數(shù)字千分位,日期格式化,抑或是服務(wù)監(jiān)聽的端口號,這些不包含任何邏輯,也對環(huán)境沒有強(qiáng)依賴的代碼,我們都可以放在這里。
這也是沒有做前后分離帶來的一個小甜頭吧,前后可以共享一部分代碼。

要實現(xiàn)這樣的配置,基于上述項目需要修改如下幾處:

1 src下的utilsconfig部分代碼遷移到common文件夾下,主要是用于區(qū)分是否可前后通用

2 為了將對之前node結(jié)構(gòu)方面的影響降至最低,我們需要在common文件夾下新增一個index.ts索引文件,并在utils/index.ts下引用它,這樣對于node方面使用來講,并不需要關(guān)心這個文件是來自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" // 并不需要關(guān)心是來自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可以找到對應(yīng)的目錄,不然會在編輯器中提示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
環(huán)境的搭建

如果使用vs code進(jìn)行開發(fā),而且使用了ESLint的話,需要修改TS語法支持的后綴,添加typescriptreact的一些處理,這樣才會自動修復(fù)一些ESLint的規(guī)則:

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

因為在前端使用了React,按照目前的主流,webpack肯定是必不可少的。
并沒有選擇成熟的cra(create-react-app)來進(jìn)行環(huán)境搭建,原因有下:

webpack更新到4以后并沒有嘗試過,想自己耍一耍

結(jié)合著TS以及公司內(nèi)部的東西,會有一些自定義配置情況的出現(xiàn),擔(dān)心二次開發(fā)太繁瑣

但是其實也沒有太多的配置,本次重構(gòu)選用的UI框架為Google Material的實現(xiàn):material-ui
而他們采用的是jss 來進(jìn)行樣式的編寫,所以也不會涉及到之前慣用的scss的那一套loader了。

webpack分了大概如下幾個文件:

file desc
common.js 公共的webpack配置,類似env之類的選項
dll.js 用于將一些不會修改的第三方庫進(jìn)行提前打包,加快開發(fā)時編譯效率
base.js 可以理解為是webpack的基礎(chǔ)配置文件,通用的loader以及plugins在這里
pro.js 生產(chǎn)環(huán)境的特殊配置(代碼壓縮、資源上傳)
dev.js 開發(fā)環(huán)境的特殊配置(source-map

dll是一個很早之前的套路了,大概需要修改這么幾處:

創(chuàng)建一個多帶帶的webpack文件,用于生成dll文件

在普通的webpack文件中進(jìn)行引用生成的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`文件的時候通過它來做映射關(guān)系的
      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中存在的那些包了。
有一點要注意的,如果最終需要上傳這些靜態(tài)資源,記得連帶著verdors.dll.js一并上傳

在本地開發(fā)時,vendors文件并不會自動注入到html模版中去,所以我們有用到了另一個插件,add-asset-html-webpack-plugin。
同時在使用中可能還會遇到webpack無限次數(shù)的重新打包,這個需要配置ignore來解決-.-:

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

{
  plugins: [
    // 將`ejs`模版文件放到目標(biāo)文件夾,并注入入口`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相關(guān)的配置

TS的配置分了兩塊,一個是webpack的配置,另一個是tsconfig的配置。

首先是webpack,針對ts、tsx文件我們使用了兩個loader

{
  rules: [
    {
      test: /.tsx?$/,
      use: ["babel-loader", "ts-loader"],
      exclude: /node_modules/,
    }
  ],
  resolve: {
    // 一定不要忘記配置ts tsx后綴
    extensions: [".tsx", ".ts", ".js"],
  }
}

ts-loader用于將TS的一些特性轉(zhuǎn)換為JS兼容的語法,然后執(zhí)行babel進(jìn)行處理react/jsx相關(guān)的代碼,最終生成可執(zhí)行的JS代碼。

然后是tsconfig的配置,ts-loader的執(zhí)行是依托于這里的配置的,大致的配置如下:

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es6",
    "allowSyntheticDefaultImports": true,
    // import的相對起始路徑
    "baseUrl": ".",
    "sourceMap": true,
    // 構(gòu)建輸出目錄,但因為使用了`webpack`,所以這個配置并沒有什么卵用
    "outDir": "../client-dist",
    // 開啟`JSX`模式, 
    // `preserve`的配置讓`tsc`不會去處理它,而是使用后續(xù)的`babel-loader`進(jìn)行處理
    "jsx": "preserve", 
    "strict": true,
    "moduleResolution": "node",
    // 開啟裝飾器的使用
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    // `vs code`所需要的,在開發(fā)時找到對應(yīng)的路徑,真實的引用是在`webpack`中配置的`alias`
    "paths": {
      "@Common": [
        "../src/common"
      ],
      "@Common/*": [
        "../src/common/*"
      ]
    }
  },
  "exclude": [
    "node_modules"
  ]
}
ESLint的配置

最近這段時間,我們團(tuán)隊基于airbnbESLint規(guī)則進(jìn)行了一些自定義,創(chuàng)建了自家的eslint-config-blued
同時還存在了react和typescript的兩個衍生版本。

關(guān)于ESLint的配置文件.eslintrc,在本項目中存在兩份。一個是根目錄的blued-typescript,另一個是client-src下的blued-react + blued-typescript。
因為根目錄的更多用于node項目,所以沒必要把react什么的依賴也裝進(jìn)來。

# .eslintrc
extends: blued-typescript

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

一個需要注意的小細(xì)節(jié)
因為我們的reacttypescript實現(xiàn)版本中都用到了parser
react使用的是babel-eslint,typescript使用的是typescript-eslint-parser。
但是parser只能有一個,從option的命名中就可以看出extends、plugins、rules,到了parser就沒有復(fù)數(shù)了。
所以這兩個插件在extends中的順序就變得很關(guān)鍵,babel現(xiàn)在并不能理解TS的語法,但好像babel開發(fā)者有支持TS的意愿。
但就目前來說,一定要保證react在前,typescript在后,這樣parser才會使用typescript-eslint-parser來進(jìn)行覆蓋。

node層的修改

除了上邊提到的兩端公用代碼以外,還需要添加一個controller用于吐頁面,因為使用的是routing-controllers這個庫,渲染一個靜態(tài)頁面被封裝的非常棒,僅僅需要修改兩個頁面,一個用于設(shè)置render模版的根目錄,另一個用來設(shè)置要吐出來的模版名稱:

// 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",
}))

如果是多個頁面,那就創(chuàng)建多個用來Renderts文件就好了

深坑,注意

目前的routing-controller對于Koa的支持還不是很好,(原作者對Koa并不是很了解,導(dǎo)致Render對應(yīng)的接口被請求一次以后,后續(xù)所有的其他的接口都會直接返回該模版文件,原因是在負(fù)責(zé)模版渲染的URL觸發(fā)時,本應(yīng)返回數(shù)據(jù),但是目前的處理卻是添加了一個中間件到Koa中,所以任何請求都會將該模版文件作為數(shù)據(jù)來返回)所以@Render并不能適用于Koa驅(qū)動。
不過我已經(jīng)提交了PR了,跑通了測試用例,坐等被合并代碼,但是這是一個臨時的修改方案,涉及到這個庫針對外部中間件注冊的順序問題,所以對于app.ts還要有額外的修改才能夠?qū)崿F(xiàn)。

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

// 手動創(chuàng)建koa實例,然后添加`render`的中間件,確保`ctx.render`方法會在請求的頭部就被添加進(jìn)去
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}`],
})

// 后續(xù)的邏輯就都一樣了
export default app

當(dāng)然,這個是新版發(fā)出以后的邏輯了,基于現(xiàn)有的結(jié)構(gòu)也可以繞過去,但是就不能使用@Render裝飾器了,拋開koa-views直接使用內(nèi)部的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() {
    // 直接在接口返回時獲取模版渲染后的數(shù)據(jù)
    return cons.ejs(path.resolve(__dirname, "../../views/index.ejs"), {
      title: "Example For TypeScript React App",
    })
  }
}

目前的示例代碼采用的上邊的方案

小結(jié)

至此,一個完整的TS前后端項目架構(gòu)就已經(jīng)搭建完成了(剩下的任務(wù)就是往骨架里邊填代碼了)。
我已經(jīng)更新了之前的typescript-exmaple 在里邊添加了本次重構(gòu)所使用的一些前端TS+React的示例,還包括針對@Render的一些兼容。

TypeScript是一個很棒的想法,解決了N多javaScript種令人詬病的問題。
使用靜態(tài)語言來進(jìn)行開發(fā)不僅能夠提高開發(fā)的效率,同時還能降低錯誤出現(xiàn)的幾率。
結(jié)合著強(qiáng)大的vs code,Enjoy it.

如果在使用TS的過程中有什么問題、或者有什么更好的想法,歡迎來溝通討論。

One more things

Blued前端/Node團(tuán)隊招人。。初中高都有HC
坐標(biāo)帝都朝陽雙井,有興趣的請聯(lián)系我:
wechat: github_jiasm
mail: [email protected]

歡迎砸簡歷

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97066.html

相關(guān)文章

  • typescript - 一種思維方式

    摘要:怎么影響了我的思考方式對前端開發(fā)者來說,能強(qiáng)化了面向接口編程這一理念。使用的過程就是在加深理解的過程,確實面向接口編程天然和靜態(tài)類型更為親密。 電影《降臨》中有一個觀點,語言會影響人的思維方式,對于前端工程師來說,使用 typescript 開發(fā)無疑就是在嘗試換一種思維方式做事情。 其實直到最近,我才開始系統(tǒng)的學(xué)習(xí) typescript ,前后大概花了一個月左右的時間。在這之前,我也在...

    CKJOKER 評論0 收藏0
  • TypeScript - 一種思維方式

    摘要:怎么影響了我的思考方式對前端開發(fā)者來說,能強(qiáng)化了面向接口編程這一理念。使用的過程就是在加深理解的過程,確實面向接口編程天然和靜態(tài)類型更為親密。摘要: 學(xué)會TS思考方式。 原文:TypeScript - 一種思維方式 作者:zhangwang Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 電影《降臨》中有一個觀點,語言會影響人的思維方式,對于前端工程師來說,使用 typescript 開...

    noONE 評論0 收藏0
  • 前端進(jìn)階資源整理

    摘要:前端進(jìn)階進(jìn)階構(gòu)建項目一配置最佳實踐狀態(tài)管理之痛點分析與改良開發(fā)中所謂狀態(tài)淺析從時間旅行的烏托邦,看狀態(tài)管理的設(shè)計誤區(qū)使用更好地處理數(shù)據(jù)愛彼迎房源詳情頁中的性能優(yōu)化從零開始,在中構(gòu)建時間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項目(一) Webpack 4 配置最佳實踐 react Redux狀態(tài)管理之痛點、分析與...

    BlackMass 評論0 收藏0
  • TypeScriptReact、 Redux和Ant-Design的最佳實踐

    摘要:使用官方的的另外一種版本和一起使用自動配置了一個項目支持。需要的依賴都在文件中。帶靜態(tài)類型檢驗,現(xiàn)在的第三方包基本上源碼都是,方便查看調(diào)試。大型項目首選和結(jié)合,代碼調(diào)試維護(hù)起來極其方便。 showImg(https://segmentfault.com/img/bVbrTKz?w=1400&h=930); 阿特伍德定律,指的是any application that can be wr...

    wangbinke 評論0 收藏0

發(fā)表評論

0條評論

Hwg

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<