【Vue 入門】使用 Vue2 開發一個展示項目列表的應用

前言

一直沒有找到一個合適的展示個人項目的模板,所以自己動手使用 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 給父組件發送消息,下面示意圖:

圖片來自 https://github.com/webplus/blog/issues/10

父組件向子組件傳遞數據

通過使用 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

子組件向父組件通信

如果子組件需要把信息傳遞給父組件,可以使用自定義事件:

  1. 使用 $on(eventName) 監聽事件
  2. 使用 $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 實例作爲中央事件總線,通過自定義事件的監聽和觸發,來完成通信功能,下面是一個示意圖:

圖片來自 https://github.com/webplus/blog/issues/10

下面我們來看一個具體的實例:

  • 首先定義一個空的 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

參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章