Build a Vue 2.0 + Vuex Timer App: Step‑by‑Step Guide
This tutorial walks you through creating a simple timer application with Vue 2.0, Vue‑Router, Vuex and Webpack, highlighting the differences from Vue 1.0, showing project setup, file structure, core code snippets, and best practices for state management.
Introduction
The article demonstrates how to build a small timer project using Vue 2.0, Vue‑Router, Vuex and Webpack, and compares the workflow with the older Vue 1.0 approach.
Prerequisites and Versions
node v6.2.0
vue v2.1.0
vuex v2.0.0
vue‑router v2.1.1
webpack v1.13.2
Project Setup
Install the Vue CLI globally: npm install vue-cli -g Initialize a project in a chosen directory (avoid Chinese characters):
vue init webpack my-project
# or vue init webpack-simple my-projectChoose the simple template for a lightweight setup.
Enter the project folder and install dependencies:
cd my-project
npm installStart the development server: npm run dev This command opens the browser automatically; the port can be changed in /config/index.js if needed.
File Structure
main.js Initialization
The entry file configures Vue‑Router, imports the Vuex store, registers global CSS, defines routes, creates a router instance, and mounts the root Vue instance.
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')Differences Between Vue 1.0 and Vue 2.0
Route Mapping : Vue 1.0 used router.map() with an object; Vue 2.0 replaces it with a new VueRouter({ routes }) array.
Router Initialization : Vue 1.0 called router.start(); Vue 2.0 mounts the router directly on the root instance.
App.vue
Defines the root layout, includes a navigation bar with router-link, a sidebar component, and a transition wrapper for route views. The template must have a single root element (e.g., div.clock_wrap) to avoid errors.
<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() { return { transitionName: 'slide-left' } },
mounted() { if (this.$route.name == undefined) this.$router.push('home') },
components: { Sidebar }
}
</script>Key Vue 2.0 Changes Highlighted
Root Element : A single root element is required.
Navigation : Use router-link instead of v-link; the active class is added automatically.
Transitions : Wrap the view with a transition component and optionally set a mode (e.g., out‑in).
Lifecycle Hooks : ready is replaced by mounted; new hooks such as beforeMount, beforeUpdate, updated are available.
Component Details
Home.vue
<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" :id="clockId">开始计时</span>
</div>
</div>
</template>
<script>
export default {
data() { return { hour: '', minute: '', second: '', clockId: 'clock_time' } },
mounted() { this.nowTime() },
methods: {
nowTime() { /* update hour/minute/second each second */ },
doClock() { /* dispatch Vuex actions to change status, duration, and record */ }
}
}
</script>Sidebar.vue
<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>Clocklist.vue
<template>
<div class="clock_record">
<div class="clock_record_nothing" v-if="!list.length">没有记录</div>
<div class="clock_record_item" v-else v-for="(item, index) in list" :key="index">
<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>Vuex Store
state.js
const state = {
status: '已结束',
duration: '0',
timer: null,
len: 0,
clockList: []
}
export default statemutation-types.js
export const CHANGE_STATUS = 'CHANGE_STATUS'
export const ADD_DURATION = 'ADD_DURATION'
export const SAVE_CLOCK_LIST = 'SAVE_CLOCK_LIST'mutations.js
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 === '已计时') {
state.len = state.clockList.length
state.clockList.push({ gotowork: nowTime, gooffwork: '' })
}
if (state.status === '已结束') {
state.clockList[state.len].gooffwork = nowTime
}
}
}actions.js
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(() => {
const h = parseInt(num / 3600)
const m = parseInt(num / 60)
const s = num
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
export default {
getStatus: state => state.status,
getDuration: state => state.duration,
switchTime: state => {
const list = []
state.clockList.forEach(v => {
const date = `${v.gotowork.getFullYear()}年${v.gotowork.getMonth()+1}月${v.gotowork.getDate()}日`
const gotowork = `${v.gotowork.getHours()}:${v.gotowork.getMinutes()}:${v.gotowork.getSeconds()}`
const gooffwork = v.gooffwork ? `${v.gooffwork.getHours()}:${v.gooffwork.getMinutes()}:${v.gooffwork.getSeconds()}` : ''
list.push({ date, gotowork, gooffwork })
})
return list
}
}Strict mode is enabled only in non‑production environments; enabling it in production may affect performance. Vuex modules can be used to split a large store into logical sections.
Conclusion
The guide shows a complete Vue 2.0 + Vuex timer project, explains the migration from Vue 1.0, and provides practical code snippets and configuration tips. Following these steps gives a solid foundation for building more complex single‑page applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Aotu Lab
Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
