一. 概述
React與Vue是我們熟悉的兩大前端主流框架,來自官方的解釋,Vue是一套用于構(gòu)建用戶界面的漸進式框架,React是一個用于構(gòu)建用戶界面的JavaScript庫,兩個框架都使用各自的語法,專注于用戶UI界面的構(gòu)建.那我們會有疑問,這兩個框架都專注于UI界面的構(gòu)建,但是隨著JavaScript單頁應(yīng)用開發(fā)日趨復(fù)雜,我們?nèi)绾芜M行更多數(shù)據(jù)的管理呢?比如網(wǎng)絡(luò)請求的數(shù)據(jù)、緩存數(shù)據(jù)、本地生成尚未持久化到服務(wù)器的數(shù)據(jù),UI狀態(tài)數(shù)據(jù),激活的路由,被選中的標簽等等. 基于上面的疑問,兩個框架都有各自的解決方案:React-Redux與Vuex.
二.使用
1.Redux
使用react-redux之前我們先來了解一下Redux。Redux是 JavaScript 狀態(tài)容器,提供可預(yù)測化的狀態(tài)管理,Redux由Flux演變而來,當然除了和React一起用外,還支持其它界面庫,不過我們這里主要介紹它配合React進行使用.先來了解下它的幾個核心概念:
(1) 核心概念
-
State: 所謂的state就是React組件所依賴的狀態(tài)對象。你可以在里面定義任何組件所依賴的狀態(tài)。比如一個簡單的todo應(yīng)用的state可能是這樣
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
-
Action:action就是一個普通JavaScript對象,用來描述發(fā)生了什么.比如
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
你可以把action理解為一個描述發(fā)生了什么的指示器。在實際應(yīng)用中,我們會dispatch(action),通過派發(fā)action來達到修改state的目的。這樣做的好處是可以清晰地知道應(yīng)用中到底發(fā)生了什么。
-
Reducer:reducer的作用是用來初始化整個Store,并且串起state與action, 它是一個接收state和action并返回新state的函數(shù).我們可以通過區(qū)分不同的action類型,來處理并返回不同的state.
const StoreAction = (state = defaultState,action) => {
switch (action.type) {
case HOME_ACTION_UPDATE_STATE:
return {...state,...action.data};
case ADD_ARTICLELIST_STATE:
let newState = JSON.parse(JSON.stringify(state));/// 深拷貝
newState.articleList = newState.articleList.concat(action.data);
return newState;
default:
return state;
}
}
(2) 使用原則
使用Redux進行數(shù)據(jù)管理時有三個原則需要注意
單一數(shù)據(jù)源
整個應(yīng)用的state被儲存在一棵object tree中,并且這個object tree只存在于唯一一個store中。State是只讀的
唯一改變state的方法就是觸發(fā)action,action是一個用于描述已發(fā)生事件的普通對象。使用純函數(shù)來執(zhí)行修改
我們通過reducer接收先前的state和action,并返回新的state,Reducer必須是一個純函數(shù),所謂的純函數(shù)就是一個函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過程中沒有副作用。
(3)React Redux
react-redux是Redux官方提供的React綁定庫.他的使用也遵循上面的redux原則。
- 安裝
npm install --save react-redux
-
流程
image.png
通過上面的流程圖可以很清晰的明確Redux的使用:
React組件首先調(diào)用ActionCreators里事先定義好的方法,得到一個actoion,通過dispatch(action)達到派發(fā)action給Reducer的目的。Reducer通過接受的不同的action來對state數(shù)據(jù)進行處理,處理完成后,返回一個新的state,state變化后React組件進行重新渲染。
-
使用
-
入口文件index.js
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
-
-
創(chuàng)建store/reducer.js
import { ADD_TODO_LIST_VALUE } from "./actionTypes"; /// 初始化數(shù)據(jù) const defaultState = { todos: [{ text: 'Eat food', completed: true }, { text: 'Exercise', completed: false }], visibilityFilter: true, } /// Reducer 可以接受state,但是不能修改State ! export default (state = defaultState , action) => { switch (action.type) { case ADD_TODO_LIST_VALUE: const newState = JSON.parse(JSON.stringify(state));///將原來的state 做一次深拷貝 newState.todos.push(action.value); return newState; default: return state; } } -
根據(jù)
reducer創(chuàng)建store/index.js,import { createStore,compose,applyMiddleware } from 'redux'; import reducer from './reducer'; const store = createStore(reducer); export default store; -
創(chuàng)建
actionCreatorsstore/actionCreators.jsimport { ADD_TODO_LIST_VALUE } from "./actionTypes" export const addItemListAction = (value) => ({ type:ADD_TODO_LIST_VALUE, value }) -
創(chuàng)建
actionTypes專門用來存儲action的type值export const ADD_TODO_LIST_VALUE = 'add_todo_list_value'; -
React組件中使用
import React, { Component } from 'react'; import { addItemListAction } from '../pages/home/store/actionCreators'; import {connect} from 'react-redux'; class Customtodolist extends Component { render() { return ( <div className='todo-list' onClick={()=>{this.props.addListItem()}}> <ul> { this.props.todos.map((item,index) => <li key={index}>{index}:{item}</li> ) } </ul> </div> ); } } const mapStateToProps = (state) => { return { todos: state.todos } } const mapDispatchToProps = (dispatch) => { return { addListItem: () => { const item = {text: 'Eat food',completed: true} const actionCreator = addItemListAction(item); dispatch(actionCreator); } } } export default connect(mapStateToProps, mapDispatchToProps)(Customtodolist);我們通過
react-redux的connect方法,將mapStateToProps與mapDispatchToProps 方法與組件鏈接,然后直接在類組件中通過this.props.XXX的方式進行訪問Store中的state. -
React hooks中使用/// hooks 中使用react-redux import { useSelector, useDispatch } from 'react-redux'; const Home = (props)=> { // hook 獲取store state 方式 const storeCount = useSelector(state => state.home.count); // 獲取actionCreator方法 const dispatch = useDispatch(); return ( <div className={style['home-content']}> <div className='home-content-detail'> StoreCount數(shù)據(jù)展示{storeCount} <div> {/* addStoreCount是在actionCreators中定義的方法 */} <button onClick={()=> {dispatch(addStoreCount(0))}}>點擊storeCount+1</button> </div> </div> </div> ) } -
redux-thunk的使用redux-thunk是redux的中間件.他的主要作用是可以使用異步派發(fā)action。例如我們要進行網(wǎng)絡(luò)請求,那么可以在actionCreators里面異步派發(fā)action.安裝與使用
npm install --save redux-thunk(1).在store/index.js 中添加引入
import thunk from "redux-thunk";/// redux-thunk 中間件 需要引入的(2). 使用
thunk初始化store/// 下面的代碼是固定寫法 /// redux-thunk 中間件 需要引入的 const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ }) : compose; /// 中間件都需要用這個方法 const enhancer = composeEnhancers( applyMiddleware(thunk),/// redux-thunk 中間件 需要引入的 ); /// 創(chuàng)建 const store = createStore( reducer, enhancer//使用Redux-thunk 中間件 );(3).在
actionCreater.js中添加異步派發(fā)函數(shù),注意:在獲取到異步處理的結(jié)果后,我們?nèi)匀恍枰{(diào)用actionCreater.js中的其他創(chuàng)建action方法,來對其進行dispatch.export const initListAction = (data) => ({ type:INIT_LIST_ACTION, data }) /// 將異步請求 放在 Action 中執(zhí)行 export const getToList = () => { /// 這里返回一個函數(shù),能獲取到 dispatch 方法 這就是 redux-thunk 的作用,可以返回一個函數(shù) return (dispatch) => { axios.get('/api/todolist').then((res) => { // alert(res.data); const data = res.data; const action = initListAction(data); dispatch(action); }).catch((error)=>{ console.log('網(wǎng)絡(luò)請求錯誤了---thunk----》'); }) } } -
reducer的拆分與合并隨著項目功能模塊越來越多,如果只有一個
reducer來維護state,會使其變動越來越大,從而導致難以維護。combineReducer應(yīng)運而生, 它將根reducer分拆成多個reducer,拆分之后的reducer都是相同的結(jié)構(gòu)(state, action),并且每個函數(shù)獨立負責管理該特定切片 state 的更新。多個拆分之后的reducer可以響應(yīng)一個 action,在需要的情況下獨立的更新他們自己的切片 state,最后組合成新的 state。使用
import { combineReducers } from 'redux';/// 將小的Reducer 合并成大的reducers /// 需要拆分 import headerReducer from '../common/header/store/reducer' import mainReducer from './mainReducer'; import {reducer as homeReducer} from '../pages/home/store'; import {reducer as loginReducer} from '../pages/login/store'; /// 進行 reducer的合并 const reducer = combineReducers({ header:headerReducer, main:mainReducer, login:loginReducer, home:homeReducer, }) export default reducer;在
react組件中使用,要加上reducer名稱,例如我們在Home組件中這樣獲取其stateconst mapStateToProps = (state, ownProps) => { return { showScroll: state.home.showScroll,//state后面添加reducer名稱 } }
2.Vuex
Vuex是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式 + 庫。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。它主要用來解決多個組件共享狀態(tài)的問題。

Vuex 跟 Redux的數(shù)據(jù)管理模式很相似,如果理解Redux,那么Vuex也很容易理解了,只不過Vuex 是專門為 Vue.js 設(shè)計的狀態(tài)管理庫,使用起來要更加方便。
(1) 核心概念
State: 就是組件所依賴的狀態(tài)對象。我們可以在里面定義我們組件所依賴的數(shù)據(jù)??梢栽赩ue組件中通過this.$store.state.XXX獲取state里面的數(shù)據(jù).-
Getter:從store中的state中派生出一些狀態(tài),可以把他理解為是store的計算屬性.const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: (state) => { return state.todos.filter(todo => todo.done) } } })例如我們定義了上面的store,在Vue組件中通過
store.getters.doneTodos訪問它的getter. -
Mutation:更改 Vuex 的 store 中狀態(tài)的唯一方法是提交 mutation,我們通過在mutation中定義方法來改變state里面的數(shù)據(jù)。const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // 變更狀態(tài) state.count++ } } })在Vue組件中,我們通過
store.commit('increment'),來提交。需要注意的是,Mutation 必須是同步函數(shù)。在實際使用中我們一般使用常量替代 Mutation 事件類型。例如:export const INCREMENT_MUTATION = 'INCREMENT_MUTATION' const store = createStore({ state: { count: 1 }, mutations: { // 我們可以使用 ES2015 風格的計算屬性命名功能來使用一個常量作為函數(shù)名 [INCREMENT_MUTATION] (state) { // 變更狀態(tài) state.count++ } } }) -
Action:Action 類似于 Mutation,不同在于:- Action 提交的是 mutation,而不是直接變更狀態(tài)。
- Action 可以包含任意異步操作。
const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } } })在組件中我們通過
store.dispatch('incrementAsync')觸發(fā)action。 -
Module: 當我們的應(yīng)用較大時,為了避免所有狀態(tài)會集中到一個比較大的對象中,Vuex 允許我們將 store 分割成模塊(module),你可以把它理解為Redux中的combineReducer的作用.const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = createStore({ modules: { a: moduleA, b: moduleB } })
在Vue組件中我們使用store.state.a, store.state.b來分別獲取兩個模塊的狀態(tài).
(2) 使用
-
安裝
npm install vuex@next --save OR yarn add vuex@next --save -
main.js中掛載storeimport { createApp } from 'vue'; import App from './App.vue'; import router from './router'; import store from './store'; createApp(App).use(store).use(router).mount('#app'); -
創(chuàng)建
store/index.jsimport { createStore } from 'vuex'; import { ADD_ITEM_LIST, REDUCE_ITEM_LIST, CHANGE_ITEM_LIST_ASYNC } from './constants'; export default createStore({ state: { itemList: [ { text: 'Learn JavaScript', done: true }, { text: 'Learn Vue', done: false }, { text: 'Build something awesome', done: false }, ], }, getters: { doneItemList: (state) => state.itemList.filter((todo) => todo.done), }, mutations: { // 使用ES2015風格的計算屬性命名功能 來使用一個常量作為函數(shù)名 [ADD_ITEM_LIST](state, item) { console.log('增加數(shù)據(jù)', item); state.itemList.push(item); }, [REDUCE_ITEM_LIST](state) { console.log('減少數(shù)據(jù)'); state.itemList.pop(); }, }, actions: { [CHANGE_ITEM_LIST_ASYNC]({ commit, state }, todoItem) { /// 模擬網(wǎng)絡(luò)請求 setTimeout(() => { commit(ADD_ITEM_LIST, todoItem); console.log('state===', state); }, 1000); }, }, modules: { }, });
注意我們這里仍然使用常量來作
actions和mutations的方法名,使用時候要加上[].
-
在選項式API中使用
<template> <div class="about"> <div>{{counter}}</div> <button @click="handleClick">按鈕</button> <div> <div> <TodoItem :itemList="$store.state.itemList"/> </div> <div> 完成的Todo <TodoItem :itemList="$store.getters.doneItemList"/> </div> <div class="btn-content"> <button @click="addClick">增加Item</button> <button @click="reduceClick">減少Item</button> <button @click="changeClickAsync">調(diào)用Action</button> </div> </div> </div> </template> <script> import TodoItem from '../components/TodoItem.vue'; import { ADD_ITEM_LIST, REDUCE_ITEM_LIST, CHANGE_ITEM_LIST_ASYNC, } from '../store/constants'; export default { name: 'About', components: { TodoItem, }, data() { return { counter: 0, }; }, computed: { todos() { return this.$store.getters.doneItemList; }, }, methods: { handleClick() { console.log('handleClick--->'); this.counter += 1; }, addClick() { const item = { text: 'add_item_list success!', done: true }; /// 提交mutations this.$store.commit(ADD_ITEM_LIST, item); }, reduceClick() { this.$store.commit(REDUCE_ITEM_LIST); }, changeClickAsync() const item = { text: 'async_add_item_list success!', done: true }; ///派發(fā)actions this.$store.dispatch(CHANGE_ITEM_LIST_ASYNC, item); }, }, }; </script> -
在組合式API中使用
在組合式API中通過調(diào)用
useStore函數(shù),來在setup鉤子函數(shù)中訪問 store。這與在組件中使用選項式 API 訪問this.$store是等效的.import { useStore } from 'vuex' import { computed } from 'vue' export default { setup () { const store = useStore() return { // 在 computed 函數(shù)中訪問 state count: computed(() => store.state.count), // 在 computed 函數(shù)中訪問 getter double: computed(() => store.getters.double) // 使用 mutation increment: () => store.commit('increment'), // 使用 action asyncIncrement: () => store.dispatch('asyncIncrement') } } }
三. 總結(jié)
通過對比React Redux與Vuex可以發(fā)現(xiàn),兩者的封裝與使用有很大的相似性,它們都借鑒了 Flux的設(shè)計思想,通過使用對比可以讓我們更容易掌握他們。
一些參考:
