序言
本文通過一個初熟的小栗子來實踐2.0與1.0的不同之處,最終的效果如下圖所示:
提示
- 可作為vue2.0+vuex入門參考
- 只貼上核心程式碼,看原始碼建議直接下載demo 其實只有sass沒有貼上啦
- demo在移動端親測可用
搭建專案
知識點
這個栗子將涉及以下知識點:
- vue2.0
- vue-cli腳手架
- vuex狀態管理庫
- webpack
版本宣告
本篇文章的版本:
- node: v6.2.0
- vue: v2.1.0
- vuex: v2.0.0
- vue-router: v2.1.1
- webpack: v1.13.2
檔案目錄
安裝
首先請確保已安裝了node、npm、webpack。
- 安裝vue腳手架。
1npm install vue-cli -g - 選擇一個目錄並執行。
1vue init webpack 專案名字 或者vue init webpack-simple 專案名字 <注意不能用中文>
然後根據命令列的相關提示輸入資訊;
webpack與webpack-simple兩者的區別在於webpack-simple沒有包括eslint等功能,普通的專案用simple就好了;
我使用的是前者。 - 進入專案目錄下載相關依賴。
12cd 專案目錄npm install - 啟動。
1npm run dev
這一行命令會自動啟動瀏覽器並執行專案(如果你不想佔用8080埠,可通過 專案 /config/index.js
中的port修改)。
初始化
初始化入口js檔案 main.js
main.js是應用入口檔案,可以在這裡
- 配置路由vue-router
- 引入路由子元件
- 引入狀態管理store(注入所有子元件)
- 例項化Vue
- 引入公共樣式等
已完成的main.js如下: 懶癌患者可直接拷貝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import Vue from 'vue' import store from './store' //引入路由及元件 import VueRouter from 'vue-router' import App from './App' import Home from './components/Home' import Clocklist from './components/Clocklist' //引入公共css import './static/css/reset.css' Vue.use(VueRouter) //定義路由 const routes = [ { path : '/', component : Home }, { path : '/home', component : Home }, { path : '/clocklist', component : Clocklist }, ] //建立例項 const router = new VueRouter({ routes }) //例項化,並將store、router掛載到根例項,從而應用到整個專案 new Vue({ store, router, ...App }).$mount('#app')//或者直接在options裡宣告掛載的el |
與1.0的不同
- 對映路由:1.0是通過router的map方法對映路由,並且map接收的是一個物件,2.0中map()被替換了,通過例項化VueRouter並定義一個陣列來對映路由;
- 初始化路由:1.0通過router.start()來初始化路由,2.0中router.start()被替換了,直接通過掛載到vue根例項進行初始化
初始化根元件App.vue
在App.vue中新增路由,並引入Sidebar.vue元件,對應的樣式直接寫在每個獨立的元件下,注意這裡使用了sass語法,需在 ./build/webpack.base.conf.js
中配置,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
vue: { loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), postcss: [ require('autoprefixer')({ browsers: ['last 2 versions'] }), require('postcss-import'), require('postcss-sass-extend'), require('postcss-simple-vars'), require('postcss-nested')//sass巢狀語法,其他的看最後一個單詞就知道是幹什麼的了 ] } |
下面附上整個App.vue的程式碼,注意看註釋掉的部分哦。 從這裡開始後面的sass不貼出來了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
<template> <!-- 不加會報錯 --> <div class="clock_wrap"> <!--S 頭部 --> <section class="clock_header"> <h1>計時器</h1> </section> <!--E 頭部 --> <section class="clock_container"> <!--S 導航 --> <nav class="clock_nav"> <router-link to="/home">首頁</router-link> <router-link to="/clocklist">計時列表</router-link> </nav> <!--E 導航 --> <!--S sidebar --> <div class="clock_sidebar"> <sidebar></sidebar> </div> <!--E sidebar --> <!--S 路由部分 --> <div class="clock_router"> <!-- <transition mode="out-in"> --> <transition :name="transitionName"> <router-view class="clock_router_inner"></router-view> </transition> </div> <!--E 路由部分 --> </section> </div> </template> <script> import Sidebar from './components/Sidebar.vue' export default { data:function(){ return { transitionName: 'slide-left' } }, mounted:function(){ if(this.$route.name==undefined){ this.$router.push('home'); } }, /*watch: { '$route' (to, from) { console.log(to.path) } },*/ components: { Sidebar } } </script> <style> @import './static/sass/_function.scss'; body { overflow: hidden; } .clock_header { height: 42px; line-height: 42px; background: #39383e; h1 { font-size: 18px; color: #fff; text-align: center; &::before { content: ''; width: 18px; height: 18px; display: inline-block; vertical-align: middle; margin: -2px 5px 0 5px; background: url(./static/img/logo.png) no-repeat; background-size: 100% 100%; } } } .clock_nav { height: 36px; line-height: 36px; background: #f0eff5; padding-left: 5px; font-size: 0; a { display: inline-block; padding: 0 10px; position: relative; font-size: 14px; &.router-link-active { color: $color_red; } &:not(:last-child) { &::after { content: ''; width: 1px; background: #e1e0e6; position: absolute; right: 0; top: 12px; bottom: 12px; } } } } .clock_container { @extend %clearfix; } .clock_sidebar { width: 30%; float: left; box-sizing: border-box; border: 1px solid #ddd; margin-top: 10px; } .clock_router { width: 70%; float: left; position: relative; &_inner { position: absolute; left: 0; right: 0; top: 0; transition: all .3s linear; } } .slide-left-enter{ opacity: 0; transform: translate3d(60px, 0, 0); } .slide-left-leave-active { opacity: 0; transform: translate3d(-60px, 0); } </style> |
這裡我將路由樣式設定為相對定位,路由的子元件設定為絕對定位,可以解決切換路由的時候頁面抖動問題。
與1.0的不同
- 根元素:在2.0中template下需要有一個根元素(clock_wrap),否則會報錯;
- 路由導航:在1.0中我們通過v-link來指定導航連結,在2.0中可以直接使用router-link元件來導航,在瀏覽器中渲染後是一個a標籤,並且會自動設定選中的class屬性值.router-link-active, 然後通過to 屬性指定連結;
- 過渡:在1.0中通過在目標元素(router-view)使用transition與transition-mode新增過渡,在2.0中,則改成了使用transition標籤包裹目標元素,可以自定義name過渡,也可以使用自帶的mode新增過渡動效(如mode=”out-in”),2.0中也支援通過$route設定基於路由的動態過渡;
- 鉤子:在1.0中的ready已經被mounted取代,此外2.0還新增了beforeMount、beforeUpdate、update等,下面是1.0與2.0生命週期示意圖
1.0
2.0
建立首頁Home.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<template> <div class="clock_time"> <!-- <div class="clock_time_inner" v-html = "time"> --> <div class="clock_time_inner"> <i>{{hour}}</i> <span>:</span> <i>{{minute}}</i> <span>:</span> <i>{{second}}</i> </div> <div class="clock_time_btn"> <span <a href="http://www.jobbole.com/members/pjclick">@click</a> = 'doClock' v-bind:id="clockId">開始計時</span> </div> </div> </template> <script> export default { data() { return { //time: '', hour: '', minute: '', second: '', clockId: 'clock_time' } }, mounted () { this.nowTime() }, methods: { nowTime () { const t = new Date(), h = t.getHours(), m = t.getMinutes(), s = t.getSeconds() //this.$data.time = '<i>' + h +'</i><span>:</span><i>' + m +'</i><span>:</span><i>' + s + '</i>' this.$data.hour = h this.$data.minute = m this.$data.second = s setTimeout(() => { this.nowTime() }, 1000) }, doClock () { const nowTime = new Date() //狀態 this.$store.dispatch('changeStatus') //時長 this.$store.dispatch('addDuration') //計時列表 this.$store.dispatch('saveClockList', nowTime) } } } </script> |
通過nowTime ()方法獲取當前的時間,doClock ()分別變更狀態、時長以及儲存計時記錄,後面會講到vuex部分。
與1.0的不同
- 資料繫結:與1.0一樣繫結資料的形式都使用“Mustache” 語法,但2.0不能在html屬性中使用了,比如栗子中的繫結id 的方法v-bind:id=”clockId”而不能直接使用
{{clockId}}
,否則會報錯;- 真實的html:1.0中輸出真實的html是使用三個大括號
{{{ }}}
,2.0之後需要使用v-html指令,如上面註釋掉的部分所示;
建立側邊欄Sidebar.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<template> <div class="clock_sidebar_inner"> <div class="clock_sidebar_item"> <span class="clock_sidebar_title">狀態</span> <span class="clock_sidebar_desc" :class = "{ 'green': status == '已計時', 'red': status == '已結束' }">{{ status }}</span> </div> <div class="clock_sidebar_item"> <span class="clock_sidebar_title">時長</span> <span class="clock_sidebar_desc">{{ duration }}</span> </div> </div> </template> <script> export default { computed: { status() { return this.$store.getters.getStatus }, duration() { return this.$store.getters.getDuration } } } </script> |
通過計算屬性computed去獲取狀態與時長。
繼續建立打卡列表Clocklist.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<template> <div class="clock_record"> <div class="clock_record_nothing" v-if = "!list.length">沒有記錄</div> <div class="clock_record_item" v-else = "list.length > 0" v-for = "(item, index) in list"> <div class="clock_record_name"><i>{{index + 1}}</i>{{ item.date }}</div> <div class="clock_record_desc">計時開始 {{ item.gotowork }}</div> <div class="clock_record_desc">計時結束 {{ item.gooffwork }}</div> </div> </div> </template> <script> export default { computed: { list () { return this.$store.getters.switchTime } } } </script> |
與1.0的不同
- v-else-if:在2.0中新增了v-else-if,類似於js中的else if,不能單獨使用,需跟在v-if之後;
- v-for:在使用v-for遍歷物件的時候,當存在index時,1.0的引數順序是(index, value),2.0變成了(value, index);
- v-for:1.0中,v-for塊內有一個隱性的特殊變數$index可以獲取當前陣列的索引,在2.0中移除了,改為了以上這種顯式的定義方式;
- key:key替代track-by
vuex部分
vuex是為vue.js設計的一個狀態管理模式,主要是用來儲存共享狀態、實現資料通訊,簡單理解就是統一管理和維護各個vue元件的狀態 ,它可以解決多層巢狀元件的傳參、兄弟元件的狀態傳遞等難題, 程式碼更結構化且容易維護。核心概念包括State、Getters、Mutations、Actions、Modules。
建立index.js
在src目錄新建store資料夾用來存放共享資料(vuex),然後新建index.js,用來初始化並匯出 store。 store已經在main.js中引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Vue from 'vue' import Vuex from 'vuex' import state from './state' import getters from './getters' import mutations from './mutations' import actions from './actions' Vue.use(Vuex) export default new Vuex.Store({ state, getters, mutations, actions strict: process.env.NODE_ENV !== 'production', //是否開啟嚴格模式 }) |
strict為是否開啟嚴格模式,在這種模式下任何狀態變更不是由Mutation函式觸發的都會報錯,但是為了避免效能損失,不要在釋出環境開啟嚴格模式;
在構建大型應用時,store物件會變的非常臃腫,Vuex允許將store分割為模組(module),每個模組有自己個State、Mutations、Actions、Getters。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) |
建立state.js
在store目錄下繼續建立state.js,程式碼如下
1 2 3 4 5 6 7 8 9 |
const state = { status: '已結束', duration: '0', timer: null, len: 0, clockList: [] } export default state |
Vuex使用一個state包含了全部的應用層級狀態–也就是一個單一狀態樹;
通常在計算屬性(computed)返回(檢測到資料發生變動時就會執行對相應資料有引用的函式),如下:
1 2 3 4 5 |
computed: { list () { return this.$store.state.status } } |
建立mutations.js
在store目錄下繼續建立mutations.js。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import * as types from './mutation-types' export default { [ types.CHANGE_STATUS ] ( state) { if( state.status === '已結束' ) { state.status = '已計時' }else if(state.status === '已計時') { state.status = '已結束' } }, [ types.ADD_DURATION ] ( state, obj ) { if( state.status === '已計時' ) { state.duration = obj.time state.timer = obj.timer }else { clearInterval(obj.timer) } }, [ types.SAVE_CLOCK_LIST] (state, nowTime) { if( state.status === '已計時' ) { console.log(state.clockList.length) state.len = state.clockList.length state.clockList.push({"gotowork": nowTime, 'gooffwork': ''}) } if( state.status === '已結束' ) { state.clockList[state.len].gooffwork = nowTime } } } |
mutations是註冊各種資料變化的方法,它接受state作為第一個引數,需注意以下幾點
- 變更state必須通過mutation提交,這樣使得我們可以方便地跟蹤每一個狀態的變化
- mutation 必須是同步函式,非同步應在action操作
- 通常使用常量替代mutation事件型別,在實際操作中通常會建立一個mutation-types.js來儲存mutation常量,這樣的好處是可以對整個 app 包含的 mutation 一目瞭然
建立mutation-types.js
在store目錄下繼續建立mutation-types.js,用來儲存mutation事件名
1 2 3 |
export const CHANGE_STATUS = 'CHANGE_STATUS' export const ADD_DURATION = 'ADD_DURATION' export const SAVE_CLOCK_LIST = 'SAVE_CLOCK_LIST' |
建立actions.js
action類似autation,與之不同的是:
- action不能直接變更state,而是提交mutation
- action可包含非同步操作,而mutation不能(嚴格模式下報錯)
Action基本語法如下:
1 2 3 4 5 |
actions: { someMethod (context) { context.commit('someMethod') } } |
action 函式接受一個與 store 例項具有相同方法和屬性的 context 物件,因此可以呼叫 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。
在實踐中,通常使用 ES2015 的 引數解構簡化程式碼,如下:
1 2 3 4 5 |
actions: { someMethod ({ commit }) { commit('someMethod') } } |
最後附上actions.js的所有程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import * as types from './mutation-types' export default { changeStatus({ commit }) { commit(types.CHANGE_STATUS) }, addDuration(context) { let num = 1, obj = {} if(context.state.status === '已計時') { obj.timer = setInterval(() => { let h = parseInt(num / 3600), m = parseInt(num / 60), s = num if(s >= 60) { s = s % 60 } if(m >= 60) { m = m % 60 } obj.time = h + '時' + m + '分' + s + '秒' context.commit(types.ADD_DURATION, obj) num ++ }, 1000) }else { context.commit(types.ADD_DURATION, obj) } }, saveClockList({ commit }, nowTime) { commit(types.SAVE_CLOCK_LIST, nowTime) } } |
建立getters.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
export default { getStatus:state => state.status, getDuration:state => state.duration, switchTime:state => { //轉換前 let date = '', toTime = '', offTime = '', list = [] //轉換後 let switchDate = '', switchToTime = '', switchOffTime = '' state.clockList.forEach(function (v, i) { switchDate = v.gotowork.getFullYear() + '年' + ( v.gotowork.getMonth() + 1 ) + '月' + v.gotowork.getDate() switchToTime = v.gotowork.getHours() + ':' + v.gotowork.getMinutes() + ':' + v.gotowork.getSeconds() if(v.gooffwork !== '') { switchOffTime = v.gooffwork.getHours() + ':' + v.gooffwork.getMinutes() + ':' + v.gooffwork.getSeconds() }else { switchOffTime = '' } list.push({'date': switchDate, 'gotowork': switchToTime, 'gooffwork': switchOffTime}) }) return list } } |
getter接收state作為第一個引數,我們可以通過它
- 獲取state的狀態
- 對需要返回的資料進行處理,如過濾、轉換等
關於程式碼結構
只要遵守了vuex的規則,如何組織程式碼可根據專案的實際情況以及個人、團隊的使用習慣,vuex並不會限制你的程式碼結構。所以,放開手腳一起搞事吧!
以上就是整篇文章的所有內容,如有錯誤,懇請指正! 反正我們也不改
寫在最後
不得不說前端的技術更新真是快啊,從來沒有哪個行業像前端這樣繁榮卻又令人不安。作為前端人,我們唯有保持對新技術敏銳的嗅覺與熱情,才能避免被技術前進的浪潮拍在沙灘上,正所謂路漫漫其修遠兮,吾將…好了好了,搬磚去了…
參考資料
http://cn.vuejs.org/v2/guide/
http://vuex.vuejs.org/zh-cn/getting-started.html
https://gold.xitu.io/post/583d1fe00ce463006baca2fa
https://aotu.io/notes/2016/10/13/vue2/