TypeScript + VS Code 应用技巧三则

开发 Vue 组件过程中,偶有所得,不敢独享,以飨读者。

高亮显示 HTML

问题的缘起

玩 RN 的朋友都知道,HTML 标签集成到 JSX 文件去了。Vue 也是有类似的模版语言,就是 *.vue。不过貌似解析 *.vue 可不是原生 HTML/JS/CSS 就能搞定的,还需要 npm 等的其他的帮忙。它们最终都会编译到 HTML/JS/CSS,于是我还是想用原生的方法写好组件,——仍然无非就是结构(标签)、样式(CSS/LESS)、行为(JS)代码组织的问题。

老实说,这个问题一直令我很头疼。样式嘛,统一 less 搞定,最终导入编译后的 css 即可——问题不大(此问题我们下文第三点会详细谈谈)。主要就是组件的标签,不知如何“安放”好。

组件是一个类,或者一个对象。这个视角下,组件的标签被归属为字符串类型出现在类的属性上,组件要消耗这个 HTML 字符串。问题就是怎么合理安排这字符串保存在哪里,然后又让组件读取到这个字符串。

首先一个问题,标签是要像 *.jsx/*.vue 那样子集成到 JS 里面,还是强调尽可能分离?为了舒服地维护代码,笔者倾向于集成到 JS/TS 中。实际上两种方法笔者都实践过,结论是:修改起来要打开两处的文件并且定位,不如在一起修改那样子来得轻松。

既然在 JS/TS 代码中集成另外一种语言:HTML,肯定不是天生为其而设的,尴尬在于都是长如水蛇的多行标签,而不是简短的字符串。当然你要天生而设也不是没有,那就是回到 *.jsx/*.vue 那里去了。当下我们讨论的是排除这两种方案的方法。

多行文本的解决

玩过 JS 都知道字符串拼凑就用 +,而且还要转义——老麻烦了。这是最原始的方案,然后“高级”一点的,就是用 JS 换行符 \ 放在每一行最后的位置。这个方法能够只使用一对引号便可,大大减少了使用引号的痛苦,不过还是要转义。
√

后来压缩 JS 时候,居然提示不推荐使用换行符。同时我也感觉此方案不优雅。于是我又想其他的办法,这时我想到 Vue 组件的生命周期有个 beforeCreate 的事件,且允许不先声明 template 属性,能不能 beforeCreate 的时候才加入 template 呢?简单实验过也是可以的。这样的做的目的是分离出 HTML,然后在 beforeCreate 时候注入 HTML。HTML 的位置可以是一个元素,或者单独保存为 *.html 文件,——我选择了后者,让同步的 XHR 读取这份 html 文件。这里肯定要用同步了,不然组件初始化完毕了,远程的 HTML 都未读取完回来。有人担心同步浏览器会不会很卡?一个组件一个 HTML 当然会卡,我们不妨多个组件一个 HTML,然后判断一下是否已经加载过了,避免多次同步的远程请求。

这个方法额外的好处是 HTML 在编辑器的高亮显示问题也一并解决了。缺点是两处打开又要同步定位,比较麻烦的说。

尽管如此,这个方法我用了好久,直到 TS 的出现。要知道,ES6 引入了多行文本特性:

`这是一个
多行
字符串`;

TS 自然也是继承 ES 多行字符串的这个特性。不用那么费事地转义和处理引号"",还有模板字符串……妈妈再也不用担心我用 + 拼凑了:)。

// 如果浏览器支持模板字符串,将会替换字符串内部的变量:
var name = '小明';
var age = 20;
console.log(`你好, ${name}, 你今年${age}岁了!`);

模板字符串特性对于 Vue 组件来说,作用不大。反正我已经很感谢多行文本在定义组件时候带来的方便,语言层面的加强带来的收益很高,成本却不大。

代码颜色高亮

最后一点不能媲美 *.jsx/*.vue 组件的地方就是编辑器高亮颜色显示了。这个我不强求,还是能勉强处理这堆标签的,只是越长的 HTML 越需要颜色高亮,好方便阅读嘛~。

没想到的是,这个“痛点”在强大的 VS Code 下面也有解决方案!是不是突然觉得这个世界很美好呢:)。首先安装插件 lit-html。

在这里插入图片描述

然后在标签字符串前面加入 html,如下图所示。

在这里插入图片描述

即可高亮颜色显示并智能联想提示~

好奇的你可能会问,加入这个 html 会不会影响 JS 的编译。没错,那又是 TS 编译的黑科技:实际 html 会编译为 html("xxxxx")的函数调用。这时我们简单赋值 html 给 String 函数即可。

// VS Code 高亮 HTML 用
var html = String;

String() 作用就是把输入的转换为字符串输出。

TS 编译过程中会自动加入辅助函数:

var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
    if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
    return cooked;
};

笔者表示看不懂,同时觉得也没啥作用,于是去掉了(下一节会讲如何去掉)。

移除冗余函数

TypeScript 编译后的 JavaScript,往往带有相关的辅助函数,用于弥补 JS 的不足。例如继承 extends 特性:

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

TS 编译器的规则是每个 JS 结果都带上这些函数。而是最后打包之后(all.js)就会好多没必要的重复!虽然编译器有个开关关掉加入辅助函数,但是仅限于单个文件的编译(而且单个使用 js 时,加入辅助函数是有必要的),watch 模式下或者通过配置文件关闭的却没有。

对应之策网上有高人分享他的经验,就是字符串替换大法,参见这里。恰好我也是使用 gulp.js 工具,所以这里主要依靠 gulp-replace 插件来进行替换。请看 gulp.js 构建文件 gulpfile.js 替换部分的逻辑。

const replace = require('gulp-replace');

const _EXTENDS_ = `var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();`,
__MMakeTemplateObject__ = `var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
    if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
    return cooked;
};`;

/**
 * 
 * @param {*} arr 
 * @param {*} allJsName 
 * @param {*} isRemoveHelperFn 移除重复的 ts 辅助函数 TypeScript removing extends duplicates
 */
function mod(arr, allJsName, isRemoveHelperFn) {
    let obj = src(arr)
        .pipe(concat(allJsName));// 合并所有js到all.js

    if (isRemoveHelperFn) {
        obj = obj.pipe(replace(_EXTENDS_, "")).pipe(replace(__MMakeTemplateObject__, ""));
    }

    obj = obj.pipe(dest('./dist'));

    obj.pipe(rename({ suffix: '.min' }))            // rename压缩后的文件名
        .pipe(sourcemaps.init())                    // Source Map 
        .pipe(uglify())                             // 压缩 js
        .pipe(sourcemaps.write('../sourcemap/'))    // Source Map 
        .pipe(dest('./dist'));
}

我的方法相比网上的会简单点。

如何引入样式

最早的时候通过 Less.js on Node 服务生成 CSS,后来我嫌太复杂于是采用了纯前端的方案,就是在开发阶段引入 less.js 本体,通过 <link href="xxx.less" /> 导入 *.less 样式。要知道编译 Less 并不会消耗太大的运算力,浏览器也可吃得消。到线上阶段,则不是导入 less 而是导入 CSS 结果。

这是个一个方法,后来改用 VS Code,前端全家桶了,于是 CSS 也顺便轻松搞定,不纠结引入 node 的复杂性。因为之前放弃 node 是因为只用它作 CSS 编译一件事情,小题大作。现在不仅有 JS 还有 CSS,果断切换 VS Code+Node 搞定。

与之前 Node Web 服务即时编译 CSS 不同,虽然也有 Web 服务,但编译过程是分离的,Web 访问不产生 Less.js 编译,而是 gulp.js 独立执行编译。所以开发阶段稍微麻烦一点,每次修改后都要 gulp 一下编译结果。最后 Node Server 访问结果 CSS。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页