赞
踩
目录
忙里偷闲,整理了一下关于如何借助 vue-cli3 搭建 ts + 装饰器 的脚手架,并如何自定义 webpack 配置,优化。
- npm i nrm -g // 安装
- nrm ls // 查看可用源,及当前源,带*的是当前使用的源
- nrm use taobao // 切换源,使用源
- nrm add <registry> <url> // 其中reigstry为源名,url为源的路径
- nrm del <registry> // 删除对应的源
- nrm test npm // 测试源的响应速度
- 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项目
vue create vue-cli3-ts
备注:如果是 window 系统,用 git bash 交互提示符(切换)不会工作,用以下命令,即可解决:
winpty vue.cmd create vue-cli3-ts
交互说明:
vue add @vue/typescript
会把所有 .js 更改为 .ts
- // - 启动服务
- npm run serve
- // - 打包编译
- npm run build
- // - 执行lint
- npm run lint
- // - 执行单元测试
- npm run test:unit
npm run serve 启动服务:http://localhost:8080/#/
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
用法可参考:
- 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>
ts 中的类和 es6 中的大体相同,关注特性 访问修饰符
构造函数:初始化成员变量,参数加上修饰符,能够定义并初始化一个属性
- 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); });
- };
- }
在项目根目录下新建 vue.config.js
- module.exports = {
- devServer: {
- proxy: {
- '/api': {
- target: '<url>',
- changeOrigin: true,
- pathRewrite: {
- '^/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
- }])
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。