如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的方法
忙里偷闲,整理了一下关于如何借助vue-cli3搭建ts+装饰器的脚手架,并如何自定义webpack配置,优化。
准备工作
- @vue/cli@4.1.1
- vue2.6
- nodev12.13.0
安装node
- 安装node
- 全局安装nrm,npm的镜像源管理工具。
npminrm-g//安装 nrmls//查看可用源,及当前源,带*的是当前使用的源 nrmusetaobao//切换源,使用源 nrmadd//其中reigstry为源名,url为源的路径 nrmdel //删除对应的源 nrmtestnpm//测试源的响应速度
安装vue-cli3
参考官方文档:https://cli.vuejs.org/zh/guide/
npmi@vue/cli-g//全局安装 vue--version//检查是否安装
补充
npmlist-g--depth0//查看全局安装的包 npmoutdated-g--depth=0//查看需要更新的全局包 npmupdate包名-g//更新全局安装的包
搭建项目
可参考:使用Vue-cli3.0搭建Vue项目
新建一个基于ts的vue项目
vuecreatevue-cli3-ts
备注:如果是window系统,用gitbash交互提示符(切换)不会工作,用以下命令,即可解决:
winptyvue.cmdcreatevue-cli3-ts
- 自定义选项-Manuallyselectfeatures
- 添加ts支持-TypeScript
- 基于类的组件-y
- tslint
- 根据需要添加router、vuex、css(less或scss)预处理器、单元测试(jest)
交互说明:
- 上下箭头键切换
- 空格键选中
- 回车确定
在已存在的项目中添加ts
vueadd@vue/typescript
会把所有.js更改为.ts
script命令
//-启动服务 npmrunserve //-打包编译 npmrunbuild //-执行lint npmrunlint //-执行单元测试 npmruntest:unit
npmrunserve启动服务:http://localhost:8080/#/
vue中ts语法
demo:src/components/HelloWorld.vue
import{Component,Prop,Vue}from'vue-property-decorator'; @Component exportdefaultclassHelloWorldextendsVue{ @Prop()privatemsg!:string; }
和普通的vue项目不一样的就是.vue文件中script的写法。
主要用到的一个库:vue-property-decorator
用法可参考:
- npm
- vue-property-decorator用法
- ts官方文档
1.类型注解,类型推论
- 变量后面通过冒号+类型来做类型注解。
- 编译时类型检查,写代码时代码提醒。
- 类型推论,根据赋值类型推论出被赋值变量的类型,进行类型限制。
lettitle:string;//类型注解 title='ts';//正确 title=4;//错误 lettext='txt';//类型推论 text=2;//错误
错误时,vscode编辑器会有红色波浪号提示。
数组
letnames:string[];//Arraynames=['Tom'];
任意类型,没有类型限制
letfoo:any; foo='foo'; foo=3; letlist:any[]; list=[1,true,'free']; list[1]=100;
函数中使用类型
functiongreeting(person:string):string{ return'Hello,'+person; } //void类型,常用于没有返回值的函数 functionwarnUser():void{ alert('Thisismsg'); }
案例:vuedemo
{{feature}}
2.类
ts中的类和es6中的大体相同,关注特性访问修饰符
- private私有属性,不能在类的外部访问
- protected保护属性,可以在类的内部和派生类的内部访问,不能在类的外部访问
- public公有属性,可以在任意地方访问,默认值
- readonly只读属性,必须在声明时或构造函数里初始化,不可改变值
构造函数:初始化成员变量,参数加上修饰符,能够定义并初始化一个属性
constructor(privatename='Tom'){ super(); }
等同于
name:string; constructor(){ super(); this.name='Tom'; }
存取器,暴露存取数据时可添加额外逻辑;在vue中可用作计算属性
getfullName(){returnthis.name;} setfullName(val){this.name=val;}
案例:vuedemo
特性数量:{{count}}
exportdefaultclassDemoextendsVue{ //定义getter作为计算属性 getcount(){ returnthis.features.length; } }
接口
接口仅约束结构,不要求实现
interfacePerson{ firstName:string; lastName:string; } functiongreeting(person:Person){ return`Hello,${person.firstName}${person.lastName}`; } constuser={firstName:'Jane',lastName:'user'}; console.log(greeting(user));
案例:vuedemo,声明接口类型约束数据结构
{{feature.name}} //定义一个接口约束feature的数据结构 interfaceFeature{ id:number; name:string; } exportdefaultclassDemoextendsVue{ privatefeatures:Feature[]; constructor(){ super(); this.features=[ {id:1,name:'类型注解'}, {id:2,name:'类型推论'}, {id:3,name:'编译型语言'} ] } }
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类,而是在使用时才指定类型的一种特性。
interfaceResult{ data:T; } //不使用泛型 interfaceResult{ data:Feature[]; }
案例:使用泛型约束接口返回类型
functiongetData():Result { constdata:any=[ {id:1,name:'类型注解'}, {id:2,name:'类型推论'}, {id:3,name:'编译型语言'} ]; return{data}; } //调用 this.features=getData ().data;
案例:使用泛型约束接口返回类型Promise
functiongetData():Promise >{ constdata:any=[ {id:1,name:'类型注解'}, {id:2,name:'类型推论'}, {id:3,name:'编译型语言'} ]; returnPromise.resolve >({data}); } //调用async方式 asyncmounted(){ this.features=(awaitgetData ()).data; } //调用then方式 mouted(){ getData ().then((res:Result )=>{ this.features=res.data; }) }
装饰器
装饰器用于扩展类或者它的属性和方法。
属性声明:@Prop
除了在@Component中声明,还可以采用@Prop的方式声明组件属性
exportdefaultclassDemoextendsVue{ //Props()参数是为vue提供属性选项 //!称为明确赋值断言,它是提供给ts的 @Prop({type:String,require:true}) privatemsg!:string; }
事件处理:@Emit
//通知父类新增事件,若未指定事件名则函数名作为事件名(驼峰变中划线分隔) @Emit() privateaddFeature(event:any){//若没有返回值形参将作为事件参数 constfeature={name:event.target.value,id:this.features.length+1}; this.features.push(feature); event.target.value=""; returnfeature;//若有返回值则返回值作为事件参数 }
template模板组件上正常写,@add-feature
变更监测:@Watch
@Watch('msg') onRouteChange(val:string,oldVal:any){ console.log(val,oldVal); }
装饰器原理
装饰器本质是工厂函数,修改传入的类、方法、属性等
类装饰器
//类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。 functionlog(target:Function){ //target是构造函数 console.log(target===Foo);//true target.prototype.log=function(){ console.log(this.bar); } //如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。 } @log classFoo{ bar='bar' } constfoo=newFoo(); //@ts-ignore foo.log();
实战一下Component,新建Decor.vue
{{msg}}import{Vue}from"vue-property-decorator"; functionComponent(options:any){ returnfunction(target:any){ returnVue.extend(options); }; } @Component({ props:{ msg:{ type:String, default:"" } } }) exportdefaultclassDecorextendsVue{}
源码简单了解
类装饰器主要依赖库:vue-class-component,深入源码,了解其背后究竟做了什么。
vue-property-decorator.js
importVuefrom'vue'; importComponent,{createDecorator,mixins}from'vue-class-component'; export{Component,Vue,mixinsasMixins};
createDecorator、applyMetadata是核心,后续实现都依赖它,比如Prop、Watch、Ref。
Prop源码实现:
exportfunctionProp(options){ if(options===void0){options={};} returnfunction(target,key){ applyMetadata(options,target,key); createDecorator(function(componentOptions,k){ ; (componentOptions.props||(componentOptions.props={}))[k]=options; })(target,key); }; }
applyMetadata,见名知义,就是将装饰器中的信息拿出来放到options.type中。
/**@see{@linkhttps://github.com/vuejs/vue-class-component/blob/master/src/reflect.ts}*/ varreflectMetadataIsSupported=typeofReflect!=='undefined'&&typeofReflect.getMetadata!=='undefined'; functionapplyMetadata(options,target,key){ if(reflectMetadataIsSupported){ if(!Array.isArray(options)&& typeofoptions!=='function'&& typeofoptions.type==='undefined'){ options.type=Reflect.getMetadata('design:type',target,key); } } }
Reflect.getMetadata获取设置在类装饰器上的元数据。可参考文章理解:
- Decorators
- TypeScript:理解ReflectMetadata
- JavaScriptReflectMetadata详解
createDecorator,见名知义,就是创建装饰器。本质是在类上定义一个私有属性
exportfunctioncreateDecorator(factory){ returnfunction(target,key,index){ varCtor=typeoftarget==='function' ?target :target.constructor; if(!Ctor.__decorators__){ Ctor.__decorators__=[]; } if(typeofindex!=='number'){ index=undefined; } Ctor.__decorators__.push(function(options){returnfactory(options,key,index);}); }; }
项目代理及webpack性能优化
在项目根目录下新建vue.config.js
本地开发api代理
module.exports={ devServer:{ proxy:{ '/api':{ target:'', 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-servicebuild--report"
会在dist目录下生成report.html,可直接打开,查看打包依赖,进行分析,进行打包优化
打包优化-cdn引入公共库
在vue.config.js中加入配置:
configureWebpack:{ externals:{//cdn外链,避免包太大,首屏优化 'vue':'Vue', 'vue-router':'VueRouter', 'vuex':'Vuex' } }
在public/index.html中加入cdn库地址
再次优化,htmlhead信息中加,dns域名预解析,js库reload预加载。
其他
修改本地开发端口号,在vue.config.js中加入配置:
devServer:{ port:8888 }
体验优化-打包完成提示:
constWebpackBuildNotifierPlugin=require('webpack-build-notifier'); constpath=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:"MyProjectWebpackBuild", logo:path.resolve("./img/favicon.png"), suppressSuccess:true }]) } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。