javascript 遷移 typescript 實踐

只是抱着嘗試的心態對項目進行了遷移,體驗了一番typeScript的強大,固然,習慣了JavaScript的靈活,弱類型,剛用上typeScript時會很不適應,猶如懶散慣了的人被忽然箍上各類枷鎖,約束。可是,從長遠來看,尤爲是多人協做的項目,仍是頗有必要的。html

typescript的優勢

  • 靜態代碼檢查

能夠規避一些容易被忽視,隱晦的邏輯或語法錯誤,幫助咱們寫更加健壯,安全的代碼,以下所示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

clipboard.png

這種狀況也適用於引用非定義變量等錯誤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

  1. 項目目錄介紹

--

clipboard.png

如上圖所示,項目中全部源碼都放在src目錄中,src/client爲客戶端的源碼,src/server爲服務器端的代碼,dist目錄是編譯後的目錄

2. typescript In node

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 遇到的問題

  • 動態地爲global添加屬性

因爲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

  1. 安裝 jest

npm i -D jest @types/jest
npm i -D ts-jest

  1. 安裝 enzyme

npm i -D enzyme @types/enzyme

  1. 配置 jest.config.js 文件
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')
})
相關文章
相關標籤/搜索