Vue CLI 是什麼
基於 Webpack 所建置的開發工具
便於使用各種第三方套件 (BS4、Vue Router、Axios、…)
可運行 Sass 、Bebel 、TypeScript 等編譯工具
便於開發 SPA 的網頁工具
webpack 它是一個「打包工具」。將眾多模組與資源載入並編譯我們需要預先處理的內容,變成瀏覽器看得懂的東西,讓我們可以上傳到伺服器。
SPA (singal-page application)
是一種網路應用程式或網站的模型,通過動態重寫目前頁面來與用戶互動,而非傳統的從伺服器重新載入整個新頁面。這種方法避免了頁面之間切換打斷用戶體驗,使應用程式更像一個桌面應用程式。
使用 Vue CLI
先安裝 Node.js
Vue CLI 2.x GITHUB
使用前須先確認:Node.js (>=6.x, 8.x preferred), npm version 3+ and Git.
全域安裝 Vue Cli 2.x
檢視 Vue Cli 可用版型
Vue 建立模板
此處使用webpack
安裝前設定
?Project name ?Project description (A Vue.js project) ?Author ?Install vue-router? (Y/n) ?Use ESLint to lint your code? (Y/n) ?Set up unit tests (Y/n) ?Setup e2e tests with Nightwatch? (Y/n) ?Should we run `npm install` for you after the project has been created? (recommended) (Use arrow keys) Yes, use NPM Yes, use Yarn >No, I will handle that myself
開始安裝 (進入目標資料夾下,每個專案只有第一次需要執行)
運行 Vue + Webpack
編譯 Production 版本
Vue CLI 3.x
Vue CLI 需要 Node.js v8.9 以上的版本
全域安裝 Vue Cli 3.x npm install -g @vue/cli yarn global add @vue/cli
Vue 建立模板 vue create my-project vue ui
安裝前設定
Vue CLI v3.4.0 ? 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 ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y ? 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 ? 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 ? Save this as a preset for future projects? (y/N) N
運行 Vue + Webpack
編譯 Production 版本
資料結構說明
├── 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-template
、javascript
、style
。
執行 npm run build
會將網頁編譯出並放置於 dist
資料夾中,網頁需執行在 server 端中。
~
代表 node_modules
路徑
@
代表 src
路徑
hello-world/ │ ├── dist/ │ ├── node_modules/ │ ├── public/ │ │ │ ├── favicon.ico │ └── index.html │ ├── src/ │ │ │ ├── assets/ │ ├── components/ │ ├── views/ │ ├── App.vue │ ├── main.js │ ├── router.js │ └── store.js │ └── package.json
自定義環境變數
config/ ├── dev.env.js # 開發環境的環境變數 ├── index.js # 針對運行環境的設定 ├── prod.env.js # 生產環境的環境變數
編寫環境變數
載入環境變數
建立一個 .env.xxx
檔案 src/ ├─ .env.development ├─ .env.production
.env .env.local .env.[mode] .env.[mode].local
一個環境文件只包含環境變量的“鍵=值”
載入環境變數 process.env.VUE_APP_SECRET
vue.config.js配置簡介
vue-cli3.0 構建專案目錄中沒有 build
和 config
目錄,新增自定義配置需要在專案跟目錄中新增 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' )) }
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 模塊。
Type: boolean | Object
Default: 生產環境下是 true
,開發環境下是 false
是否將組件中的 CSS 提取至一個獨立的 CSS 文件中 (而不是動態注入到 JavaScript 中的 inline 代碼)。
css.loaderOptions
Type: Object
Default: {}
向 CSS 相關的 loader 傳遞選項。例如:
loaderOptions: { css: { }, postcss: { scss: { data: ` $baseUrl: "/"; @import '@/assets/scss/_common.scss'; ` prependData: `@import "~@/variables.scss";` } } }
支持的 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
如果你的前端應用和後端 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,可以使用以下選項: type
, default
, required
, validator
。 Constructor[],指定 prop 的複數型別。 Constructor,例如 String,Number,Boolean 等,指定 prop 的型別。 屬性後面需要加上 undefined
類型;或者在屬性名後面加上!
,表示非 null
和 非 undefined
的斷言,否則編譯器會給出錯誤提示
!
: 表示一定存在,?
: 表示可能不存在。此兩種語法稱為賦值斷言。
export default { props: { propA: String , 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 包含兩種屬性immediate?:boolean
- 監聽開始之後是否立即調用該 callback functiondeep?: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) { } else { } }) } }