如何用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 无符号右移运算符
Apr 17 Javascript
jQueryUI写一个调整分类的拖放效果实现代码
May 10 Javascript
禁止你的左键复制实用技巧
Jan 04 Javascript
浅析offsetLeft,Left,clientLeft之间的区别
Nov 30 Javascript
js中document.write的那点事
Dec 12 Javascript
JS弹出对话框实现方法(三种方式)
Dec 18 Javascript
jquery插件方式实现table查询功能的简单实例
Jun 06 Javascript
JS表格组件神器bootstrap table使用指南详解
Apr 12 Javascript
通过构造函数实例化对象的方法
Jun 28 Javascript
浅谈Angular4实现热加载开发旅程
Sep 08 Javascript
JS字符串和数组如何实现相互转化
Jul 02 Javascript
Vuex实现购物车小功能
Aug 17 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
PHP 遍历文件实现代码
2011/05/04 PHP
PHP原生模板引擎 最简单的模板引擎
2012/04/25 PHP
PHP实现 APP端微信支付功能
2018/06/22 PHP
关于Laravel参数验证的一些疑与惑
2019/11/19 PHP
PHP实现微信公众号验证Token的示例代码
2019/12/16 PHP
select组合框option的捕捉实例代码
2008/09/30 Javascript
Javascript 对象的解释
2008/11/24 Javascript
AngularJS bootstrap启动详解及实例代码
2016/09/14 Javascript
javascript表单正则应用
2017/02/04 Javascript
JS实现禁止用户使用Ctrl+鼠标滚轮缩放网页的方法
2017/04/28 Javascript
jQuery实现轮播图及其原理详解
2020/04/12 jQuery
浅谈webpack4.x 入门(一篇足矣)
2018/09/05 Javascript
微信小程序实现多选框全选与取消全选功能示例
2019/05/14 Javascript
javascript实现的时间格式加8小时功能示例
2019/06/13 Javascript
[45:18]完美世界DOTA2联赛循环赛 PXG vs IO 第二场 11.06
2020/11/09 DOTA
Python的Django应用程序解决AJAX跨域访问问题的方法
2016/05/31 Python
Pycharm 设置自定义背景颜色的图文教程
2018/05/23 Python
Python3.5基础之NumPy模块的使用图文与实例详解
2019/04/24 Python
python3中替换python2中cmp函数的实现
2019/08/20 Python
通过python连接Linux命令行代码实例
2020/02/18 Python
浅析Django 接收所有文件,前端展示文件(包括视频,文件,图片)ajax请求
2020/03/09 Python
python speech模块的使用方法
2020/09/09 Python
深入CSS3 动画效果的总结详解
2013/05/09 HTML / CSS
html5 跨文档消息传输示例探讨
2013/04/01 HTML / CSS
HTML5 LocalStorage 本地存储刷新值还在
2017/03/10 HTML / CSS
Html5 滚动穿透的方法
2019/05/13 HTML / CSS
美国高档帽子网上商店:Hats.com
2018/08/09 全球购物
ORLY官网:美国专业美甲一线品牌
2019/12/11 全球购物
保加利亚服装和鞋类购物网站:Bibloo.bg
2020/11/08 全球购物
岗位明星事迹材料
2014/05/18 职场文书
房屋租赁协议书(标准版)
2014/10/02 职场文书
2014年度考核工作总结
2014/12/24 职场文书
运动会运动员赞词
2015/07/22 职场文书
工作违纪的检讨书范文
2019/07/09 职场文书
python中对列表的删除和添加方法详解
2022/02/24 Python
苹果的回收机器人可以通过拆解iPhone获取大量的金和铜并外公布了环境保护最新进展
2022/04/21 数码科技