如何用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 相关文章推荐
jQuery中需要注意的细节问题小结
Dec 06 Javascript
jQuery Animation实现CSS3动画示例介绍
Aug 14 Javascript
JavaScript如何从listbox里同时删除多个项目
Oct 12 Javascript
JavaScript中的时间处理小结
Feb 24 Javascript
JavaScript简单下拉菜单特效
Sep 13 Javascript
详解javascript表单的Ajax提交插件的使用
Dec 29 Javascript
JS前端开发判断是否是手机端并跳转操作(小结)
Feb 05 Javascript
vue按需加载组件webpack require.ensure的方法
Dec 13 Javascript
Bootstrap fileinput 上传新文件移除时触发服务器同步删除的配置
Oct 08 Javascript
说说如何使用Vuex进行状态管理(小结)
Apr 14 Javascript
vue h5移动端禁止缩放代码
Oct 28 Javascript
jquery实现进度条状态展示
Mar 26 jQuery
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
《OVERLORD》手游英文版即将上线 手机上也能扮演骨王
2020/04/09 日漫
PHP 配置open_basedir 让各虚拟站点独立运行
2009/11/12 PHP
PHP云打印类完整示例
2016/10/15 PHP
PHP通过bypass disable functions执行系统命令的方法汇总
2018/05/02 PHP
再论Javascript下字符串连接的性能
2011/03/05 Javascript
JS动态创建Table,Tr,Td并赋值的具体实现
2013/07/05 Javascript
jquery实现在光标位置插入内容的方法
2015/02/05 Javascript
基于jQuery插件实现环形图标菜单旋转切换特效
2015/05/15 Javascript
《JavaScript高级编程》学习笔记之object和array引用类型
2015/11/01 Javascript
全面了解JS中的匿名函数
2016/06/29 Javascript
jQuery实现的无限级下拉菜单功能示例
2016/09/12 Javascript
JS开发中基本数据类型具体有哪几种
2017/10/19 Javascript
vue-router项目实战总结篇
2018/02/11 Javascript
Node.js的Koa实现JWT用户认证方法
2018/05/05 Javascript
Vue中使用vee-validate表单验证的方法
2018/05/09 Javascript
详解vue中的父子传值双向绑定及数据更新问题
2019/06/13 Javascript
[01:17:47]TNC vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python实现百度关键词排名查询
2014/03/30 Python
python删除服务器文件代码示例
2018/02/09 Python
详解Python中如何写控制台进度条的整理
2018/03/07 Python
解决Django 在ForeignKey中出现 non-nullable field错误的问题
2019/08/06 Python
Python不支持 i ++ 语法的原因解析
2020/07/22 Python
Bulk Powders意大利:运动补充在线商店
2019/02/09 全球购物
医学生实习自荐信
2013/10/01 职场文书
外贸采购员求职的自我评价
2013/11/26 职场文书
费用会计岗位职责
2014/01/01 职场文书
难忘的一天教学反思
2014/04/30 职场文书
党员个人剖析材料
2014/09/30 职场文书
工作骂脏话检讨书
2014/10/05 职场文书
2014年学习委员工作总结
2014/11/14 职场文书
团员个人总结
2015/02/26 职场文书
实习推荐信格式模板
2015/03/27 职场文书
借条格式范本
2015/05/25 职场文书
卢旺达饭店观后感
2015/06/05 职场文书
喜迎建国70周年:有关爱国的名言名句
2019/09/24 职场文书
《语言的突破》读后感3篇
2019/12/12 职场文书