当前位置:   article > 正文

如何用vue-cli3脚手架搭建一个基于ts的基础脚手架_vue3+ts 组件库脚手架

vue3+ts 组件库脚手架

目录

  • 准备工作
  • 搭建项目
  • vue 中 ts 语法
  • 项目代理及 webpack 性能优化
  • 其他

忙里偷闲,整理了一下关于如何借助 vue-cli3 搭建 ts + 装饰器 的脚手架,并如何自定义 webpack 配置,优化。

准备工作

系统小说网 wap.kuwx.net
  • @vue/cli@4.1.1
  • vue 2.6
  • node v12.13.0
安装 node
  • 安装 node
  • 全局安装 nrm,npm 的镜像源管理工具。
  1. npm i nrm -g // 安装
  2. nrm ls // 查看可用源,及当前源,带*的是当前使用的源
  3. nrm use taobao // 切换源,使用源
  4. nrm add <registry> <url> // 其中reigstry为源名,url为源的路径
  5. nrm del <registry> // 删除对应的源
  6. nrm test npm // 测试源的响应速度
安装 vue-cli3
  • 参考官方文档:https://cli.vuejs.org/zh/guide/
  1. npm i @vue/cli -g // 全局安装
  2. vue --version // 检查是否安装

补充

  1. npm list -g --depth 0 // 查看全局安装的包
  2. npm outdated -g --depth=0 // 查看需要更新的全局包
  3. 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 命令
  1. // - 启动服务
  2. npm run serve
  3. // - 打包编译
  4. npm run build
  5. // - 执行lint
  6. npm run lint
  7. // - 执行单元测试
  8. npm run test:unit

npm run serve 启动服务:http://localhost:8080/#/

vue 中 ts 语法

demo: src/components/HelloWorld.vue

  1. <script lang="ts">
  2. import { Component, Prop, Vue } from 'vue-property-decorator';
  3. @Component
  4. export default class HelloWorld extends Vue {
  5. @Prop() private msg!: string;
  6. }
  7. </script>

和普通的 vue 项目不一样的就是.vue 文件中 script 的 写法。

主要用到的一个库:vue-property-decorator

用法可参考:

  • npm
  • vue-property-decorator用法
  • ts 官方文档
1. 类型注解,类型推论
  • 变量后面通过 冒号+类型 来做类型注解。
  • 编译时类型检查,写代码时代码提醒。
  • 类型推论,根据赋值类型推论出被赋值变量的类型,进行类型限制。
  1. let title: string; // 类型注解
  2. title = 'ts'; // 正确
  3. title = 4; // 错误
  4. let text = 'txt'; // 类型推论
  5. text = 2; // 错误

错误时,vscode 编辑器会有红色波浪号提示。

数组

  1. let names: string[]; // Array<string>
  2. names = ['Tom'];

任意类型,没有类型限制

  1. let foo: any;
  2. foo = 'foo';
  3. foo = 3;
  4. let list: any[];
  5. list = [1, true, 'free'];
  6. list[1] = 100;

函数中使用类型

  1. function greeting (person: string): string {
  2. return 'Hello, ' + person;
  3. }
  4. // void 类型,常用于没有返回值的函数
  5. function warnUser (): void {
  6. alert('This is msg');
  7. }

案例:vue demo

  1. <template>
  2. <div class="hello">
  3. <input type="text" placeholder="请输入新特性" @keyup.enter="addFeature" />
  4. <ul>
  5. <li v-for="feature in features" :key="feature">{{feature}}</li>
  6. </ul>
  7. </div>
  8. </template>
  9. <script lang="ts">
  10. import { Component, Prop, Vue } from 'vue-property-decorator';
  11. @Component
  12. export default class Demo extends Vue {
  13. // 相当于 data 中的数据项
  14. features: string[];
  15. constructor () {
  16. super();
  17. this.features = ['类型注解', '类型推论', '编译型语言'];
  18. }
  19. // 相当于 methods 中的方法
  20. addFeature (event: any) {
  21. console.log(event);
  22. this.features.push(event.target.value);
  23. event.target.value = '';
  24. }
  25. }
  26. </script>
2.类

ts 中的类和 es6 中的大体相同,关注特性 访问修饰符

  • private 私有属性,不能在类的外部访问
  • protected 保护属性,可以在类的内部和派生类的内部访问,不能在类的外部访问
  • public 公有属性,可以在任意地方访问,默认值
  • readonly 只读属性,必须在声明时或构造函数里初始化,不可改变值

构造函数:初始化成员变量,参数加上修饰符,能够定义并初始化一个属性

  1. constructor (private name = 'Tom') {
  2. super();
  3. }

等同于

  1. name: string;
  2. constructor () {
  3. super();
  4. this.name = 'Tom';
  5. }

存取器,暴露存取数据时可添加额外逻辑;在 vue 中可用作计算属性

  1. get fullName () { return this.name; }
  2. set fullName (val) { this.name = val; }

案例:vue demo

  1. <template>
  2. <p>特性数量:{{count}}</p>
  3. </template>
  4. <script lang="ts">
  5. export default class Demo extends Vue {
  6. // 定义 getter 作为计算属性
  7. get count () {
  8. return this.features.length;
  9. }
  10. }
  11. </script>
接口

接口仅约束结构,不要求实现

  1. interface Person {
  2. firstName: string;
  3. lastName: string;
  4. }
  5. function greeting (person: Person) {
  6. return `Hello, ${person.firstName} ${person.lastName}`;
  7. }
  8. const user = {firstName: 'Jane', lastName: 'user'};
  9. console.log(greeting(user));

案例:vue demo,声明接口类型约束数据结构

  1. <template>
  2. <li v-for="feature in features" :key="feature.id">{{feature.name}}</li>
  3. </template>
  4. <script lang="ts">
  5. // 定义一个接口约束feature的数据结构
  6. interface Feature {
  7. id: number;
  8. name: string;
  9. }
  10. export default class Demo extends Vue {
  11. private features: Feature[];
  12. constructor () {
  13. super();
  14. this.features = [
  15. {id: 1, name: '类型注解'},
  16. {id: 2, name: '类型推论'},
  17. {id: 3, name: '编译型语言'}
  18. ]
  19. }
  20. }
  21. </script>
泛型

泛型 是指在定义函数、接口或类的时候,不预先指定具体的类,而是在使用时才指定类型的一种特性。

  1. interface Result<T> {
  2. data: T;
  3. }
  4. // 不使用泛型
  5. interface Result {
  6. data: Feature[];
  7. }

案例:使用泛型约束接口返回类型

  1. function getData<T>(): Result<T> {
  2. const data: any = [
  3. {id: 1, name: '类型注解'},
  4. {id: 2, name: '类型推论'},
  5. {id: 3, name: '编译型语言'}
  6. ];
  7. return {data};
  8. }
  9. // 调用
  10. this.features = getData<Feature[]>().data;

案例:使用泛型约束接口返回类型 Promise

  1. function getData<T>(): Promise<Result<T>> {
  2. const data: any = [
  3. {id: 1, name: '类型注解'},
  4. {id: 2, name: '类型推论'},
  5. {id: 3, name: '编译型语言'}
  6. ];
  7. return Promise.resolve<Result<T>>({data});
  8. }
  9. // 调用 async 方式
  10. async mounted () {
  11. this.features = (await getData<Feature[]>()).data;
  12. }
  13. // 调用 then 方式
  14. mouted () {
  15. getData<Feature[]>().then((res: Result<Feature[]>) => {
  16. this.features = res.data;
  17. })
  18. }
装饰器

装饰器用于扩展类或者它的属性和方法。

属性声明:@Prop

除了在 @Component 中声明,还可以采用@Prop的方式声明组件属性

  1. export default class Demo extends Vue {
  2. // Props() 参数是为 vue 提供属性选项
  3. // !称为明确赋值断言,它是提供给ts的
  4. @Prop({type: String, require: true})
  5. private msg!: string;
  6. }

事件处理:@Emit

  1. // 通知父类新增事件,若未指定事件名则函数名作为事件名(驼峰变中划线分隔)
  2. @Emit()
  3. private addFeature(event: any) {// 若没有返回值形参将作为事件参数
  4. const feature = { name: event.target.value, id: this.features.length + 1 };
  5. this.features.push(feature);
  6. event.target.value = "";
  7. return feature;// 若有返回值则返回值作为事件参数
  8. }

template 模板组件上正常写,@add-feature

变更监测:@Watch

  1. @Watch('msg')
  2. onRouteChange(val:string, oldVal:any){
  3. console.log(val, oldVal);
  4. }

装饰器原理

装饰器本质是工厂函数,修改传入的类、方法、属性等

类装饰器

  1. // 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
  2. function log(target: Function) {
  3. // target是构造函数
  4. console.log(target === Foo); // true
  5. target.prototype.log = function() {
  6. console.log(this.bar);
  7. }
  8. // 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
  9. }
  10. @log
  11. class Foo {
  12. bar = 'bar'
  13. }
  14. const foo = new Foo();
  15. // @ts-ignore
  16. foo.log();

实战一下 Component,新建 Decor.vue

  1. <template>
  2. <div>{{msg}}</div>
  3. </template>
  4. <script lang='ts'>
  5. import { Vue } from "vue-property-decorator";
  6. function Component(options: any) {
  7. return function(target: any) {
  8. return Vue.extend(options);
  9. };
  10. }
  11. @Component({
  12. props: {
  13. msg: {
  14. type: String,
  15. default: ""
  16. }
  17. }
  18. })
  19. export default class Decor extends Vue {}
  20. </script>
源码简单了解

类装饰器主要依赖库:vue-class-component,深入源码,了解其背后究竟做了什么。

vue-property-decorator.js

  1. import Vue from 'vue';
  2. import Component, { createDecorator, mixins } from 'vue-class-component';
  3. export { Component, Vue, mixins as Mixins };

createDecorator、applyMetadata 是核心,后续实现都依赖它,比如 Prop、Watch、Ref。

Prop 源码实现:

  1. export function Prop(options) {
  2. if (options === void 0) { options = {}; }
  3. return function (target, key) {
  4. applyMetadata(options, target, key);
  5. createDecorator(function (componentOptions, k) {
  6. ;
  7. (componentOptions.props || (componentOptions.props = {}))[k] = options;
  8. })(target, key);
  9. };
  10. }

applyMetadata,见名知义,就是将装饰器中的信息拿出来放到 options.type 中。

  1. /** @see {@link https://github.com/vuejs/vue-class-component/blob/master/src/reflect.ts} */
  2. var reflectMetadataIsSupported = typeof Reflect !== 'undefined' && typeof Reflect.getMetadata !== 'undefined';
  3. function applyMetadata(options, target, key) {
  4. if (reflectMetadataIsSupported) {
  5. if (!Array.isArray(options) &&
  6. typeof options !== 'function' &&
  7. typeof options.type === 'undefined') {
  8. options.type = Reflect.getMetadata('design:type', target, key);
  9. }
  10. }
  11. }

Reflect.getMetadata 获取设置在类装饰器上的元数据。可参考文章理解:

  • Decorators
  • TypeScript:理解 Reflect Metadata
  • JavaScript Reflect Metadata 详解

createDecorator,见名知义,就是创建装饰器。本质是在类上定义一个私有属性

  1. export function createDecorator(factory) {
  2. return function (target, key, index) {
  3. var Ctor = typeof target === 'function'
  4. ? target
  5. : target.constructor;
  6. if (!Ctor.__decorators__) {
  7. Ctor.__decorators__ = [];
  8. }
  9. if (typeof index !== 'number') {
  10. index = undefined;
  11. }
  12. Ctor.__decorators__.push(function (options) { return factory(options, key, index); });
  13. };
  14. }

项目代理及 webpack 性能优化

在项目根目录下新建 vue.config.js

本地开发 api 代理
  1. module.exports = {
  2. devServer: {
  3. proxy: {
  4. '/api': {
  5. target: '<url>',
  6. changeOrigin: true,
  7. pathRewrite: {
  8. '^/api': ''
  9. }
  10. }
  11. }
  12. }
  13. }
本地开发 api 模拟
  1. devServer: {
  2. before (app) {
  3. before (app) {
  4. app.get('/api/getList', (req, res) => {
  5. res.json({data: [{id: 1, name: 'vue'}]})
  6. })
  7. }
  8. }
  9. }
性能优化

查看打包依赖

在 package.json 文件 script 中加入命令:

"build:report": "vue-cli-service build --report"

会在 dist 目录下生成 report.html,可直接打开,查看打包依赖,进行分析,进行打包优化

打包优化 - cdn 引入公共库

在 vue.config.js 中加入配置:

  1. configureWebpack: {
  2. externals: { // cdn 外链,避免包太大,首屏优化
  3. 'vue': 'Vue',
  4. 'vue-router': 'VueRouter',
  5. 'vuex': 'Vuex'
  6. }
  7. }

在 public/index.html 中加入 cdn 库地址

  1. <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
  2. <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js"></script>
  3. <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js"></script>
  4. <!-- built files will be auto injected -->

再次优化,html head 信息中加,dns 域名预解析,js 库 reload 预加载。

  1. <link rel="dns-prefetch" href="cdnjs.cloudflare.com" >
  2. <link href="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" rel="preload" as="script">
  3. <link href="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js" rel="preload" as="script">
  4. <link href="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js" rel="preload" as="script">

其他

修改本地开发端口号,在 vue.config.js 中加入配置:

  1. devServer: {
  2. port: 8888
  3. }

体验优化-打包完成提示:

  1. const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
  2. const path = require('path');
  3. module.exports = {
  4. // 链式操作
  5. chainWebpack: config => {
  6. // 移除 prefetch 插件,移动端对带宽敏感
  7. // 路由懒加载,只对用户频繁操作的路由,通过 注释 提前获取
  8. // component: () => import(/* webpackChunkName: "about" */ /* webpackPrefetch: true */'../views/About.vue')
  9. config.plugins.delete('prefetch');
  10. // 生产打包才提示,开发不提示
  11. if (process.env.NODE_ENV === 'production') {
  12. config.plugin('build-notify').use(WebpackBuildNotifierPlugin, [{
  13. title: "My Project Webpack Build",
  14. logo: path.resolve("./img/favicon.png"),
  15. suppressSuccess: true
  16. }])
  17. }
  18. }
  19. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/320693
推荐阅读
相关标签
  

闽ICP备14008679号