如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的方法


Posted in Javascript onDecember 12, 2019

忙里偷闲,整理了一下关于如何借助 vue-cli3 搭建 ts + 装饰器 的脚手架,并如何自定义 webpack 配置,优化。

准备工作

  • @vue/cli@4.1.1
  • vue 2.6
  • node v12.13.0

安装 node

  • 安装 node
  • 全局安装 nrm,npm 的镜像源管理工具。
npm i nrm -g // 安装
nrm ls // 查看可用源,及当前源,带*的是当前使用的源
nrm use taobao // 切换源,使用源
nrm add <registry> <url> // 其中reigstry为源名,url为源的路径
nrm del <registry> // 删除对应的源
nrm test npm // 测试源的响应速度

安装 vue-cli3

参考官方文档:https://cli.vuejs.org/zh/guide/

npm i @vue/cli -g // 全局安装

vue --version // 检查是否安装

补充

npm list -g --depth 0 // 查看全局安装的包
npm outdated -g --depth=0 // 查看需要更新的全局包
npm update 包名 -g // 更新全局安装的包

搭建项目

可参考:使用Vue-cli 3.0搭建Vue项目

新建一个基于 ts 的 vue 项目

vue create vue-cli3-ts

备注:如果是 window 系统,用 git bash 交互提示符(切换)不会工作,用以下命令,即可解决:

winpty vue.cmd create vue-cli3-ts
  • 自定义选项 - Manually select features
  • 添加 ts 支持 - TypeScript
  • 基于类的组件 - y
  • tslint
  • 根据需要添加 router、vuex、css(less 或 scss) 预处理器、单元测试(jest)

交互说明:

  • 上下箭头键切换
  • 空格键选中
  • 回车确定

在已存在的项目中添加 ts

vue add @vue/typescript

会把所有 .js 更改为 .ts

script 命令

// - 启动服务
npm run serve
// - 打包编译
npm run build
// - 执行lint
npm run lint
// - 执行单元测试
npm run test:unit

npm run serve 启动服务:http://localhost:8080/#/

vue 中 ts 语法

demo: src/components/HelloWorld.vue

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
 @Prop() private msg!: string;
}
</script>

和普通的 vue 项目不一样的就是.vue 文件中 script 的 写法。

主要用到的一个库:vue-property-decorator

用法可参考:

  • npm
  • vue-property-decorator用法
  • ts 官方文档

1. 类型注解,类型推论

  • 变量后面通过 冒号+类型 来做类型注解。
  • 编译时类型检查,写代码时代码提醒。
  • 类型推论,根据赋值类型推论出被赋值变量的类型,进行类型限制。
let title: string; // 类型注解
title = 'ts'; // 正确
title = 4; // 错误

let text = 'txt'; // 类型推论
text = 2; // 错误

错误时,vscode 编辑器会有红色波浪号提示。

数组

let names: string[]; // Array<string>
names = ['Tom'];

任意类型,没有类型限制

let foo: any;
foo = 'foo';
foo = 3;

let list: any[];
list = [1, true, 'free'];
list[1] = 100;

函数中使用类型

function greeting (person: string): string {
 return 'Hello, ' + person;
}

// void 类型,常用于没有返回值的函数
function warnUser (): void {
 alert('This is msg');
}

案例:vue demo

<template>
 <div class="hello">
 <input type="text" placeholder="请输入新特性" @keyup.enter="addFeature" />
 <ul>
 <li v-for="feature in features" :key="feature">{{feature}}</li>
 </ul>
 </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Demo extends Vue {

 // 相当于 data 中的数据项
 features: string[];
 constructor () {
 super();
 this.features = ['类型注解', '类型推论', '编译型语言'];
 }

 // 相当于 methods 中的方法
 addFeature (event: any) {
 console.log(event);
 this.features.push(event.target.value);
 event.target.value = '';
 }
}
</script>

2.类

ts 中的类和 es6 中的大体相同,关注特性 访问修饰符

  • private 私有属性,不能在类的外部访问
  • protected 保护属性,可以在类的内部和派生类的内部访问,不能在类的外部访问
  • public 公有属性,可以在任意地方访问,默认值
  • readonly 只读属性,必须在声明时或构造函数里初始化,不可改变值

构造函数:初始化成员变量,参数加上修饰符,能够定义并初始化一个属性

constructor (private name = 'Tom') {
 super();
}

等同于

name: string;
constructor () {
 super();
 this.name = 'Tom';
}

存取器,暴露存取数据时可添加额外逻辑;在 vue 中可用作计算属性

get fullName () { return this.name; }
set fullName (val) { this.name = val; }

案例:vue demo

<template>
 <p>特性数量:{{count}}</p>
</template>
<script lang="ts">
 export default class Demo extends Vue {
 // 定义 getter 作为计算属性
 get count () {
  return this.features.length;
 }
 }
</script>

接口

接口仅约束结构,不要求实现

interface Person {
 firstName: string;
 lastName: string;
}
function greeting (person: Person) {
 return `Hello, ${person.firstName} ${person.lastName}`;
}
const user = {firstName: 'Jane', lastName: 'user'};
console.log(greeting(user));

案例:vue demo,声明接口类型约束数据结构

<template>
 <li v-for="feature in features" :key="feature.id">{{feature.name}}</li>
</template>
<script lang="ts">
 // 定义一个接口约束feature的数据结构
 interface Feature {
 id: number;
 name: string;
 }
 
 export default class Demo extends Vue {
 private features: Feature[];
 
 constructor () {
  super();
  
  this.features = [
  {id: 1, name: '类型注解'},
  {id: 2, name: '类型推论'},
  {id: 3, name: '编译型语言'}
  ]
 }
 }
</script>

泛型

泛型 是指在定义函数、接口或类的时候,不预先指定具体的类,而是在使用时才指定类型的一种特性。

interface Result<T> {
 data: T;
}

// 不使用泛型
interface Result {
 data: Feature[];
}

案例:使用泛型约束接口返回类型

function getData<T>(): Result<T> {
 const data: any = [
 {id: 1, name: '类型注解'},
 {id: 2, name: '类型推论'},
 {id: 3, name: '编译型语言'} 
 ];
 return {data};
}

// 调用
this.features = getData<Feature[]>().data;

案例:使用泛型约束接口返回类型 Promise

function getData<T>(): Promise<Result<T>> {
 const data: any = [
 {id: 1, name: '类型注解'},
 {id: 2, name: '类型推论'},
 {id: 3, name: '编译型语言'} 
 ];
 return Promise.resolve<Result<T>>({data});
}

// 调用 async 方式
async mounted () {
 this.features = (await getData<Feature[]>()).data;
}

// 调用 then 方式
mouted () {
 getData<Feature[]>().then((res: Result<Feature[]>) => {
 this.features = res.data;
 })
}

装饰器

装饰器用于扩展类或者它的属性和方法。

属性声明:@Prop

除了在 @Component 中声明,还可以采用@Prop的方式声明组件属性

export default class Demo extends Vue {
 // Props() 参数是为 vue 提供属性选项
 // !称为明确赋值断言,它是提供给ts的
 @Prop({type: String, require: true})
 private msg!: string;
}

事件处理:@Emit

// 通知父类新增事件,若未指定事件名则函数名作为事件名(驼峰变中划线分隔)
@Emit()
private addFeature(event: any) {// 若没有返回值形参将作为事件参数
 const feature = { name: event.target.value, id: this.features.length + 1 };
 this.features.push(feature);
 event.target.value = "";
 return feature;// 若有返回值则返回值作为事件参数
}

template 模板组件上正常写,@add-feature

变更监测:@Watch

@Watch('msg')
onRouteChange(val:string, oldVal:any){
 console.log(val, oldVal);
}

装饰器原理

装饰器本质是工厂函数,修改传入的类、方法、属性等

类装饰器

// 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
function log(target: Function) {
 // target是构造函数
 console.log(target === Foo); // true
 target.prototype.log = function() {
 console.log(this.bar);
}
// 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
}
@log
class Foo {
 bar = 'bar'
}
const foo = new Foo();
// @ts-ignore
foo.log();

实战一下 Component,新建 Decor.vue

<template>
 <div>{{msg}}</div>
</template>
<script lang='ts'>
 import { Vue } from "vue-property-decorator";
 function Component(options: any) {
 return function(target: any) {
  return Vue.extend(options);
 };
 }
 
 @Component({
 props: {
  msg: {
  type: String,
  default: ""
  }
 }
 })
 export default class Decor extends Vue {}
</script>

源码简单了解

类装饰器主要依赖库:vue-class-component,深入源码,了解其背后究竟做了什么。

vue-property-decorator.js

import Vue from 'vue';
import Component, { createDecorator, mixins } from 'vue-class-component';
export { Component, Vue, mixins as Mixins };

createDecorator、applyMetadata 是核心,后续实现都依赖它,比如 Prop、Watch、Ref。

Prop 源码实现:

export function Prop(options) {
 if (options === void 0) { options = {}; }
 return function (target, key) {
 applyMetadata(options, target, key);
 createDecorator(function (componentOptions, k) {
  ;
  (componentOptions.props || (componentOptions.props = {}))[k] = options;
 })(target, key);
 };
}

applyMetadata,见名知义,就是将装饰器中的信息拿出来放到 options.type 中。

/** @see {@link https://github.com/vuejs/vue-class-component/blob/master/src/reflect.ts} */
var reflectMetadataIsSupported = typeof Reflect !== 'undefined' && typeof Reflect.getMetadata !== 'undefined';
function applyMetadata(options, target, key) {
 if (reflectMetadataIsSupported) {
 if (!Array.isArray(options) &&
  typeof options !== 'function' &&
  typeof options.type === 'undefined') {
  options.type = Reflect.getMetadata('design:type', target, key);
 }
 }
}

Reflect.getMetadata 获取设置在类装饰器上的元数据。可参考文章理解:

  • Decorators
  • TypeScript:理解 Reflect Metadata
  • JavaScript Reflect Metadata 详解

createDecorator,见名知义,就是创建装饰器。本质是在类上定义一个私有属性

export function createDecorator(factory) {
 return function (target, key, index) {
 var Ctor = typeof target === 'function'
  ? target
  : target.constructor;
 if (!Ctor.__decorators__) {
  Ctor.__decorators__ = [];
 }
 if (typeof index !== 'number') {
  index = undefined;
 }
 Ctor.__decorators__.push(function (options) { return factory(options, key, index); });
 };
}

项目代理及 webpack 性能优化

在项目根目录下新建 vue.config.js

本地开发 api 代理

module.exports = {
 devServer: {
 proxy: {
  '/api': {
  target: '<url>',
  changeOrigin: true,
  pathRewrite: {
  '^/api': ''
  }
  }
 }
 }
}

本地开发 api 模拟

devServer: {
 before (app) {
 before (app) {
  app.get('/api/getList', (req, res) => {
  res.json({data: [{id: 1, name: 'vue'}]})
  })
 }
 }
}

性能优化

查看打包依赖

在 package.json 文件 script 中加入命令:

"build:report": "vue-cli-service build --report"

会在 dist 目录下生成 report.html,可直接打开,查看打包依赖,进行分析,进行打包优化

打包优化 - cdn 引入公共库

在 vue.config.js 中加入配置:

configureWebpack: {
 externals: { // cdn 外链,避免包太大,首屏优化
 'vue': 'Vue',
 'vue-router': 'VueRouter',
 'vuex': 'Vuex'
 }
}

在 public/index.html 中加入 cdn 库地址

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js"></script>

<!-- built files will be auto injected -->

再次优化,html head 信息中加,dns 域名预解析,js 库 reload 预加载。

<link rel="dns-prefetch" href="cdnjs.cloudflare.com" rel="external nofollow" >
<link href="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" rel="preload" as="script">
<link href="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js" rel="preload" as="script">
<link href="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js" rel="preload" as="script">

其他

修改本地开发端口号,在 vue.config.js 中加入配置:

devServer: {
 port: 8888
}

体验优化-打包完成提示:

const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const path = require('path');

module.exports = {
 // 链式操作
 chainWebpack: config => {
 // 移除 prefetch 插件,移动端对带宽敏感
 // 路由懒加载,只对用户频繁操作的路由,通过 注释 提前获取
 // component: () => import(/* webpackChunkName: "about" */ /* webpackPrefetch: true */'../views/About.vue')
 config.plugins.delete('prefetch');
 
 // 生产打包才提示,开发不提示
 if (process.env.NODE_ENV === 'production') {
  config.plugin('build-notify').use(WebpackBuildNotifierPlugin, [{
  title: "My Project Webpack Build",
  logo: path.resolve("./img/favicon.png"),
  suppressSuccess: true
  }])
 }
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript 验证上传图片大小[客户端]
Aug 01 Javascript
javascript学习之闭包分析
Dec 02 Javascript
JS获取页面input控件中所有text控件并追加样式属性
Feb 25 Javascript
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环
Jul 06 Javascript
jQuery实现3D文字特效的方法
Mar 10 Javascript
node.js回调函数之阻塞调用与非阻塞调用
Nov 13 Javascript
深入理解JavaScript单体内置对象
Jun 06 Javascript
JS组件系列之使用HTML标签的data属性初始化JS组件
Sep 14 Javascript
整理关于Bootstrap表单的慕课笔记
Mar 29 Javascript
ES6 Promise对象概念与用法分析
Apr 01 Javascript
如何理解Vue的v-model指令的使用方法
Jul 19 Javascript
vue实现简单的MVVM框架
Aug 05 Javascript
js实现倒计时秒杀效果
Mar 25 #Javascript
vue el-table实现自定义表头
Dec 11 #Javascript
Vue如何获取数据列表展示
Dec 11 #Javascript
vue el-table实现行内编辑功能
Dec 11 #Javascript
Vue.js实现可编辑的表格
Dec 11 #Javascript
Vue.js 实现地址管理页面思路详解(地址添加、编辑、删除和设置默认地址)
Dec 11 #Javascript
用JS实现一个简单的打砖块游戏
Dec 11 #Javascript
You might like
javascript显示选择目录对话框的代码
2008/11/10 Javascript
一些Javascript的IE和Firefox(火狐)兼容性的问题总结及常用例子
2009/05/21 Javascript
javascript页面上使用动态时间具体实现
2014/03/18 Javascript
jQuery实现简单的点赞效果
2020/05/29 Javascript
JS修改地址栏参数实例代码
2016/06/14 Javascript
深入分析node.js的异步API和其局限性
2016/09/05 Javascript
js实现图片切换(动画版)
2016/12/25 Javascript
Vue2.0 从零开始_环境搭建操作步骤
2017/06/14 Javascript
Nodejs+angularjs结合multiparty实现多图片上传的示例代码
2017/09/29 NodeJs
简单实现jQuery弹窗效果
2017/10/30 jQuery
vue组件横向树实现代码
2018/08/02 Javascript
JS实现的视频弹幕效果示例
2018/08/17 Javascript
如何实现iframe父子传参通信
2020/02/05 Javascript
[01:05:40]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS DT第三场
2014/05/24 DOTA
Python map和reduce函数用法示例
2015/02/26 Python
简单的python后台管理程序
2017/04/13 Python
Windows下的Jupyter Notebook 安装与自定义启动(图文详解)
2018/02/21 Python
用python与文件进行交互的方法
2018/03/01 Python
利用python的socket发送http(s)请求方法示例
2018/05/07 Python
树莓派+摄像头实现对移动物体的检测
2019/06/22 Python
python opencv 批量改变图片的尺寸大小的方法
2019/06/28 Python
python 自定义装饰器实例详解
2019/07/20 Python
numpy.array 操作使用简单总结
2019/11/08 Python
python 如何去除字符串头尾的多余符号
2019/11/19 Python
纯CSS3实现绘制各种图形实现代码详细整理
2012/12/26 HTML / CSS
测试驱动开发的主要步骤是什么
2014/12/10 面试题
自考毕业生自我鉴定
2013/11/04 职场文书
商业融资计划书
2014/04/29 职场文书
2014年秋季新学期寄语
2014/08/02 职场文书
幼儿园大班毕业评语
2014/12/31 职场文书
家长对孩子的寄语
2015/02/26 职场文书
安全教育第一课观后感
2015/06/17 职场文书
2016大学生求职自荐信范文
2016/01/28 职场文书
升职自荐书
2019/05/09 职场文书
mysql 数据插入优化方法之concurrent_insert
2021/07/01 MySQL
vue项目支付功能代码详解
2022/02/18 Vue.js