Vue.js 破浪——使用 Typescript 编写简单的单页面组件

原文 TypeScript-Vue-Starter

译序

  已经很久没有更新博客了,最近在翻译 ASP.Net Core 的文档,又写了一下 JS ,发现写完强类型的 C# 后觉得 IDE 对弱类型的 JS 的自动补全简直天差地别。听说 TypeScript 在 Js 的基础上加入了类型系统,能够让 IDE 实现更好的自动补全和在运行之前处理更多的错误,所以试用了一下 TypeScript。然后联想到我的 Vue 系列很久没有更新了,就直接翻译了一篇基于 TypeScript 的 Vue 指南 。

TypeScrip Vue 指南

  这个快速指南能够指导你在 Vue 项目中使用 TypeScript 。通过这个指南,你能够灵活地将 TypeScript 集成到现有的 Vue 项目中。

创建文件夹目录

先创建一个 package

mkdir typescript-vue-tutorial
cd typescript-vue-tutorial

然后按照下面的目录结构创建相关的文件夹

typescript-vue-tutorial/
├─ dist/
└─ src/
   └─ components/

TypeScript 文件从src文件夹中开始,通过 TypesScript 编译器执行,然后使用 webpack 打包, 最终被打包成 dist 文件夹中的 bundle.js 文件。src/components文件夹用来保存自己编写的组件。

创建 components 文件夹

mkdir src
cd src
mkdir components
cd ..

dist文件夹会由 Webpack 自动创建

初始化 npm 项目

现在将文件夹转换为 npm 包,执行下面指令

npm init

然后根据提示填写相关的信息,也可以直接按回车使用默认的信息。之后可以在 package.json 文件中更改相关的信息。

安装依赖

&nbps; 在这里我们会使用非官方的 Vue 分支用于支持实验性的类型声明特性。这些声明需要用到 Vue 的一个 fork 版本,在不久的将来可能会并入到 Vue 官方版本中(在翻译的时候 Vue 2.5 已经支持这些特性)。

npm install https://github.com/DanielRosenwasser/vue#540a38fb21adb7a7bc394c65e23e6cffb36cd867

接着,确认已经安装了 TypeScript, Webpack 以及其他必要的加载器(loaders)

npm install --save-dev typescript webpack ts-loader css-loader vue-loader vue-template-compiler@2.2.1

Wenpack 是一个打包工具,它能够将你使用的依赖按照配置文件打包成一个单独的 .js 文件。 当你不需要使用 Webpack, Browserify 等打包工具,这些工具会允许我们使用 .vue 文件。关于使用 .vue 文件,我们在之后会涉及到。

现在,已经不需要在根目录下添加 .d.ts 文件,但如果使用的包没有包含 .d.ts 声明文件,需安装相应的 @types/ 包。 了解如何在文档中使用声明文件

添加 TypeScript 配置文件

之后你可能会希望将 编写的 TypeScript 代码和一些必要的声明文件绑定在一起。为了达成这种效果,需要创建一个 tsconfig.json 文件。tsconfig文件中包含了输入文件和编译设定。创建一个文件并命名位 tsconfig.json 并填入以下代码:

{
  "compilerOptions": {
    "outDir": "./built/",
    "sourceMap": true,
    "strict": true,
    "module": "es2015",
    "moduleResolution": "node",
    "target": "es5",
    "experimentalDecorators": true
  },
  "include": ["./src/**/*"]
}

注意, strict 选项设置位 true 。之前,TypeScript 的 noImplicitThis 选项需要打开来使得 Vue 的声明文件生效,而现在有了 strict 选项提供相应的功能。strict 选项还能提供更多的功能(如 noImplicitAnystrictNullChecks)。 现在,强烈建议使用 TypeScript 的 strict 选项来获得更好的体验。

添加 Webpack

  为了打包应用,需要添加 Webpack 的配置文件 webpack.config.js

var path = require("path");

var webpack = require("webpack");

module.exports = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "./dist"),
    publicPath: "/dist/",
    filename: "build.js"
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: "vue-loader",
        options: {
          loaders: {
            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
            // the "scss" and "sass" values for the lang attribute to the right configs here.
            // other preprocessors should work out of the box, no loader config like this necessary.
            scss: "vue-style-loader!css-loader!sass-loader",
            sass: "vue-style-loader!css-loader!sass-loader?indentedSyntax"
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/]
        }
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: "file-loader",
        options: {
          name: "[name].[ext]?[hash]"
        }
      }
    ]
  },
  resolve: {
    extensions: [".ts", ".js", ".vue", ".json"],
    alias: {
      vue$: "vue/dist/vue.esm.js"
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  performance: {
    hints: false
  },
  devtool: "#eval-source-map"
};
if (process.env.NODE_ENV === "production") {
  module.exports.devtool = "#source-map";
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,

      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ]);
}

添加构建脚本

  打开 package.json 文件,在 scripts 下添加 build 属性,通过这部设置就可以使用 Webpack 来构建应用了。之后的 scripts 属性应该如下所示

"scripts": {
    "build": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

在添加了构建入口之后,就能够执行 npm run build 来构建应用,也可以执行 npm run build -- --warch 来实时监听项目变化。

创建一个基本项目

接着创建一个最简单的 Vue 和 TypeScript 项目。 首先,创建 ./src/index.ts 文件

// src/index.ts
import Vue from "vue";
let v = new Vue({
    el: "#app",
    template: `
    <div>
        <div>Hello {{name}}!</div>
        Name: <input v-model="name" type="text">
    </div>`,
    data: {
        name: "World"
    }
});

检查相关的资源有没有被正确链接。然后创建 index.html 文件,并添加以下代码

<!doctype html>
<html>
<head></head>

<body>
    <div id="app"></div>
</body>
<script src="./dist/build.js"></script>

</html>

现在运行 npm run build 命令然后使用浏览器打开 index.html 文件。

现在可以在浏览器上看到 Hello World!,下面还有一个文本框。如果改变文本框中的文字,你会注意到上面的 World 会跟着改变。

恭喜!你已经能够使用 TypeScript 来写 Vue 了(其实和 Js 并没有什么区别啊。)

添加一个组件

正如刚刚看到的一样, Vue 完成简单的任务时使用了很简单的接口。 如果页面只需要在两个元素之间沟通少量的数据的话, Vue 只需要更改少量的代码。

为了完成更复杂的任务, Vue 能够灵活的将应用拆分为组件。组件化主要关注的是如何将用户关注的各个实体拆分。可以通过阅读官方文档来获取更多关于组件的信息。

可以通过下面的代码来声明一个简单的 Vue 组件

// src/components/Hello.ts

import Vue from "vue";

export default Vue.extend({
    template: `
        <div>
            <div>Hello {{name}}{{exclamationMarks}}</div>
            <button @click="decrement">-</button>
            <button @click="increment">+</button>
        </div>
    `,
    props: ['name', 'initialEnthusiasm'],
    data() {
        return {
            enthusiasm: this.initialEnthusiasm,
        }
    },
    methods: {
        increment() { this.enthusiasm++; },
        decrement() {
            if (this.enthusiasm > 1) {
                this.enthusiasm--;
            }
        },
    },
    computed: {
        exclamationMarks(): string {
            return Array(this.enthusiasm + 1).join('!');
        }
    }
});

这个组件有两个按钮和一些文字。在渲染的时候,该组件会获取初始的 name 值和 initialEnthusiasminitialEhtusiasm表示的是要显示的感叹号的数目。当点击 + 按钮的时候,会在文本的后面添加感叹号。同理,当点击 - 按钮的时候会删除一个感叹号直到只剩下一个感叹号。

根实例会按照下面的步骤使用这个组件

// src/index.ts

import Vue from "vue";
import HelloComponent from "./components/Hello";

let v = new Vue({
    el: "#app",
    template: `
    <div>
        Name: <input v-model="name" type="text">
        <hello-component :name="name" :initialEnthusiasm="5" />
    </div>
    `,
    data: { name: "World" },
    components: {
        HelloComponent
    }
});

但是,Vue 单页面组件(SFC)在组件式开发中更受欢迎。接下来会介绍如何编写单页面组件

单页面组件

当使用 Webpack 和 Browserify 的时候, Vue 会使用像 [vue-loader](https://github.com/vuejs/vue-loader)[vueify](https://www.npmjs.com/package/vueify) 这样的插件,有了这些插件就可以在类似于 HTML 格式的文件中编写组件。单文件组件一般都是以 .vue 作为扩展名。

为了在 .vue 文件中使用 TypeScript ,我们需要做一些工作,庆幸的是我们已经完成了一半的工作。例如,在之前解决依赖的时候已经安装了 vue-loader,也在 webpach.config.js 文件中编写了 appendTsSuffixTo: [/\.vue$/] 规则,这项规则的意思是从单文件组件中抽出的脚本代码会使用 TypeScript 处理。

在这里需要额外做的就是告诉 TypeScript .vue 文件在被载入的时候应该怎么做。 为了完成这项任务我们会编写一个 vue-shims.d.ts 文件

// src/vue-shims.d.ts

declare module "*.vue" {
    import Vue from "vue";
    export default Vue;
}

接下来就不需要在代码中载入这个文件,TypeScript 会自动包含这个文件模块,这个文件模块主要功能是在告诉引入 .vue 组件的文件, .vue 组件是使用 Vue 构造函数构造的。

剩下的就是编写业务代码了,文本编辑器能够提供完美的 TypeScript 支持。为了在 .vue 文件中发挥编辑器的作用,这里推荐使用 Visual Studio CodeVetur 插件。

现在就可以编写单文件组件了:

<!-- src/components/Hello.vue -->

<template>
    <div>
        <div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
        <button @click="decrement">-</button>
        <button @click="increment">+</button>
    </div>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
    props: ['name', 'initialEnthusiasm'],
    data() {
        return {
            enthusiasm: this.initialEnthusiasm,
        }
    },
    methods: {
        increment() { this.enthusiasm++; },
        decrement() {
            if (this.enthusiasm > 1) {
                this.enthusiasm--;
            }
        },
    },
    computed: {
        exclamationMarks(): string {
            return Array(this.enthusiasm + 1).join('!');
        }
    }
});
</script>

<style>
.greeting {
    font-size: 20px;
}
</style>

然后在根实例中引入该组件

// src/index.ts

import Vue from "vue";
import HelloComponent from "./components/Hello.vue";

let v = new Vue({
    el: "#app",
    template: `
    <div>
        Name: <input v-model="name" type="text">
        <hello-component :name="name" :initialEnthusiasm="5" />
    </div>
    `,
    data: { name: "World" },
    components: {
        HelloComponent
    }
});

使用单文件组件有以下要点:

  • 使用 <script lang="ts"> 标签从而使用 TypeScript 编写脚本。
  • 需要在 index.ts 中引入 .vue 单文件组件。
  • 可以在 style 标签中编写相关的 CSS 样式,而在 .ts 组件中并不能编写样式。
  • 默认输出一个 Vue.extend 方法调用(而不是使用自动生成的)。如果不使用 Vue.extend, Vetur并不会提示错误,但当构建项目的时候就会报错。(好像不使用也不会报错)

使用装饰器定义组件

  定义组件的另一种方法是使用装饰器。在额外的依赖包(vue-class-component 和 vue-property-decorator)的帮助下,可以使用下面的方法重写组件的脚本

import { Vue, Component, Prop } from "vue-property-decorator";

@Component
export default class HelloDecorator extends Vue {
    @Prop() name: string;
    @Prop() initialEnthusiasm: number;
    enthusiasm = this.initialEnthusiasm;
    increment() {
        this.enthusiasm++;
    }
    decrement() {
        if (this.enthusiasm > 1) {
            this.enthusiasm--;
        }
    }
    get exclamationMarks(): string {
        return Array(this.enthusiasm + 1).join('!');
    }
}

在这里使用了继承 Vue 得类和使用 @Component 装饰器来定义组件而不是使用 Vue.extend@Component 包含在 vue-class-component 包里面但是被 vue-property-decorator 重新输出。

还有可以在属性前面添加 @Prop() 来定义一个 props 属性。 @Prop() 同样来自于 vue-property-deorator 包。

至于一般的实例变量,如例子中的 enthusiasm ,会和模板中的对应属性自动关联,就如同在 data 属性中定义一样。需要注意的是,变量必须赋予非 undefined 的值才能够进行绑定。

同样的,在这里像 increment 这样的方法跟在 methods 属性中编写的方法具有同样的效果,也会自动和模板关联。

最后,像 exclamationMarkscomputed 属性会使用 get 钩子编写。

下一步

你可以在 Githuh 上克隆这个项目并尝试运行。

一旦你觉得你可以自由的在 Vue 中使用 TypeScript ,你就可以尝试使用 TypeScript 和 Vue 实现一个待办事项应用。该应用实例使用 vue-router 来管理页面路由,所以你的应用页面会根据不同的 URL 显示不同的内容。

如果想要使用 Redux 风格的状态管理,可以了解 Vuex

Logo

前往低代码交流专区

更多推荐