如何用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 相关文章推荐
简单实用的js调试logger组件实现代码
Nov 20 Javascript
JSON.stringify 语法实例讲解
Mar 14 Javascript
from 表单提交返回值用post或者是get方法实现
Aug 21 Javascript
通过AJAX的JS、JQuery两种方式解析XML示例介绍
Sep 23 Javascript
JS简单实现文件上传实例代码(无需插件)
Nov 15 Javascript
基于jquery的simpleValidate简易验证插件
Jan 31 Javascript
jQuery插件实现静态HTML验证码校验
Nov 06 Javascript
在Docker快速部署Node.js应用的详细步骤
Sep 02 Javascript
详解springmvc 接收json对象的两种方式
Dec 06 Javascript
vue.js树形组件之删除双击增加分支实例代码
Feb 28 Javascript
webpack配置proxyTable时pathRewrite无效的解决方法
Dec 13 Javascript
JS+CSS3实现的简易钟表效果示例
Apr 13 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
Gregarius中文日期格式问题解决办法
2008/04/22 PHP
php ajax 静态分页过程形式
2011/09/02 PHP
PHP5中Cookie与 Session使用详解
2013/04/30 PHP
使用swoole扩展php websocket示例
2014/02/13 PHP
Javascript typeof 用法
2008/12/28 Javascript
JavaScript Prototype对象
2009/01/07 Javascript
动态为事件添加js代码示例
2009/02/15 Javascript
通过JS获取用户本地图片路径并显示的代码
2012/02/16 Javascript
浅谈javascript的原型继承
2012/07/25 Javascript
JavaScript的漂亮的代码片段
2013/06/05 Javascript
jQuery结合CSS制作漂亮的select下拉菜单
2015/05/03 Javascript
使用Node.js为其他程序编写扩展的基本方法
2015/06/23 Javascript
jQuery插件ajaxFileUpload异步上传文件
2016/10/19 Javascript
AngularJS实现动态添加Option的方法
2017/05/17 Javascript
jquery仿京东商品放大浏览页面
2017/06/06 jQuery
vue.js的手脚架vue-cli项目搭建的步骤
2017/08/30 Javascript
vue组件三大核心概念图文详解
2019/05/30 Javascript
javascript canvas时钟模拟器
2020/07/13 Javascript
Python自然语言处理之词干,词形与最大匹配算法代码详解
2017/11/16 Python
Python元组及文件核心对象类型详解
2018/02/11 Python
python 字符串只保留汉字的方法
2018/11/16 Python
django celery redis使用具体实践
2019/04/08 Python
Python中函数的返回值示例浅析
2019/08/28 Python
Python装饰器如何实现修复过程解析
2020/09/05 Python
Python之字典添加元素的几种方法
2020/09/30 Python
Python环境使用OpenCV检测人脸实现教程
2020/10/19 Python
设计师珠宝:Ylang 23
2018/05/11 全球购物
Kaufmann Mercantile官网:家居装饰、配件、户外及更多
2018/09/28 全球购物
ajax是什么及其工作原理
2012/02/08 面试题
师范学院美术系毕业生自我鉴定
2014/01/29 职场文书
我们的节日端午节活动方案
2014/03/02 职场文书
企业理念标语
2014/06/09 职场文书
优秀团员事迹材料1500字
2014/08/31 职场文书
2014年售后服务工作总结
2014/11/18 职场文书
志愿者个人总结
2015/03/03 职场文书
高三生物教学反思
2016/02/22 职场文书