如何用vue-cli3脚手架搭建一个基于ts的基础脚手架
忙里偷闲,整理了一下关于如何借助 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
npm i @vue/cli -g // 全局安装
vue --version // 检查是否安装
补充
npm list -g --depth 0 // 查看全局安装的包
npm outdated -g --depth=0 // 查看需要更新的全局包
npm update 包名 -g // 更新全局安装的包
搭建项目
新建一个基于 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
用法可参考:
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 获取设置在类装饰器上的元数据。可参考文章理解:
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" >
<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
}])
}
}
}
如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的更多相关文章
- 如何用 Parse 和 Swift 搭建一个像 Instagram 那样的应用?(3)
[编者按]本篇文章作者是 Reinder de Vries,既是一名企业家,也是优秀的程序员,发表多篇应用程序的博客.本篇文章中,作者主要介绍了如何基于 Parse 特点,打造一款类似 Instagr ...
- 如何用 Parse 和 Swift 搭建一个像 Instagram 那样的应用?(2)
[编者按]本篇文章作者是 Reinder de Vries,既是一名企业家,也是优秀的程序员,发表多篇应用程序的博客.本篇文章中,作者主要介绍了如何基于 Parse 特点,打造一款类似 Instagr ...
- 如何用 Parse 和 Swift 搭建一个像 Instagram 那样的应用?
[编者按]本篇文章作者是Reinder de Vries,既是一名企业家,也是优秀的程序员,发表多篇应用程序的博客.本篇文章中,作者主要介绍了如何基于Parse特点,打造一款类似Instagram的应 ...
- Uber如何搭建一个基于Kafka的跨数据中心复制平台 原创: 徐宏亮 AI前线 今天
Uber如何搭建一个基于Kafka的跨数据中心复制平台 原创: 徐宏亮 AI前线 今天
- 快速搭建一个基于react的项目
最近在学习react,快速搭建一个基于react的项目 1.创建一个放项目文件夹,用编辑器打开 2.打开集成终端输入命令: npm install -g create-react-app 3. cre ...
- 如何用webgl(three.js)搭建一个3D库房,3D密集架,3D档案室(升级版)
很长一段时间没有写3D库房,3D密集架相关的效果文章了,刚好最近有相关项目落地,索性总结一下 与之前我写的3D库房密集架文章<如何用webgl(three.js)搭建一个3D库房,3D密集架,3 ...
- 如何用webgl(three.js)搭建一个3D库房,3D仓库,3D码头,3D集装箱可视化孪生系统——第十五课
序 又是快两个月没写随笔了,长时间不总结项目,不锻炼文笔,一开篇,多少都会有些生疏,不知道如何开篇,如何写下去.有点江郎才尽,黔驴技穷的感觉. 写随笔,通常三步走,第一步,搭建框架,先把你要写的内容框 ...
- 如何用webgl(three.js)搭建一个3D库房,3D仓库3D码头,3D集装箱,车辆定位,叉车定位可视化孪生系统——第十五课
序 又是快两个月没写随笔了,长时间不总结项目,不锻炼文笔,一开篇,多少都会有些生疏,不知道如何开篇,如何写下去.有点江郎才尽,黔驴技穷的感觉. 写随笔,通常三步走,第一步,搭建框架,先把你要写的内容框 ...
- 用vue-cli脚手架搭建一个仿网易云音乐的全家桶vue项目
一,vue-cli环境搭建 1,全局安装webpack npm install webpack -g 2,安装vue脚手架 npm install vue-cli -g 3,新建一个新的project ...
随机推荐
- 使用Typescript重构axios(十)——异常处理:增强版
0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...
- 2684亿!阿里CTO张建锋:不是任何一朵云都撑得住双11
2019天猫双11 成交额2684亿! "不是任何一朵云都能撑住这个流量.中国有两朵云,一朵是阿里云,一朵叫其他云."11月11日晚,阿里巴巴集团CTO张建锋表示,"阿里 ...
- 简单搭建syslog-ng server记录log
### 简单搭建syslog-ng server记录log 安装syslog-ng apt-get install syslog-ng 安装syslog-ng 配置syslog-ng vim /etc ...
- 史上最详细的C语言和Python的插入排序算法
史上最详细的C语言和Python的插入排序算法插入排序原理:所谓插入排序,就像我们在打牌(斗地主)时,整理我们自己手中自己的牌一样,就像是2,1,3,9,J,K,5,4,这四张牌.我们要把它其中的几张 ...
- Java设计模式之模板方法模式(Template)
前言: 我们在开发中有很多固定的流程,这些流程有很多步凑是固定的,比如JDBC中获取连接,关闭连接这些流程是固定不变的,变动的只有设置参数,解析结果集这些是根据不同的实体对象“来做调整”,针对这种拥有 ...
- jenkins手把手教你从入门到放弃01-jenkins简介(详解)
一.简介 jenkins是一个可扩展的持续集成引擎.持续集成,也就是通常所说的CI(Continues Integration),可以说是现代软件技术开发的基础.持续集成是一种软件开发实践, 即团队开 ...
- vuejs学习之新的components组件挂载
暂时写个目录,内容待完善,主要是记录我的学习过程,方便以后复习
- PowerMock学习(六)之Mock Final的使用
Mock Final mockfinal相对来说就比较简单了,使用powermock来测试使用final修饰的method或class,比较简单,接口调用部分,还是service调用dao. 对于接口 ...
- 性能测试——记XX银行电票系统上线后宕机问题诊断优化
四月份我们公司负责的电票系统上线了,这个系统上线比客户方其他系统上线还特殊,是二期改造项目,旧系统数据还要整合抽取到新系统中继续使用,而且该系统不是增量型方式开发上线的,而且全部开发完后全国上线的,这 ...
- 【Android - 问题解决】之ScrollView嵌套ListView时总是自动滑动到ListView顶部的问题
最近做了一个项目,里面有一个ScrollView嵌套ListView的布局. 做出来之后发现,进入这个界面之后,总是自动滑动到ListView的顶部,而ScrollView中位于ListView上面的 ...