前言
一直沒有找到一個合適的展示個人項目的模板,所以自己動手使用 Vue 寫了一個。該模板基於 Markdown 文件進行配置,只需要按一定規則編寫 Markdown 文件,然後使用一個 在線工具 轉爲 JSON 文件即可。下面是該項目的在線地址和源碼。本文主要記錄一下項目中用到的相關知識。
效果
程序最終的效果如下圖所示:
整個項目只包含兩個組件:項目介紹 和 側邊導航,邏輯比較簡單,十分適合入門。
環境配置
這裏我們使用 Gulp 和 Webpack 用作項目構建工具。初次使用 Gulp 和 Webpack 可能不太適應,因爲它們的配置可能讓你看的一頭霧水。不過不用擔心,這兩個畢竟只是一個工具,在初始時沒有必要特別的瞭解它們的工作原理,只要能運行起來就可以。等到使用了一段時間之後,自然而然的就知道該如何配置了。這裏主要記錄一下項目中使用的配置,如果想要系統的學習如何使用這兩個工具,可以參考下面的文章:
Gulp 和 Webpack 集成
Gulp 和 Webpack 集成一個比較簡單的方式就是將 Webpack 作爲 Gulp 的一個 task,如下面的形式:
var gulp = require("gulp");
var webpack = require("webpack");
gulp.task("webpack", function (callback) {
//webpack配置文件
var config = {
watch: true,
entry: {
index: __dirname + '/src/js/index.js'
},
output: {
path: __dirname + '/dist/js',
filename: '[name].js'
}
//........
};
webpack(config, function (err, stats) {
console.log(stats.toString());
});
});
gulp.task('default', [ 'webpack']);
下面我們分別介紹一下 gulp 和 webpack 的配置
Gulp 配置
Gulp 中主要配置了兩個任務:webpack 和 browserSync,這裏主要說一下 browserSync。browserSync 主要用來自動刷新瀏覽器。首先我們配置需要監聽的文件,當這些文件發生改變後,調用 browserSync 使瀏覽器自動刷新頁面。下面是具體的配置
var gulp = require("gulp");
var browserSync = require('browser-sync');
// 添加 browserSync 任務
gulp.task('browserSync', function () {
browserSync({
server: {
baseDir: '.'
},
port: 80
})
});
// 配置需要監聽的文件,當這些文件發生變化之後
// 將調用 browserSync.reload 使瀏覽器自動刷新
gulp.task("watch", function () {
gulp.watch("./**/*.html", browserSync.reload);
gulp.watch("dist/**/*.js", browserSync.reload);
gulp.watch("dist/**/*.css", browserSync.reload);
});
// 添加到默認任務
gulp.task('default', ['browserSync', 'watch', 'webpack']);
Webpack 配置
我們使用 webpack 進行資源打包的工作,就是說將各種資源(css、js、圖片等)交給 Webpack 進行管理,它會將資源整合壓縮,我們在頁面中只需引用壓縮之後的文件即可。webpack 的基礎配置文件如下所示
gulp.task("webpack", function (callback) {
//webpack配置文件
var config = {
// true 表示 監聽文件的變化
watch: true,
// 加載的插件項
plugins: [
new ExtractTextPlugin("../css/[name].css")
],
// 入口文件配置
entry: {
index: __dirname + '/src/js/index.js'
},
// 輸出文件配置
output: {
path: __dirname + '/dist/js',
filename: '[name].js'
},
module: {
// 加載器配置,它告訴 Webpack 每一種文件需要採用什麼加載器來處理,
// 只有配置好了加載器才能處理相關的文件。
// test 用來測試是什麼文件,loader 表示對應的加載器
loaders: [
{test: /\.vue$/, loader: 'vue-loader'}
]
},
resolve: {
// 模塊別名定義,方便後續直接引用別名,無須多寫長長的地址
// 例如下面的示例,使用時只需要寫 import Vue from "vue"
alias: {
vue: path.join(__dirname, "/node_modules/vue/dist/vue.min.js")
},
// 自動擴展文件後綴名,在引入文件時只需寫文件名,而不用寫後綴
extensions: ['.js', '.json', '.less', '.vue']
}
};
webpack(config, function (err, stats) {
console.log(stats.toString());
});
});
webpack 的相關配置說明可以參考前面的給出的文章,下面說一下使用 webpack 2 遇到的坑:
extract-text-webpack-plugin
extract-text-webpack-plugin 會將 css 樣式打包成一個獨立的 css 文件,而不是直接將樣式打包到 js 文件中。下面是使用方法
{
plugins: [new ExtractTextPlugin("../css/[name].css")],
module: {
loaders: [{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader!less-loader"
})
}
},
}
這裏需要注意的地方就是,extract-text-webpack-plugin 在 webpack 1 和 webapck 2 中的安裝方式不同,需要根據使用的 webpack 版本來安裝:
# for webpack 1
npm install --save-dev extract-text-webpack-plugin
# for webpack 2
npm install --save-dev extract-text-webpack-plugin@beta
壓縮文件
使用 UglifyJsPlugin 插件可以壓縮 css 和 js 文件,但是一開始時總是無法壓縮文件,後來查閱了一下資料,大概是因爲下面幾個原因:
1. uglifyjs-webpack-plugin 依賴於 uglify-js,而 uglify-js 默認不支持 ES6 語法,所以需要安裝支持 ES6 語法的 uglify-js
npm install mishoo/UglifyJS2#harmony --save
2. webpack 2 中,UglifyJsPlugin 默認不壓縮 loaders,如果要啓動 loaders 壓縮,需要加入下面的配置:
js
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true
})
]
如果按上面的修改了還是不能壓縮文件,可以試着將 node_modules 刪除,然後重新安裝依賴。
Vue
本部分主要記錄一下程序中用到的 Vue 語法,如果想要系統的學習一下 Vue.js,可以參考下面的文章:
HelloWorld
我們首先來看一個最簡單的 Vue 示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Demo</title>
</head>
<body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
</body>
</html>
每個 Vue 應用都會創建一個 Vue 的根實例,在根實例中需要傳入 html 標籤的 id,用來告訴 Vue 該標籤中的內容需要被 Vue 來解析。上面是一個簡單的數據綁定的示例,在運行實 {{ message }} 會被解析爲 “Hello Vue!”。
基礎
本節參考自 Vue 中文文檔,略有修改
在寫 Vue 應用之前,我們要熟悉一下 Vue 的基本語法,主要包括數據綁定、事件處理、條件、循環等,下面我們依次看下相關的知識。
數據綁定
Vue.js 使用了基於 HTML 的模版語法,允許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循規範的瀏覽器和 HTML 解析器解析。下面是 Vue.js 數據綁定的相關語法:
文本
數據綁定最常見的形式就是使用 “Muestache” 語法(雙大括號),如下面的形式:<span>Message: {{ msg }} </span>
Muestache 標籤會被解析爲對應對象上的 msg 屬性值。當 msg 屬性發生改變之後,Muestache 標籤處解析的內容也會隨着更新。
通過使用
v-once
指令,我們可以執行一次性解析,即數據改變時,解析的內容不會隨着更新。需要注意的是v-once
會影響該節點上的所有數據綁定<span v-once>This will never change: {{ msg }}</span>
- Raw HTML
不論屬性值是什麼內容,Muestache 標籤裏的內容都會被解析爲純文本。如果希望將綁定的值解析爲 HTML 格式,就需要使用v-html
指令:
html
<div v-html="variable"></div>
- 屬性值
Mustache 語法不能用在 HTML 的屬性中,如果想爲屬性綁定變量,需要使用v-bind
指令:
html
<div v-bind:id="dynamicId"></div>
假設dynamicId=1
,那麼上面代碼就會被解析爲
html
<div id="1"></div>
另外v-bind
指令可以被縮寫爲:
,所以我們在程序中經常看到的是下面的語法形式:
html
<div :id="dynamicId"></div>
<!-- 等價於 -->
<div v-bind:id="dynamicId"></div>
表達式
對於所有的數據綁定, Vue.js 都提供了完全的 JavaScript 表達式支持,如下面的形式:// 加法 {{ number + 1 }} // 三元表達式 {{ ok ? 'YES' : 'NO' }} // JS 庫函數 {{ message.split('').reverse().join('') }} // 指令中使用表達式 <div v-bind:id="'list-' + id"></div>
事件處理
通過使用 v-on
指令可以監聽 DOM 事件來觸發 JS 處理函數,下面是一個完整的示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Demo</title>
</head>
<body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<button v-on:click="increase">增加 1</button>
<p>這個按鈕被點擊了 {{ counter }} 次。</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
increase: function() {
this.counter++;
}
}
})
</script>
</body>
</html>
通常情況下,v-on
會被簡寫爲 @
,所以我們在程序中一般是看到下面的形式
<button @click="increase">增加 1</button>
<!-- 等價於 -->
<button v-on:click="increase">增加 1</button>
條件指令 v-if
通過 v-if 指令我們可以根據某些條件來決定是否渲染內容,如下面的形式
<h1 v-if="ok">Yes</h1>
我們通常將 v-if 和 v-else 結合起來使用,如下所示:
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
在 Vue 2.1.0 中新增了一個 v-else-if 指令,可以進行鏈式判斷:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
循環指令 v-for
通過 v-for
指令,我們可以根據一組數據進行迭代渲染,下面是一個基本示例:
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{message: 'Foo' },
{message: 'Bar' }
]
}
})
上面是一個簡單的對數組迭代的示例,我們還可以針對對象進行迭代,如果只使用一個參數,就是針對對象的屬性值進行迭代:
<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
如果傳入第二個參數,就是針對對象的屬性值以及屬性名進行迭代,注意這裏二個參數表示的是屬性名,也就是 key
<div v-for="(value, key) in object">
{{ key }} : {{ value }}
</div>
如果再傳入第三個參數,第三個參數就表示索引
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }} : {{ value }}
</div>
組件
組件是 Vue.js 最強大的功能之一。組件可以擴展 HTML元素,封裝可重用的代碼。在我們的程序中包含兩個組件:project 組件和 sidebar 組件,如下圖所示。這裏我們主要介紹單文件組件的使用,即將組件用到 html、js 和 css 都寫在一個文件裏,每個組件自成一個系統。
文件結構
單文件組件一般使用 “.vue” 作爲後綴名,一般的文件結構如下所示:
project.vue
<template>
<div>
{{ key }}
</div>
</template>
<script>
export default {
data: function() {
return {
"key": "value"
}
},
methods: {
demoMethod: function() {
}
}
}
</script>
<style lang="less">
@import "xxx.less";
</style>
export 將模塊輸出,default 表明使用文件名作爲模塊輸出名,這就類似於將模塊在系統中註冊一下,然後其他模塊纔可用使用 import 引用該模塊。
然後我們需要在主文件中註冊該組件:
index.js
import project from '../components/project/project.vue'
Vue.component("project", project);
當註冊完成之後,就可以 html 中使用該組件了
index.html
<project></project>
生命週期
Vue 的要給組件會經歷 創建 -> 編譯 -> 掛載 -> 卸載 -> 銷燬 等一系列事件,這些事件發生的前後都會觸發一個相關的鉤子(hook)函數,通過這些鉤子函數,我們可以在事件發生的前後做一些操作,下面先看下官方給出的一個 Vue 對象的生命週期圖,其中紅框內標出的就是對應的鉤子函數
下面是關於這些鉤子函數的解釋:
hook | 描述 |
---|---|
beforeCreate | 組件實例剛被創建,組件屬性計算之前 |
created | 組件實例創建完成,屬性已綁定,但是 DOM 還未生成, $el 屬性還不存在 |
beforeMount | 模板編譯/掛載之前 |
mounted | 模板編譯/掛載之後 |
mounted | 模板編譯/掛載之後(不保證組件已在 document 中) |
beforeUpdate | 組件更新之前 |
updated | 組件更新之後 |
activated | for keep-alive ,組件被激活時調用 |
deactivated | for keep-alive ,組件被移除時調用 |
beforeDestory | 組件銷燬前調用 |
destoryed | 組件銷燬後調用 |
下面是鉤子函數的使用方法:
export default {
created: function() {
console.log("component created");
},
data {},
methods: {}
}
父子組件通信
父子組件通信可以使用 props down 和 events up 來描述,父組件通過 props 向下傳遞數據給子組件,子組件通過 events 給父組件發送消息,下面示意圖:
父組件向子組件傳遞數據
通過使用 props,父組件可以把數據傳遞給子組件,這種傳遞是單向的,當父組件的屬性發生變化時,會傳遞給子組件,但是不會反過來。下面是一個示例
comp.vue
<template>
<span>{{ message }}{{ shortMsg }}</span>
</template>
<script>
export default {
props: ["message", "shortMsg"],
}
</script>
index.html
<div id="app">
<!-- 在這裏將信息傳遞給子組件,:message 表示子組件中的變量名 -->
<comp :message="hello" :short-msg = "hi"></comp>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
"hello": "Hello",
"hi": "Hi"
}
})
</script>
在上面的流程中,父組件首先將要傳遞的數據綁定到子組件的屬性上,然後子組件在 props 中聲明與綁定屬性相同的變量名,就可以使用該變量了,需要注意的一點是如果變量採用駝峯的命名方式,在綁定屬性時,就要將駝峯格式改爲 -
連接的形式,如果上面所示 shortMsg
-> short-msg
。
子組件向父組件通信
如果子組件需要把信息傳遞給父組件,可以使用自定義事件:
- 使用 $on(eventName) 監聽事件
- 使用 $emit(eventName) 觸發事件
下面是一個示例:
comp.vue
<script>
export default {
methods: {
noticeParent: function() {
// 事件名,傳輸值
this.$emit('child_change', "value");
}
}
}
</script>
index.html
<div id="app">
<comp @child_change="childChange"></comp>
</div>
<script>
var app = new Vue({
el: '#app',
methods: {
childChange: function(msg) {
console.log("child change", msg);
}
}
});
</script>
在上面的代碼中,父組件通過 v-on
綁定了 child_chagne 事件,當 child_chagne 事件被觸發時候就會調用 childChange 方法。在子組件中可以通過 $emit
觸發 child_change 事件。這裏需要注意的是事件名不用採用駝峯命名,也不要用 -
字符,可以使用下劃線 _
連接單詞。
Event Bus 通信
Event Bus 通信模式是一種更加通用的通信方式,它既可以用於父子組件也可以用於非父子組件。它的原理就是使用一個空的 Vue 實例作爲中央事件總線,通過自定義事件的監聽和觸發,來完成通信功能,下面是一個示意圖:
下面我們來看一個具體的實例:
首先定義一個空的 Vue 實例,作爲事件總線
EventBus.js
import Vue from 'vue' export default new Vue()
在組件一中針對某個事件進行監聽
comp1.vue
<script> import eventBus from "EventBus.js" export default { created: function() { eventBus.$on("change", function() { console.log("change"); }) } } </script>
在組件二中觸發相應事件完成通信
comp2.vue
<script> import eventBus from "EventBus.js" export default { methods: { notice: function() { this.$emit('change', "value"); } } } </script>
ES6
本節摘自 ECMAScript 6 入門
與 ES5 相比,ES6 提供了更加完善的功能和語法,程序中我們使用部分 ES6 語法,這裏做一個簡單的記錄,如果想要系統的學習 ES6,可以參考下面的文章:
let
ES6 新增了 let 命令,用於聲明變量。使用 let 聲明的變量具有塊級作用域,所以在聲明變量時,應該使用 let,而不是 var。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for of 循環
ES6 借鑑 C++、Java、C# 和 Python 語言,引入了for…of循環,作爲遍歷所有數據結構的統一的方法
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v); // red green blue
}
Set 和 Map
ES6 引入了 Set 和 Map 結構。下面是兩者的具體介紹
Set
屬性
屬性 | 描述 |
---|---|
Set.prototype.size | 返回Set實例的成員總數。 |
方法
方法名 | 描述 |
---|---|
add(value) | 添加某個值,返回Set結構本身。 |
delete(value) | 刪除某個值,返回一個布爾值,表示刪除是否成功。 |
has(value) | 返回一個布爾值,表示該值是否爲Set的成員。 |
clear() | 清除所有成員,沒有返回值。 |
keys() | 返回鍵名的遍歷器 |
values() | 返回鍵值的遍歷器 |
entries() | 返回鍵值對的遍歷器 |
forEach() | 使用回調函數遍歷每個成員 |
使用示例:
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
Map
屬性
屬性 | 描述 |
---|---|
Map.prototype.size | 返回 Map 實例的成員總數。 |
方法
方法名 | 描述 |
---|---|
set(key, value) | set方法設置鍵名key對應的鍵值爲value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。 |
get(key) | 讀取 key 對應的鍵值,如果找不到 key,返回 undefined。 |
has(key) | 返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。 |
delete(key) | 刪除某個鍵,返回true。如果刪除失敗,返回false。 |
clear() | 清除所有成員,沒有返回值。 |
keys() | 返回鍵名的遍歷器 |
values() | 返回鍵值的遍歷器 |
entries() | 返回所有成員的遍歷器 |
forEach() | 遍歷 Map 的所有成員。 |
使用示例:
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false