vue2.0與1.0的區別

一個栗子實踐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。

  1. 安裝vue腳手架。

    1
    npm install vue-cli -g
  2. 選擇一個目錄並執行。

    1
    vue init webpack 項目名字 或者vue init webpack-simple 項目名字 <注意不能用中文>

    然後根據命令行的相關提示輸入信息;
    webpack與webpack-simple兩者的區別在於webpack-simple沒有包括eslint等功能,普通的項目用simple就好了;
    我使用的是前者。

  3. 進入項目目錄下載相關依賴。

    1
    2
    cd 項目目錄
    npm install
  4. 啓動。

    1
    npm 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
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'
//引入公共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
138
139
140
141
142
143
144
145
146
147
148
149
150
<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 @click = '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/


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