如何用vue-cli3腳手架搭建一個基于ts的基礎(chǔ)腳手架的方法
忙里偷閑,整理了一下關(guān)于如何借助 vue-cli3 搭建 ts + 裝飾器 的腳手架,并如何自定義 webpack 配置,優(yōu)化。
準(zhǔn)備工作
- @vue/cli@4.1.1
- vue 2.6
- node v12.13.0
安裝 node
- 安裝 node
- 全局安裝 nrm,npm 的鏡像源管理工具。
npm i nrm -g // 安裝 nrm ls // 查看可用源,及當(dāng)前源,帶*的是當(dāng)前使用的源 nrm use taobao // 切換源,使用源 nrm add <registry> <url> // 其中reigstry為源名,url為源的路徑 nrm del <registry> // 刪除對應(yīng)的源 nrm test npm // 測試源的響應(yīng)速度
安裝 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 // 更新全局安裝的包
搭建項目
新建一個基于 ts 的 vue 項目
vue create vue-cli3-ts
備注:如果是 window 系統(tǒng),用 git bash 交互提示符(切換)不會工作,用以下命令,即可解決:
winpty vue.cmd create vue-cli3-ts
- 自定義選項 - Manually select features
- 添加 ts 支持 - TypeScript
- 基于類的組件 - y
- tslint
- 根據(jù)需要添加 router、vuex、css(less 或 scss) 預(yù)處理器、單元測試(jest)
交互說明:
- 上下箭頭鍵切換
- 空格鍵選中
- 回車確定
在已存在的項目中添加 ts
vue add @vue/typescript
會把所有 .js 更改為 .ts
script 命令
// - 啟動服務(wù) npm run serve // - 打包編譯 npm run build // - 執(zhí)行l(wèi)int npm run lint // - 執(zhí)行單元測試 npm run test:unit
npm run serve 啟動服務(wù):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. 類型注解,類型推論
- 變量后面通過 冒號+類型 來做類型注解。
- 編譯時類型檢查,寫代碼時代碼提醒。
- 類型推論,根據(jù)賦值類型推論出被賦值變量的類型,進行類型限制。
let title: string; // 類型注解 title = 'ts'; // 正確 title = 4; // 錯誤 let text = 'txt'; // 類型推論 text = 2; // 錯誤
錯誤時,vscode 編輯器會有紅色波浪號提示。
數(shù)組
let names: string[]; // Array<string> names = ['Tom'];
任意類型,沒有類型限制
let foo: any; foo = 'foo'; foo = 3; let list: any[]; list = [1, true, 'free']; list[1] = 100;
函數(shù)中使用類型
function greeting (person: string): string {
return 'Hello, ' + person;
}
// void 類型,常用于沒有返回值的函數(shù)
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 {
// 相當(dāng)于 data 中的數(shù)據(jù)項
features: string[];
constructor () {
super();
this.features = ['類型注解', '類型推論', '編譯型語言'];
}
// 相當(dāng)于 methods 中的方法
addFeature (event: any) {
console.log(event);
this.features.push(event.target.value);
event.target.value = '';
}
}
</script>
2.類
ts 中的類和 es6 中的大體相同,關(guān)注特性 訪問修飾符
- private 私有屬性,不能在類的外部訪問
- protected 保護屬性,可以在類的內(nèi)部和派生類的內(nèi)部訪問,不能在類的外部訪問
- public 公有屬性,可以在任意地方訪問,默認(rèn)值
- readonly 只讀屬性,必須在聲明時或構(gòu)造函數(shù)里初始化,不可改變值
構(gòu)造函數(shù):初始化成員變量,參數(shù)加上修飾符,能夠定義并初始化一個屬性
constructor (private name = 'Tom') {
super();
}
等同于
name: string;
constructor () {
super();
this.name = 'Tom';
}
存取器,暴露存取數(shù)據(jù)時可添加額外邏輯;在 vue 中可用作計算屬性
get fullName () { return this.name; }
set fullName (val) { this.name = val; }
案例:vue demo
<template>
<p>特性數(shù)量:{{count}}</p>
</template>
<script lang="ts">
export default class Demo extends Vue {
// 定義 getter 作為計算屬性
get count () {
return this.features.length;
}
}
</script>
接口
接口僅約束結(jié)構(gòu),不要求實現(xiàn)
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,聲明接口類型約束數(shù)據(jù)結(jié)構(gòu)
<template>
<li v-for="feature in features" :key="feature.id">{{feature.name}}</li>
</template>
<script lang="ts">
// 定義一個接口約束feature的數(shù)據(jù)結(jié)構(gòu)
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>
泛型
泛型 是指在定義函數(shù)、接口或類的時候,不預(yù)先指定具體的類,而是在使用時才指定類型的一種特性。
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};
}
// 調(diào)用
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});
}
// 調(diào)用 async 方式
async mounted () {
this.features = (await getData<Feature[]>()).data;
}
// 調(diào)用 then 方式
mouted () {
getData<Feature[]>().then((res: Result<Feature[]>) => {
this.features = res.data;
})
}
裝飾器
裝飾器用于擴展類或者它的屬性和方法。
屬性聲明:@Prop
除了在 @Component 中聲明,還可以采用@Prop的方式聲明組件屬性
export default class Demo extends Vue {
// Props() 參數(shù)是為 vue 提供屬性選項
// !稱為明確賦值斷言,它是提供給ts的
@Prop({type: String, require: true})
private msg!: string;
}
事件處理:@Emit
// 通知父類新增事件,若未指定事件名則函數(shù)名作為事件名(駝峰變中劃線分隔)
@Emit()
private addFeature(event: any) {// 若沒有返回值形參將作為事件參數(shù)
const feature = { name: event.target.value, id: this.features.length + 1 };
this.features.push(feature);
event.target.value = "";
return feature;// 若有返回值則返回值作為事件參數(shù)
}
template 模板組件上正常寫,@add-feature
變更監(jiān)測:@Watch
@Watch('msg')
onRouteChange(val:string, oldVal:any){
console.log(val, oldVal);
}
裝飾器原理
裝飾器本質(zhì)是工廠函數(shù),修改傳入的類、方法、屬性等
類裝飾器
// 類裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,類的構(gòu)造函數(shù)作為其唯一的參數(shù)。
function log(target: Function) {
// target是構(gòu)造函數(shù)
console.log(target === Foo); // true
target.prototype.log = function() {
console.log(this.bar);
}
// 如果類裝飾器返回一個值,它會使用提供的構(gòu)造函數(shù)來替換類的聲明。
}
@log
class Foo {
bar = 'bar'
}
const foo = new Foo();
// @ts-ignore
foo.log();
實戰(zhàn)一下 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 是核心,后續(xù)實現(xiàn)都依賴它,比如 Prop、Watch、Ref。
Prop 源碼實現(xiàn):
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 獲取設(shè)置在類裝飾器上的元數(shù)據(jù)??蓞⒖嘉恼吕斫猓?/p>
createDecorator,見名知義,就是創(chuàng)建裝飾器。本質(zhì)是在類上定義一個私有屬性
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 性能優(yōu)化
在項目根目錄下新建 vue.config.js
本地開發(fā) api 代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
本地開發(fā) api 模擬
devServer: {
before (app) {
before (app) {
app.get('/api/getList', (req, res) => {
res.json({data: [{id: 1, name: 'vue'}]})
})
}
}
}
性能優(yōu)化
查看打包依賴
在 package.json 文件 script 中加入命令:
"build:report": "vue-cli-service build --report"
會在 dist 目錄下生成 report.html,可直接打開,查看打包依賴,進行分析,進行打包優(yōu)化
打包優(yōu)化 - cdn 引入公共庫
在 vue.config.js 中加入配置:
configureWebpack: {
externals: { // cdn 外鏈,避免包太大,首屏優(yōu)化
'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 -->
再次優(yōu)化,html head 信息中加,dns 域名預(yù)解析,js 庫 reload 預(yù)加載。
<link rel="dns-prefetch" href="cdnjs.cloudflare.com" rel="external nofollow" > <link rel="preload" as="script"> <link rel="preload" as="script"> <link rel="preload" as="script">
其他
修改本地開發(fā)端口號,在 vue.config.js 中加入配置:
devServer: {
port: 8888
}
體驗優(yōu)化-打包完成提示:
const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
const path = require('path');
module.exports = {
// 鏈?zhǔn)讲僮?
chainWebpack: config => {
// 移除 prefetch 插件,移動端對帶寬敏感
// 路由懶加載,只對用戶頻繁操作的路由,通過 注釋 提前獲取
// component: () => import(/* webpackChunkName: "about" */ /* webpackPrefetch: true */'../views/About.vue')
config.plugins.delete('prefetch');
// 生產(chǎn)打包才提示,開發(fā)不提示
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
}])
}
}
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue頁面手動刷新,實現(xiàn)導(dǎo)航欄激活項還原到初始狀態(tài)
這篇文章主要介紹了Vue頁面手動刷新,實現(xiàn)導(dǎo)航欄激活項還原到初始狀態(tài),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
windows下vue-cli導(dǎo)入bootstrap樣式
這篇文章主要介紹了windows下vue-cli導(dǎo)入bootstrap樣式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
解決Vue控制臺報錯Failed to mount component: tem
這篇文章主要介紹了解決Vue控制臺報錯Failed to mount component: template or render function not defined.問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
vue中對象的賦值Object.assign({}, row)方式
這篇文章主要介紹了vue中對象的賦值Object.assign({}, row)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
vue3?element?plus按需引入最優(yōu)雅的用法實例
這篇文章主要給大家介紹了關(guān)于vue3?element?plus按需引入最優(yōu)雅的用法,以及關(guān)于Element-plus按需引入的一些坑,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2022-03-03

