一個栗子實踐vue2.0與1.0的區別
原文:https://aotu.io/notes/2016/12/28/vue-clock/?utm_source=tuicool&utm_medium=referral
by 自成 on 2016-12-28
序言
本文通過一個初熟的小栗子來實踐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腳手架。
-
選擇一個目錄並執行。
1
|
vue init webpack 項目名字 或者vue init webpack-simple 項目名字 <注意不能用中文>
|
然後根據命令行的相關提示輸入信息;
webpack與webpack-simple兩者的區別在於webpack-simple沒有包括eslint等功能,普通的項目用simple就好了;
我使用的是前者。
-
進入項目目錄下載相關依賴。
-
啓動。
這一行命令會自動啓動瀏覽器並運行項目(如果你不想佔用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 36 37 38 39
|
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' 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 }) new Vue({ store, router, ...App }).$mount('#app')
|
與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') ] }
|
下面附上整個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 138 139 140 141 142 143 144 145 146 147 148 149 150
|
<template> <div class="clock_wrap"> <section class="clock_header"> <h1>計時器</h1> </section> <section class="clock_container"> <nav class="clock_nav"> <router-link to="/home">首頁</router-link> <router-link to="/clocklist">計時列表</router-link> </nav> <div class="clock_sidebar"> <sidebar></sidebar> </div> <div class="clock_router"> <transition :name="transitionName"> <router-view class="clock_router_inner"></router-view> </transition> </div> </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'); } }, '$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"> <i>{{hour}}</i> <span>:</span> <i>{{minute}}</i> <span>:</span> <i>{{second}}</i> </div> <div class="clock_time_btn"> <span @click = 'doClock' v-bind:id="clockId">開始計時</span> </div> </div> </template> <script> export default { data() { return { 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.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指令,如上面註釋掉的部分所示;
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/