只是抱着嘗試的心態對項目進行了遷移,體驗了一番typeScript的強大,固然,習慣了JavaScript的靈活,弱類型,剛用上typeScript時會很不適應,猶如懶散慣了的人被忽然箍上各類枷鎖,約束。可是,從長遠來看,尤爲是多人協做的項目,仍是頗有必要的。html
能夠規避一些容易被忽視,隱晦的邏輯或語法錯誤,幫助咱們寫更加健壯,安全的代碼,以下所示node
function getDefaultValue (key, emphasis) { let ret; if (key === 'name') { ret = 'GuangWong'; } else if(key=== 'gender') { ret = 'Man'; } else if (key === 'age') { ret = 23; } else { throw new Error('Unkown key '); } if (emphasis) { ret = ret.toUpperCase(); } return ret; } getDefaultValue('name'); // GuangWong getDefaultValue('gender', true) // MAN getDefaultValue('age', true)
這是一個簡單的函數,第一個參數 key 用來得到一個默認值。第二參數 emphasis 爲了某些場景下要大寫強調,只須要傳入 true 便可自動將結果轉成大寫。react
可是若是不當心將 age 的值寫成了數字字面量,若是我調用 getDefaultValue('age', true) 就會在運行時報錯。這個有多是業務上線了以後才發生,直接致使業務不可用。webpack
若有一種場景,在代碼重構遷移模塊目錄時,一些模塊依賴引用路徑變動,或者是引用的模塊還沒安裝,不存在時,配合vscode, 及時指出錯誤,不用等跑一遍編譯es6
這種狀況也適用於引用非定義變量等錯誤web
- 加強代碼的可讀性,能夠作到代碼即文檔。typescript
雖然代碼有註釋,可是並非每一個人都有良好的習慣express
react 組件設計 export interface CouponProps { coupons: CouponItemModel[]; } export interface couponState { page: number, size: number } class CouponContainer extends React.Component<CouponProps, couponState> { render() { return ( <div> { this.props.coupons.map((item: CouponItemModel) => item.title) } </div> ) } } 使用 JS 寫的 Component,Props 和 State表現的並不明顯。使用 Typescript 編寫 React 組件,須要爲組件定義好 Props 和 State。而這也被證實是個好的編碼方式。其能夠幫助你構建更健壯的組件,別人經手本身的代碼時能夠很清楚知道一個組件須要傳入哪些參數
- 加強設計npm
實踐是消弭困惑最好的方式,抱着好奇,排斥的心態仍是對對項目進行了遷徙json
--
如上圖所示,項目中全部源碼都放在src目錄中,src/client
爲客戶端的源碼,src/server
爲服務器端的代碼,dist目錄是編譯後的目錄
2.1.準備階段
使用npm安裝:npm install -g typescript,當前項目使用了是v2.8.3
2.2 tsconfig.json
在項目的根目錄下新創建tsconfig.json文件,並編輯相關配置項
{ "compilerOptions": { "module": "commonjs", "target": "es5", "noImplicitAny": true, "sourceMap": true, "lib": ["es6", "dom"], "outDir": "dist", "baseUrl": ".", "jsx": "react", "paths": { "*": [ "node_modules/*", "src/types/*" ] } }, "include": [ "src/**/*" ] }
相關配置解析可參考tsconfig.json
2.3 結合gulp
var gulp = require('gulp'); var pump = require('pump'); var webpack = require('webpack'); var gutil = require('gulp-util'); var webpackDevConfig = require(__dirname + '/webpack.config.dev.js'); var ts = require('gulp-typescript'); var livereload = require('gulp-livereload'); var tsProject = ts.createProject("tsconfig.json"); gulp.task('compile:tsc:server', function () { return gulp.src('src/server/**/*.ts') .pipe(tsProject()) .pipe(gulp.dest('dist/server')); }); gulp.task('compile:tsc:client', function(callback){ webpack(webpackDevConfig, function(err, stats){ if(err) throw new gutil.PluginError("webpack:build-js", err); gutil.log("[webpack:build-js]", stats.toString({ colors: true })); callback(); }); }); //將任務同步執行 var gulpSequence = require('gulp-sequence'); gulp.task('copy:html', function() { return pump([ gulp.src('./src/views/**/*'), gulp.dest('./dist/server/views') ]) }); gulp.task('compile', gulpSequence( 'compile:tsc:server', 'compile:tsc:client', 'copy:html' )) gulp.task('watch', ['compile'], function() { livereload.listen(); gulp.watch(['./src/server/**/*.ts'], ['compile:tsc:server']); gulp.watch(['./src/client/**/*.ts'], ['compile:tsc:client']); gulp.watch(['./src/views/**/*.html'], ['copy:html']); })
2.4 測試
在src/server/app.ts
下編寫代碼
/// <reference path="../types/custom.d.ts" /> import * as express from "express"; import * as compression from "compression"; // compresses requests import * as cookieParser from "cookie-parser"; import * as bodyParser from "body-parser"; import * as path from "path"; import * as favicon from "serve-favicon"; import * as fs from "fs"; global.APP_PATH = __dirname; const programConfig = require(path.join(global.APP_PATH + "../../../config/index")); global.config = programConfig.get("config"); const mainRouters = require("./routers/main"); const underscore = require("underscore"); global._ = underscore._; const app = express(); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })); // parse application/json app.use(bodyParser.json()); // protocal app.use(function(req: express.Request, res: express.Response, next: express.NextFunction) { app.locals.protocol = req.protocol; app.locals.host = req.headers.host; next(); }); // view engine setup app.set("views", path.join(__dirname, "./views")); app.set("view engine", "jade"); app.engine("html", require("ejs-mate")); app.use(compression()); app.use(cookieParser()); // resources const cacheOptions = { maxAge : "1d", }; app.use("/test", express.static(path.join(__dirname, "../public"), cacheOptions)); app.use("/", mainRouters); const port = process.env.PORT || programConfig.get("config").port; const server = app.listen(port, function() { console.log("App is runing"); console.log("server is listening on " + port); }); module.exports = app;
2.5 遇到的問題
因爲js靈活的風格,咱們常常動態地爲某一對象添加屬性,可是typeScript是編譯型語言,基本原則是先定義再使用,因此當咱們像下面這麼引用
global.testName = '哈哈';
便會出現這樣的錯誤
類型「Global」上不存在屬性「testName」
解決方法
(1)將global強制轉化爲any類型 (<any>global).testName = '哈哈' (2)擴展原有的對象 global.prototy.testName = '哈哈哈' (3)使用.d.ts文件
declare namespace NodeJS { export interface Global { testName: string; } }
網上不少方法是直接添加一個.d.ts文件便可,可是親測無效,須要在引用文件引入該文件,如本項目在app.ts文件中引入了
/// <reference path="../types/custom.d.ts" />
項目用的測試框架是 jest + enzyme
npm i -D jest @types/jest
npm i -D ts-jest
npm i -D enzyme @types/enzyme
module.exports = { collectCoverage: true, moduleFileExtensions: [ 'ts', 'js', 'tsx' ], transform: { "^.+\\.tsx?$": "ts-jest", }, testMatch: [ '**/test/**/*.test.(ts|js|tsx)' ], testEnvironment: 'node' };
4.編寫測試用例 coupon.test.tsx
import * as React from 'react'; import { shallow, configure } from 'enzyme'; import * as Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter()}) test('Jest-React-TypeScript 嘗試運行', () => { const renderer = shallow(<div>hello world</div>) expect(renderer.text()).toEqual('hello world') })