Vuex 使用的方式
摘要
專案結構下通常會有多個組件,組件內可能又有組件,組件之間的溝通,通常會用到 emit 和 props,emit 回來,在維護與開發會造成元件之間耦合度太高,本次介紹Vuex框架主要是將資料、處理邏輯集中化處理,視為網站的全域狀態管理,簡化vue元件之間溝通成本,我們主要介紹核心方法:state、getter、mutation、actions,這四個概念,在大型前端系統開發,利用vuex框架可以減少UI之間的資訊傳遞導致程式碼的複雜度。
單個組件的狀態非常好管理,但當遇到多個組件共享state
時,單向數據流的簡潔性就很容易被破壞,主要解決下列應用場景,例如:
- 多個view依賴同一個state
- 來自不同view的actions需要變更同一個state
vuex使用限制
- 只有 mutation 可以改變 state,action --> commit --> mutation
- action: 可以處理
非同步
的事件,再利用commit
與 mutation 溝通 - mutation: 在處理事件是
同步
的
- vuex應用架構: 開發時需要使用架構
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export const store = new Vuex.Store({
//共享物件資訊
state:{
},
//物件計算屬性
getters:{
},
//用來同步state的方法
mutations:{
},
//使用非同步呼叫
actions:{
}
});
State(data)
響應式的資料狀態儲存, 資料狀態變化時,有用到的 component 都會即時更新
- 首先需要安裝vuex的套件以及宣告new一個Store的物件,然後配置初始化的套件
- store.js 建立初始化的store物件,並且export為常數
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
* store.js
export const store = new Vuex.Store({
state:{
registrations :[],
users:[
{id:1, name:'Max', registered: false},
{id:2, name:'Anna', registered: false},
{id:3, name:'Chris', registered: false},
{id:4, name:'Sven', registered: false}
]
}
}
- main.js 引用store檔案中的store物件
import Vue from 'vue'
import App from './App.vue'
import { store } from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
- App.vue 首頁畫面分別建立Registration.vue和Registrations.vue兩個元件
<template>
<div id="app">
<app-registration></app-registration>
<app-registrations></app-registrations>
</div>
</template>
<script>
import Registration from './components/Registration.vue';
import Registrations from './components/Registrations.vue';
export default {
components: {
appRegistration: Registration,
appRegistrations: Registrations
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
- Registration.vue 註冊會員畫面,users()直接呼叫(this.$store.state)物件,就可以直接將目前人員名單。
<template>
<div id="registration">
<h3>Register here</h3>
<hr>
<div class="row" v-for="user in users">
<h4>{{user.name}}</h4>
<button @click="registerUser(user)">Register</button>
</div>
</div>
</template>
<script>
export default {
computed: {
users(){
return this.$store.state.registrations;
}
},
methods: {
registerUser(user){
const date = new Date();
const user = this.$store.state.registrations.find(user =>{
return user.id == userId
});
user.registered =true ;
const registration = {
userId: user.id,
name: user.name,
date: (date.getMonth()+1) + '/' + date.getDate()
}
this.$store.state.registrations.push(registration);
}
}
}
</script>
- Registrations.vue 顯示已經註冊的人員
<template>
<div id="registrations">
<div class="summary">
<h3>Registrations</h3>
<h5>Total: {{ total }}</h5>
</div>
<hr>
<div class="row" v-for="registration in registrations">
<h4>{{ registration.name }}</h4>
<span @click="unregister(registration)">(Unregister)</span>
<div class="date">{{ registration.date }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
methods: {
unregister(registration) {
const date = new Date();
const user = this.$store.state.users.find(user =>{
return user.id == userId
});
this.$store.state.user.registered =true ;
const registration = {
userId: user.id,
name: user.name,
date: (date.getMonth()+1) + '/' + date.getDate()
}
this.$store.state.registrations.push(registration);
}
},
computed: {
registrations(){
return this.$store.state.registrations;
},
total() {
return this.$store.state.registrations.length;
}
}
}
</script>
<style scoped>
#registrations {
box-shadow: 1px 1px 2px 1px #ccc;
margin: 20px;
padding: 20px;
display: inline-block;
width: 300px;
vertical-align: top;
text-align: left;
}
.summary {
text-align: center;
}
.row h4 {
display: inline-block;
width: 30%;
margin: 0 0 10px 0;
box-sizing: border-box;
}
.row span {
width: 30%;
color: red;
cursor: pointer;
}
.row span:hover {
color: darkred;
}
.date {
display: inline-block;
width: 38%;
text-align: right;
box-sizing: border-box;
}
</style>
Getter(computed)
- store.js 將新增一個getters方法,主要對state狀態進行某些處理,並返回需要的結果如註冊會員的人數、沒有註冊的人和已經註冊的人資料。
export const store = new Vuex.Store({
state:{
registrations :[],
users:[
{id:1, name:'Max', registered: false},
{id:2, name:'Anna', registered: false},
{id:3, name:'Chris', registered: false},
{id:4, name:'Sven', registered: false}
]
},
getters:{
unregisteredUsers(state){
return state.users.filter(user=>{
return !user.registered;
});
},
registrations(state){
return state.registrations;
},
totalRegistrations(state){
return state.registrations.length;
}
}
})
- Registrations.vue 將computed改為呼叫getter的方式來實作,搭配mapGetters來map到getter的方法,讓$store.state裡面有關的物件可以封裝成getter來使用。
<template>
<div id="registrations">
<div class="summary">
<h3>Registrations</h3>
<h5>Total: {{ total }}</h5>
</div>
<hr>
<div class="row" v-for="registration in registrations">
<h4>{{ registration.name }}</h4>
<span @click="unregister(registration)">(Unregister)</span>
<div class="date">{{ registration.date }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
methods: {
unregister(registration) {
const date = new Date();
const user = this.$store.state.users.find(user =>{
return user.id == userId
});
this.$store.state.user.registered =true ;
const registration = {
userId: user.id,
name: user.name,
date: (date.getMonth()+1) + '/' + date.getDate()
}
this.$store.state.registrations.push(registration);
}
},
computed: {
...mapGetters({
registrations:'registrations',
total :"totalRegistrations"
}),
addition(){
}
// 舊寫法
// registrations(){
// return this.$store.getters.registrations;
// },
// total() {
// return this.$store.getters.totalRegistrations;
// }
}
}
</script>
<style scoped>
#registrations {
box-shadow: 1px 1px 2px 1px #ccc;
margin: 20px;
padding: 20px;
display: inline-block;
width: 300px;
vertical-align: top;
text-align: left;
}
.summary {
text-align: center;
}
.row h4 {
display: inline-block;
width: 30%;
margin: 0 0 10px 0;
box-sizing: border-box;
}
.row span {
width: 30%;
color: red;
cursor: pointer;
}
.row span:hover {
color: darkred;
}
.date {
display: inline-block;
width: 38%;
text-align: right;
box-sizing: border-box;
}
</style>
mutations (commit)
-
用於更改 state 的方法。它們必須是同步的,並且可以直接修改state物件
-
只處理同步函數:不要在這進行非同步的動作(例如 setTimeout / 打 API 取遠端資料...等)
-
store.js 新增mutations
export const store = new Vuex.Store({
state:{
registrations :[],
users:[
{id:1, name:'Max', registered: false},
{id:2, name:'Anna', registered: false},
{id:3, name:'Chris', registered: false},
{id:4, name:'Sven', registered: false}
]
},
getters:{
unregisteredUsers(state){
return state.users.filter(user=>{
return !user.registered;
});
},
registrations(state){
return state.registrations;
},
totalRegistrations(state){
return state.registrations.length;
}
},
mutations:{
register(state,userId){
const date = new Date();
const user = state.users.find(user =>{
return user.id == userId
});
user.registered =true ;
const registration = {
userId: user.id,
name: user.name,
date: (date.getMonth()+1) + '/' + date.getDate()
}
state.registrations.push(registration);
},
unregister(state,userId){
const user = state.users.find(user =>{
return user.id == userId
});
user.registered = false;
const registration = state.registrations.find(registration =>{
return registration.userId == userId ;
})
state.registrations.splice(state.registrations.indexOf(registration), 1);
}
}
})
Registration.vue : registerUser(user)呼叫$store的mutations方法,重點為外部元件需要同步到$store內部資訊使用mutations方法來封裝。this.$store.commit('register',user.id) 這是其中一種呼叫mutations方法
<template>
<div id="registration">
<h3>Register here</h3>
<hr>
<div class="row" v-for="user in users">
<h4>{{user.name}}</h4>
<button @click="registerUser(user)">Register</button>
</div>
</div>
</template>
<script>
export default {
computed: {
users(){
return this.$store.getters.unregisteredUsers;
}
},
methods: {
registerUser(user){
this.$store.commit('register',user.id) ;
}
}
}
</script>
<style scoped>
#registration {
box-shadow: 1px 1px 2px 1px #ccc;
margin: 20px;
padding: 20px;
display: inline-block;
width: 300px;
vertical-align: top;
}
.row h4 {
display: inline-block;
width: 70%;
text-align: left;
margin: 0 0 10px 0;
}
button {
background-color: lightgreen;
border: none;
box-shadow: 1px 1px 1px black;
font-size: inherit;
text-align: right;
cursor: pointer;
}
button:hover {
background-color: green;
}
</style>
Registrations.vue : this.$store.commit({ type:'unregister', userId:registration.userId,}) 這是另外一種呼叫方式
<template>
<div id="registrations">
<div class="summary">
<h3>Registrations</h3>
<h5>Total: {{ total }}</h5>
</div>
<hr>
<div class="row" v-for="registration in registrations">
<h4>{{ registration.name }}</h4>
<span @click="unregister(registration)">(Unregister)</span>
<div class="date">{{ registration.date }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
methods: {
unregister(registration) {
this.$store.commit({
type:'unregister',
userId:registration.userId,
}) ;
}
},
computed: {
...mapGetters({
registrations:'registrations',
total :"totalRegistrations"
}),
addition(){
}
}
}
</script>
<style scoped>
#registrations {
box-shadow: 1px 1px 2px 1px #ccc;
margin: 20px;
padding: 20px;
display: inline-block;
width: 300px;
vertical-align: top;
text-align: left;
}
.summary {
text-align: center;
}
.row h4 {
display: inline-block;
width: 30%;
margin: 0 0 10px 0;
box-sizing: border-box;
}
.row span {
width: 30%;
color: red;
cursor: pointer;
}
.row span:hover {
color: darkred;
}
.date {
display: inline-block;
width: 38%;
text-align: right;
box-sizing: border-box;
}
</style>
actions (methods)
-
用於處理異步操作。它們可以包含任意的異步操作,並最終提交 mutations 來改變store狀態。
-
透
commit
→ 呼叫mutation
改變state
-
store.js :使用actions方法利用setTimeout非同步的方式來實作註冊方式。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export const store = new Vuex.Store({
state:{
registrations :[],
users:[
{id:1, name:'Max', registered: false},
{id:2, name:'Anna', registered: false},
{id:3, name:'Chris', registered: false},
{id:4, name:'Sven', registered: false}
]
},
getters:{
unregisteredUsers(state){
return state.users.filter(user=>{
return !user.registered;
});
},
registrations(state){
return state.registrations;
},
totalRegistrations(state){
return state.registrations.length;
}
},
mutations:{
register(state,userId){
const date = new Date();
const user = state.users.find(user =>{
return user.id == userId
});
user.registered =true ;
const registration = {
userId: user.id,
name: user.name,
date: (date.getMonth()+1) + '/' + date.getDate()
}
state.registrations.push(registration);
},
unregister(state,userId){
const user = state.users.find(user =>{
return user.id == userId
});
user.registered = false;
const registration = state.registrations.find(registration =>{
return registration.userId == userId ;
})
state.registrations.splice(state.registrations.indexOf(registration), 1);
},
actions:{
register(context, userId){
setTimeout(()=>{
context.commit("register",userId); // 使用mutations中的register方法
},10000)
}
}
}
})
- Registration.vue 利用 this.$store.dispatch('register',user.id) 來呼叫actions的方法
<template>
<div id="registration">
<h3>Register here</h3>
<hr>
<div class="row" v-for="user in users">
<h4>{{user.name}}</h4>
<button @click="registerUser(user)">Register</button>
</div>
</div>
</template>
<script>
export default {
computed: {
users(){
return this.$store.getters.unregisteredUsers;
}
},
methods: {
registerUser(user){
this.$store.dispatch('register',user.id);
}
}
}
</script>
<style scoped>
#registration {
box-shadow: 1px 1px 2px 1px #ccc;
margin: 20px;
padding: 20px;
display: inline-block;
width: 300px;
vertical-align: top;
}
.row h4 {
display: inline-block;
width: 70%;
text-align: left;
margin: 0 0 10px 0;
}
button {
background-color: lightgreen;
border: none;
box-shadow: 1px 1px 1px black;
font-size: inherit;
text-align: right;
cursor: pointer;
}
button:hover {
background-color: green;
}
</style>
總結
當UI元件可以使用async來呼叫actions透過commit來同步更新store物件資料
安裝 Vue Devtools
使用vue Devtools可以方便查看到vue初始化配置況狀、呼叫方法的過程,這是開發者需要必備開發工具。以下連結可以根據不同瀏覽去安裝該套件 https://devtools.vuejs.org/guide/installation.html
留言
張貼留言