资讯专栏INFORMATION COLUMN

javascript 迁移 typescript 实践

niceforbear / 2889人阅读

摘要:但是,从长远来看,尤其是多人协作的项目,还是很有必要的。第二参数为了某些场景下要大写强调,只需要传入即可自动将结果转成大写。这个有可能是业务上线了之后才发生,直接导致业务不可用。而这也被证明是个好的编码方式。

只是抱着尝试的心态对项目进行了迁移,体验了一番typeScript的强大,当然,习惯了JavaScript的灵活,弱类型,刚用上typeScript时会很不适应,犹如懒散惯了的人被突然箍上各种枷锁,约束。但是,从长远来看,尤其是多人协作的项目,还是很有必要的。

typescript的优点

静态代码检查

可以规避一些容易被忽视,隐晦的逻辑或语法错误,帮助我们写更加健壮,安全的代码,如下所示

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 即可自动将结果转成大写。

但是如果不小心将 age 的值写成了数字字面量,如果我调用 getDefaultValue("age", true) 就会在运行时报错。这个有可能是业务上线了之后才发生,直接导致业务不可用。

提高效率,错误在编写代码时报错,而非编译阶段

如有一种场景,在代码重构迁移模块目录时,一些模块依赖引用路径变更,或者是引用的模块还没安装,不存在时,配合vscode, 及时指出错误,不用等跑一遍编译

这种情况也适用于引用非定义变量等错误

- 增强代码的可读性,可以做到代码即文档。

虽然代码有注释,但是并不是每个人都有良好的习惯

react 组件设计

export interface CouponProps { 
  coupons: CouponItemModel[]; 
}

export interface couponState {
    page: number,
    size: number
}

class CouponContainer extends React.Component {

  render() {
    return (
      
{ this.props.coupons.map((item: CouponItemModel) => item.title) }
) } } 使用 JS 写的 Component,Props 和 State表现的并不明显。使用 Typescript 编写 React 组件,需要为组件定义好 Props 和 State。而这也被证明是个好的编码方式。其可以帮助你构建更健壮的组件,别人经手自己的代码时可以很清楚知道一个组件需要传入哪些参数

- 增强设计

相关实践

实践是消弭困惑最好的方式,抱着好奇,排斥的心态还是对对项目进行了迁徙

项目目录介绍

--

如上图所示,项目中所有源码都放在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下编写代码

/// 

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类型

 (global).testName = "哈哈"
    
(2)扩展原有的对象

  global.prototy.testName = "哈哈哈"

(3)使用.d.ts文件
declare namespace NodeJS {
 
  export interface Global {
    testName: string;
  }
}

网上很多方法是直接添加一个.d.ts文件即可,但是亲测无效,需要在引用文件引入该文件,如本项目在app.ts文件中引入了

/// 
集成单元测试

项目用的测试框架是 jest + enzyme

安装 jest

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

安装 enzyme

npm i -D enzyme @types/enzyme

配置 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(
hello world
) expect(renderer.text()).toEqual("hello world") })

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95387.html

相关文章

  • 从 Ionic1 迁移至 Ionic2 基本说明

    摘要:迁移概念是基于之上重写的全新框架。从迁移虽然应用需要对其语法结构进行更新,但是开发人员仍然可以通过和这两篇文章来积极的确保升级工作符合最佳的应用实践。这可以很容易的让一个的控制器迁移为一个的类。 迁移概念 Ionic 2 是基于 Angular 2 之上重写的全新框架。所有你已知的关于的 Angular 的部分仍然存在,但是也有一些作为开发人员仍要了解的新的语法和结构性变化。关于 An...

    shevy 评论0 收藏0
  • 前端每周清单第 11 期:Angular 4.1支持TypeScript 2.3,Vue 2.3优化

    摘要:斯坦福宣布使用作为计算机课程的首选语言近日,某位有年教学经验的斯坦福教授决定放弃,而使用作为计算机入门课程的教学语言。斯坦福官方站点将它们新的课程描述为是最流行的构建交互式的开发语言,本课程会用讲解中的实例。 前端每周清单第 11 期:Angular 4.1支持TypeScript 2.3,Vue 2.3优化服务端渲染,优秀React界面框架合集 为InfoQ中文站特供稿件,首发地址为...

    warkiz 评论0 收藏0
  • 大规模应用TypeScript「2019 JSConf -Brie Bunge」

    摘要:众所周知,在大公司中进行大的改革很难。目前公司有超过名开发人员,其中有个以上是前端。从年起,已经在一些小规模团队中探索使用。在年的前端调查中,静态类型系统呼声最高。在我们的主仓库中,绝大多数的公共依赖都已经由做到了类型声明。 特别说明 这是一个由simviso团队进行的关于Airbnb大规模应用TypeScript分享的翻译文档,分享者是Airbnb的高级前端开发Brie Bunge ...

    qpal 评论0 收藏0
  • TypeScript最佳实践:是否使用noImplicitAny

    摘要:我应该使用编译器标志吗编译器选项所做的,基本上是将从可选类型语言转换为强制类型检验语言。由于在实际情况中显式地声明被认为是不好的实践,所以在开发过程的早期,您就需要分配正确的类型。因此,我的建议是将设置为。 我应该使用noImplicitAny TypeScript编译器标志吗? noImplicitAny编译器选项所做的,基本上是将TypeScript从可选类型语言转换为强制类型检验...

    shiguibiao 评论0 收藏0
  • React项目从JavascriptTypescript迁移经验总结

    摘要:面对越来越火的,我们公司今年也逐渐开始拥抱。综上所述,我个人觉得是要删除相关的东西,降低项目复杂度。但是有一个例外情况。这个配置项有三个值可选择,分别是和。模式会生成,在使用前不需要再进行转换操作了,输出文件的扩展名为。 抛转引用 现在越来越多的项目放弃了javascript,而选择拥抱了typescript,就比如我们熟知的ant-design就是其中之一。面对越来越火的typesc...

    zhisheng 评论0 收藏0

发表评论

0条评论

niceforbear

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<