0%

VueCLI

Vue CLI 是什麼

  1. 基於 Webpack 所建置的開發工具
  2. 便於使用各種第三方套件(BS4、Vue Router、Axios、…)
  3. 可運行 SassBebelTypeScript 等編譯工具
  4. 便於開發 SPA 的網頁工具
  • 不便開發非 SPA 的網頁

webpack 它是一個「打包工具」。將眾多模組與資源載入並編譯我們需要預先處理的內容,變成瀏覽器看得懂的東西,讓我們可以上傳到伺服器。

SPA (singal-page application)

是一種網路應用程式或網站的模型,通過動態重寫目前頁面來與用戶互動,而非傳統的從伺服器重新載入整個新頁面。這種方法避免了頁面之間切換打斷用戶體驗,使應用程式更像一個桌面應用程式。

使用 Vue CLI

  1. 先安裝 Node.js

Vue CLI 2.x GITHUB

使用前須先確認:Node.js (>=6.x, 8.x preferred), npm version 3+ and Git.

  1. 全域安裝 Vue Cli 2.x

    npm install -g vue-cli
  2. 檢視 Vue Cli 可用版型

    vue list
  3. Vue 建立模板

    vue init <模板名稱> <專案名稱>
    此處使用webpack

    安裝前設定

    ?Project name                                       # 確認專案名稱
    ?Project description (A Vue.js project) # A Vue.js project
    ?Author # 作者(可空白)
    ?Install vue-router? (Y/n) # (N)安裝vue-router(後續可手動安裝)
    ?Use ESLint to lint your code? (Y/n) # (N)JSLint是一個靜態代碼分析工具,用於軟件開發,用於檢查JavaScript源代碼是否符合編碼規則
    ?Set up unit tests (Y/n) # (N)測試
    ?Setup e2e tests with Nightwatch? (Y/n) # (N)測試
    ?Should we run `npm install` for you after the project has been created? (recommended) (Use arrow keys)
    Yes, use NPM # 使用NPM工具安裝
    Yes, use Yarn # 使用Yarn工具安裝
    >No, I will handle that myself # 手動安裝
    開始安裝 (進入目標資料夾下,每個專案只有第一次需要執行)
    cd <專案名稱>
    npm install
  • 運行 Vue + Webpack
    npm run dev
  • 編譯 Production 版本
    npm run build

Vue CLI 3.x

Vue CLI 需要 Node.js v8.9 以上的版本

  1. 全域安裝 Vue Cli 3.x
    npm install -g @vue/cli
    # OR
    yarn global add @vue/cli
  2. Vue 建立模板
    vue create my-project
    # OR
    vue ui # GUI 介面
    安裝前設定
    Vue CLI v3.4.0
    # 選取要 preset (預先裝置) 的特性,defaule 包含了基本的 Babel + ESLint preset,這裡選 Manually 來手動選取需要的特性
    ? Please pick a preset:
    default (babel, eslint)
    ❯ Manually select features

    # 選取要安裝的特性
    ? Check the features needed for your project:
    ◉ Babel
    ◯ TypeScript
    ◯ Progressive Web App (PWA) Support
    ◉ Router
    ◉ Vuex
    ❯◉ CSS Pre-processors
    ◉ Linter / Formatter
    ◯ Unit Testing
    ◯ E2E Testing

    # Vue Router 的 mode (模式) 使用 'history' (另一種 mode 為 'hash',會在 URL 加上 '#' 符號)
    ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

    # CSS 預處理器
    ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
    ❯ Sass/SCSS (with dart-sass)
    Sass/SCSS (with node-sass)
    Less
    Stylus

    # ESLint 協助讓你寫的程式符合規範的輔助工具,區分嚴謹程度 (npm run serve 常錯誤很困擾,建議選用 ESLint + Prettier 即可)
    ? Pick a linter / formatter config:
    ESLint with error prevention only
    ESLint + Airbnb config
    ❯ ESLint + Standard config
    ESLint + Prettier

    # 代碼檢查時機
    ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ❯◉ Lint on save # 存檔時
    ◯ Lint and fix on commit (requires Git) # 提交更新時

    # 在哪為特性進行配置
    ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
    ❯ In dedicated config files
    In package.json

    # 是否將上述配置儲存到 preset 的 default (就是一開始那)
    ? Save this as a preset for future projects? (y/N) N
  • 運行 Vue + Webpack
    npm run serve
  • 編譯 Production 版本
    npm run buid

資料結構說明

├── build/             # webpack 配置檔案
├── config/ # webpack 參數設定檔
├── dist/ # 打包後可上線的檔案
├── node_modules/ # 存放套件的資料夾
├── src/ # 開發階段原始碼
├── static/ # 存放靜態檔案,是不會被編譯的檔案
├── .babelrc # babel 配置檔案
├── .editorconfig
├── .eslintignore
├── .eslintrc.js # ESLint 配置檔案
├── .gitignore
├── .postcssec.js
├── index.html # 首頁
├── package.json # node 配置檔案
└── README.md
  • package.json 內的 script ,分別記錄 npm run dev(開發環境), npm run build(正式環境) 的 dev 跟 build 的程式碼。
  • src 開發階段原始碼:
    • 目錄下 main.js 檔案是 webpack entry 是整個專案的入口點,也就是起始的檔案。
    • .vue 檔案裡分別包含 x-templatejavascriptstyle
    • 執行 npm run build 會將網頁編譯出並放置於 dist 資料夾中,網頁需執行在 server 端中。
  • ~ 代表 node_modules 路徑
  • @ 代表 src 路徑
hello-world/        # 專案目錄

├── dist/ # 使用 npm run build 構建完成的檔案

├── node_modules/ # 依賴的模組

├── public/ # 公共資源檔案,不做任何編譯壓縮處理,使用絕對路徑指定的檔案
│ │
│ ├── favicon.ico # icon 圖片
│ └── index.html # 網站入口

├── src/ # Vue 與前端的原始檔案
│ │
│ ├── assets/ # 公共資源檔案,與 public 不同的是,assets 會經由 webpacd 構建處理,使用相對路徑指定的檔案,如 LOGO 圖片
│ ├── components/ # Vue 公共組件
│ ├── views/ # Vue Router 頁面組件
│ ├── App.vue # 項目入口
│ ├── main.js # 項目的 entry
│ ├── router.js # 路由設定
│ └── store.js # Vuex 狀態管理

└── package.json # 相當於對整個項目的解釋說明文件

自定義環境變數

config/
├── dev.env.js # 開發環境的環境變數
├── index.js # 針對運行環境的設定
├── prod.env.js # 生產環境的環境變數
編寫環境變數
HTTP_PATH: '"變數的值"' # 需以單引號包覆雙引號,否則載入後會出錯
載入環境變數
process.env."變數名稱"
  1. 建立一個 .env.xxx 檔案
    src/
    ├─ .env.development #模式用於 vue-cli-service serve
    ├─ .env.production #模式用於 vue-cli-service build 和 vue-cli-service test:e2e
    .env                # 在所有的環境中被載入
    .env.local # 在所有的環境中被載入,但會被 git 忽略
    .env.[mode] # 只在指定的模式中被載入
    .env.[mode].local # 只在指定的模式中被載入,但會被 git 忽略
  2. 一個環境文件只包含環境變量的“鍵=值”
    # 只有以 VUE_APP_ 開頭的變量會被 webpack.DefinePlugin 靜態嵌入到客戶端側的包中。
    VUE_APP_SECRET=secret
  3. 載入環境變數
    process.env.VUE_APP_SECRET

vue.config.js配置簡介

vue-cli3.0 構建專案目錄中沒有 buildconfig 目錄,新增自定義配置需要在專案跟目錄中新增 vue.config.js
vue.config.js配置

  • 於根目錄下建立 vue.config.js 檔案。
const path = require('path');
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
lintOnSave: true,
// 選項
};

publicPath

  • Type: string
  • Default: '/'
    部署應用程式時的基本 URL。用法和 webpack 本身的 output.publicPath 一致,但是 Vue CLI 在一些其他地方也需要用到這個值。
    建議直接使用 publicPath 而不要直接修改 webpack 的 output.publicPath。
    module.exports = {
    publicPath: process.env.NODE_ENV === 'production'
    ? '/production-sub-path/'
    : '/'
    }

outputDir

  • Type: string
  • Default: 'dist'
    生成的生產環境構建文件的目錄。
    建議直接使用 outputDir 而不要修改 webpack 的 output.path

assetsDir

  • Type: string
  • Default: ''
    放置生成的靜態資源 (js、css、img、fonts) 的 (相對於 outputDir 的) 目錄。

chainWebpack

  • Type: Function
    是一個函數,會接收一個基於 webpack-chain 的 ChainableConfig 實例。允許對內部的 webpack 配置進行更細粒度的修改。

設定路徑別名 - alias

chainWebpack: (config) => {
config.resolve.alias
.set('@', resolve('src'))
.set('@assets',resolve('src/assets'))
// 按照此格式新增路徑別名 .set('', resolve(''))
}

Css相關配置

css.requireModuleExtension

  • Type: boolean
  • Default: true
css: {
requireModuleExtension: false
}

默認情況下,只有 *.module.[ext] 結尾的文件才會被視作 CSS Modules 模塊。

import sassStyles from './foo.module.scss'

設置為 false 後你就可以去掉文件名中的 .module 並將所有的 *.(css|scss|sass|less|styl(us)?) 文件視為 CSS Modules 模塊。

css.extract

  • Type: boolean | Object
  • Default: 生產環境下是 true,開發環境下是 false
    是否將組件中的 CSS 提取至一個獨立的 CSS 文件中 (而不是動態注入到 JavaScript 中的 inline 代碼)。

css.loaderOptions

  • Type: Object
  • Default: {}
    向 CSS 相關的 loader 傳遞選項。例如:
loaderOptions: {
css: {
// 這裡的選項會傳遞給 css-loader
},
postcss: {
// 這裡的選項會傳遞給 postcss-loader
scss: {
//設置css中引用文件的路徑,引入通用使用的scss文件(如包含的@mixin)
data: `
$baseUrl: "/";
@import '@/assets/scss/_common.scss';
`
//data: `
//$baseUrl: "/";
prependData: `@import "~@/variables.scss";`
// 向所有 Sass/Less 樣式傳入共享的全局變量
}
}
}

支持的 loader 有:

css-loader
postcss-loader
sass-loader
less-loader
stylus-loader

devServer

devServer: {
open: true,
host: '127.0.0.1',
port: 3000,
https: false,
hotOnly: false,
// 将任何未知请求 (没有匹配到静态文件的请求) 代理到该字段指向的地方
proxy: null,
before: app => {
}

devServer.proxy

  • Type: string | Object

如果你的前端應用和後端 API 服務器沒有運行在同一個主機上,你需要在開發環境下將 API 請求代理到 API 服務器。

跨來源資源共用(Cross-Origin Resource Sharing (CORS))

這個問題可以通過 vue.config.js 中的 devServer.proxy 選項來配置。

module.exports = {
devServer: {
proxy: {
'/api': {
target: '<url>',
ws: true,
changeOrigin: true
},
'/foo': {
target: '<other_url>'
}
}
}
}

Vue + TypeScript

VueCli 3.0 加入了 TypeScript 的支援,以下為使用 VueCli + TypeScript 的方法。
開始使用前先了解一下在 Vue 中使用 TypeScript 所使用的插件(plugin)

  • vue-class-component : vue-class-component 是一個 Class Decorator ,也就是 Class 的裝飾器。
  • vue-property-decorator : vue-property-decorator 是基於 vue-class-component 所做的插件。
    import { Vue, Component, Inject, Provide, Prop, Model, Watch, Emit, Mixins } from 'vue-property-decorator'
  • vuex-module-decorators : 使用於 vuex 的插件。
    import { Module, VuexModule, Mutation, Action, MutationAction, getModule } from 'vuex-module-decorators'

vue-property-decorator使用指南

vue-property-decorator 是在 vue-class-component 上增強了更多的结合 Vue 特性的裝飾器,新增了多種裝飾器:

  • @Component (從 vue-class-component 繼承)
  • @Prop
  • @Model
  • @Emit
  • @Watch
  • @Inject
  • @Provide
    並透過裝飾器將代碼扁平化。

vue-property-decorator

@component 元件

import {componentA,componentB} from '@/components';

export default{
components:{
componentA,
componentB,
},
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus()
}
}
}
}
import {Component,Vue} from 'vue-property-decorator';
import {componentA,componentB} from '@/components';

@Component({
components:{
componentA,
componentB,
},
directives: {
focus: {
// 指令的定義
inserted: function (el) {
el.focus()
}
}
}
})
export default class YourCompoent extends Vue{

}

data 物件

import { Component, Vue } from 'vue-property-decorator';

@Component
export default class Test extends Vue {
private name: string;
}

@Prop 元件之間的資料傳遞

@Prop(options: (PropOptions | Constructor[] | Constructor) = {})

  • PropOptions,可以使用以下選項: typedefaultrequiredvalidator
  • Constructor[],指定 prop 的複數型別。
  • Constructor,例如 String,Number,Boolean 等,指定 prop 的型別。

屬性後面需要加上 undefined 類型;或者在屬性名後面加上!,表示非 null 和 非 undefined 的斷言,否則編譯器會給出錯誤提示

  • !: 表示一定存在,?: 表示可能不存在。此兩種語法稱為賦值斷言。
export default {
props: {
propA: String, // propA:Number
propB: [String, Number],
propC: {
type: Array,
default: () => {
return ['a', 'b']
},
required: true,
validator: (value) => {
return ['a', 'b'].indexOf(value) !== -1
}
}
}
}
import { Component, Vue, Prop } from 'vue-property-decorator';

@Component
export default class YourComponent extends Vue {
@Prop(String) propA: string | undefined
@Prop([String, Number]) propB!: string | number
@Prop({
type: String,
default: 'default value',
required: true,
validator: (value) => {
return ['InProcess', 'Settled'].indexOf(value) !== -1
}
})
propC!: string
}

@PropSync

@PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})
@PropSync 裝飾器與 @prop 用法類似,差別在於:

  • @PropSync 裝飾器接收兩個參數
    • propName: string  表示父元件傳遞過來的屬性名
    • options: Constructor | Constructor[] | PropOptions 與@Prop的第一個參數一致
  • @PropSync 會生成一個新的計算屬性
  • @PropSync 需要配合父元件的 .sync 修飾符使用
export default {
props: {
propA: {
type: String,
default: 'abc'
}
},
computed: {
syncedPropA: {
get() {
return this.propA
},
set(value) {
this.$emit('update:propA', value)
}
}
}
}
import { Vue, Component, PropSync } from 'vue-property-decorator'

@Component
export default class MyComponent extends Vue {
@PropSync('propA', { type: String, default: 'abc' }) syncedPropA!: string
}

method

public clickFunc(): void {
console.log(this.name)
console.log(this.msg)
}

computed

public get allname() {
return 'computed ' + this.name;
}

@Model

v-model 其實是一種用單向綁定與事件而實現的雙向綁定語法。

@Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
@Model 裝飾器允許我們在一個元件上自定義 v-model ,接收兩個參數:

  • event: string 事件名稱。
  • options: Constructor | Constructor[] | PropOptions@Prop 的第一個參數一致。
  • 父元件
    <inputComponent v-model="checked" />
  • 子元件
    <input  type="checkbox" :checked="checked" @change="change">
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: {
type: Boolean
}
},
methods: {
change(e) {
this.$emit('change', e.target.checked)
}
}
}
import {Vue, Component, Model, Emit} from 'vue-property-decorator';

@Component
export default class YourComponent extends Vue {

@Model('change', {type: Boolean})
checked!: boolean;

@Emit('change')
change(e: MouseEvent) {
return (e.target as HTMLInputElement).checked
}
}

@Watch 監聽

@Watch(path: string, options: WatchOptions = {})

  • options 包含兩種屬性
    1. immediate?:boolean - 監聽開始之後是否立即調用該 callback function
    2. deep?:boolean - 被監聽的對象的屬性被改變時,是否調用該 callback function

偵聽開始,發生在 beforeCreate 鉤子之後, created 鉤子之前

export default {
watch: {
'person': {
handler: 'onPersonChanged',
immediate: true,
deep: true
}
},
methods: {
onPersonChanged(val, oldVal) {}
}
}
import {Vue, Component, Watch} from 'vue-property-decorator';

@Component
export default class YourComponent extends Vue{
@Watch('person', { immediate: true, deep: true })
onPersonChanged(val: Person, oldVal: Person) { }
}

生命周期函数

public created(): void {
console.log('created');
}

public mounted():void{
console.log('mounted')
}

@Emit

@Emit(event?: string)

  • @Emit 裝飾器接收一個可選參數,該參數是$Emit的第一個參數,充當事件名。如果沒有提供這個參數,$Emit會將callback function的camelCase轉為kebab-case,並將其作為事件名
  • @Emit 會將callback function的返回值作為第二個參數,如果返回值是一個Promise對象,$emit會在Promise對象被標記為resolved之後觸發
  • @Emit 的callback function的參數,會放在其返回值之後,一起被 $emit 當做參數使用
export default {
data() {
return {
count: 0
}
},
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
},
promise() {
const promise = new Promise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})
promise.then(value => {
this.$emit('promise', value)
})
}
}
}
import { Vue, Component, Emit } from 'vue-property-decorator'
@Component
export default class MyComponent extends Vue {
count = 0
@Emit()
addToCount(n: number) {
this.count += n
}
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue() {
return 10
}
@Emit()
onInputChange(e) {
return e.target.value
}
@Emit()
promise() {
return new Promise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})
}
}

@Ref

@Ref(refKey?: string)
@Ref 裝飾器接收一個可選參數,用來指向元素或子元件的引用信息。如果沒有提供這個參數,會使用裝飾器後面的屬性名充當參數。

export default {
computed: {
loginForm: {
cache: false,
get() {
return this.$refs.loginForm
}
},
passwordForm: {
cache: false,
get() {
return this.$refs.changePasswordForm
}
}
}
}
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'
@Componentexport default class MyComponent extends Vue {
@Ref() readonly loginForm!: Form
@Ref('changePasswordForm') readonly passwordForm!: Form

public handleLogin() {
this.loginForm.validate(valide => {
if (valide) {
// login...
} else {
// error tips
}
})
}
}