前言
React認(rèn)為每個(gè)組件都是一個(gè)有限狀態(tài)機(jī),狀態(tài)與UI是一一對應(yīng)的。我們只需管理好APP的state就能控制UI的顯示,我們可以在每個(gè)component類中來通過this.state和this.setState來管理組件的state,但是如果APP交互比較多比較復(fù)雜,或者說該組件的某一狀態(tài)需要和其他組件共享的話,這種方式就有點(diǎn)復(fù)雜了。
有沒有一種能統(tǒng)一管理APP狀態(tài)的框架呢,這時(shí)候Redux就應(yīng)用而生了,它是一個(gè)用于統(tǒng)一管理APP 所有的state的一個(gè)的js框架,它不建議我們在component中直接操作state,而是交給redux的store中進(jìn)行處理。而react-redux又是redux的作者為react專門做的一個(gè)封裝,適用于react的狀態(tài)管理容器。
那么我們的項(xiàng)目到底需不需要redux呢,從組件角度看,如果你的應(yīng)用有以下場景,可以考慮使用 Redux,否則無腦強(qiáng)行使用反而麻煩。
- 某個(gè)組件的狀態(tài),需要共享
- 某個(gè)狀態(tài)需要在任何地方都可以拿到
- 一個(gè)組件需要改變?nèi)譅顟B(tài)
- 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)
redux的設(shè)計(jì)思想
- Web 應(yīng)用是一個(gè)狀態(tài)機(jī),視圖與狀態(tài)是一一對應(yīng)的。
- 所有的狀態(tài),保存在store對象里面,由其統(tǒng)一管理。
- 狀態(tài)在組件中是‘只讀’的,要交給redux處理
redux數(shù)據(jù)流程圖
有圖有真相,先來一張redux數(shù)據(jù)流圖,讓你有一個(gè)整體的把握

redux的相關(guān)概念
Action
Action 是把數(shù)據(jù)從應(yīng)用(這里之所以不叫 view 是因?yàn)檫@些數(shù)據(jù)有可能是服務(wù)器響應(yīng),用戶輸入或其它非 view 的數(shù)據(jù) )傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源。一般來說你會(huì)通過 store.dispatch() 將 action 傳到 store。
按redux設(shè)計(jì)理念是不允許用戶直接操作組件的state,而是通過觸發(fā)動(dòng)作(action)來更新state,用戶或后臺服務(wù)器可以通過store.dispatch(action)來向store觸發(fā)一個(gè)消息(消息至少一個(gè)標(biāo)識該消息的字段type,還可以添加其他字段用于數(shù)據(jù)傳送),store會(huì)在內(nèi)部根據(jù)消息的類型type去reducer中執(zhí)行相應(yīng)的處理,這個(gè)消息我們就叫他為Action,Action本質(zhì)上是一個(gè)JavaScript對象。
實(shí)際編碼中一般會(huì)把整個(gè)應(yīng)用的消息類型type統(tǒng)一放在一個(gè)文件ActionTypes.js中
export const ADD_TODO = 'ADD_TODO'
Action的結(jié)構(gòu)如下,各個(gè)字段的key的名字可以隨意命名,但是類型的key一般都是type,數(shù)據(jù)類型最好為字符串
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
隨著程序越來越大,你會(huì)發(fā)現(xiàn)一個(gè)組件中的action太多太亂了,所以我們也會(huì)把a(bǔ)ction按業(yè)務(wù)分類放在各個(gè)指定的文件中,但是又有一個(gè)問題,若果每個(gè)action的字段都有五六個(gè),我們在如下寫法豈不是太亂了
store.dispatch({
type: ADD_TODO,
text: 'Build my first Redux app'
})
于是乎我們就想起來可以將action對象封裝在函數(shù)中,這個(gè)函數(shù)返回一個(gè)action對象,這個(gè)返回一個(gè)action對象的函數(shù)我們就稱之為 action creator,如下所示
export let todo = ()=> {
return {
type: ADD_TODO,
text: 'Build my first Redux app'
}
}
我們直接store.dispatch(todo)就好了,看著是不是整潔多了啊
異步動(dòng)作(async action)
事實(shí)上redux提供的dispatch方法只能接受純粹的action對象(即js中Object類型的對象),如下所示:
store.dispatch({
type: ADD_TODO,
text: 'Build my first Redux app'
})
這也是我們最常用的方式,這種方式屬于同步Action,一旦觸發(fā)同步動(dòng)作,redux就能同步的完成state的操作,但是我們在開發(fā)中會(huì)遇到這么一類acton,比如網(wǎng)絡(luò)請求,這是一個(gè)異步的過程,此時(shí)純粹的action對象已經(jīng)滿足不了,我們需要采用異步Action,異步Action本質(zhì)上是一個(gè)函數(shù),我們可以在此函數(shù)的回調(diào)中去調(diào)用dispatch來觸發(fā)action,而這一次的觸發(fā)是同步的,如下所示:
// 來看一下我們寫的第一個(gè) thunk action 創(chuàng)建函數(shù)!
// 雖然內(nèi)部操作不同,你可以像其它 action 創(chuàng)建函數(shù) 一樣使用它:
// store.dispatch(fetchPosts('reactjs'))
export function fetchPosts(subreddit) {
// Thunk middleware 知道如何處理函數(shù)。
// 這里把 dispatch 方法通過參數(shù)的形式傳給函數(shù),
// 以此來讓它自己也能 dispatch action。
return function (dispatch) {
// 首次 dispatch:更新應(yīng)用的 state 來通知
// API 請求發(fā)起了。此時(shí)我們可以做一些操作,比如讓網(wǎng)絡(luò)指示器顯示出來
dispatch(requestPosts(subreddit))
// thunk middleware 調(diào)用的函數(shù)可以有返回值,
// 它會(huì)被當(dāng)作 dispatch 方法的返回值傳遞
return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
// 不要使用 catch,因?yàn)闀?huì)捕獲
// 在 dispatch 和渲染中出現(xiàn)的任何錯(cuò)誤,
// 導(dǎo)致 'Unexpected batch number' 錯(cuò)誤。
// https://github.com/facebook/react/issues/6895
error => console.log('An error occurred.', error)
)
.then(json =>
// 可以多次 dispatch,這里的dispatch是同步action
// 在這里我們可以把異步操作的結(jié)果 同步的觸發(fā) 給redux,同時(shí)隱藏網(wǎng)絡(luò)指示器(即改變網(wǎng)絡(luò)指示器的狀態(tài))
dispatch(receivePosts(subreddit, json))
)
}
}
前面不是說dispatch只能接受js對象嗎,現(xiàn)在怎么可以接受一個(gè)函數(shù)了?其實(shí)這正是thunk middleware(中間件)的功能,他把原來的dispatch進(jìn)行了包裝,進(jìn)行了一系列的預(yù)處理,具體細(xì)節(jié)可以另行參考其他資源,這里不再詳述了。
reducer
它是一個(gè)純函數(shù),要求有相同的輸入(參數(shù))就一定會(huì)有相同的輸出,里面不能包含一些不確定的因素,比如new Date(),它會(huì)根據(jù)當(dāng)前的state和action來進(jìn)行邏輯處理返回一個(gè)新的state
參數(shù)一:當(dāng)前的state對象
參數(shù)二:action對象
返回值:產(chǎn)生一個(gè)新的state對象
import { VisibilityFilters } from './actions'
//初始state
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
};
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
注意:reducer函數(shù)中一定不要去修改state,而是用Object.assign()函數(shù)生成一個(gè)新的state對象,如上所示
combineReducers:隨著應(yīng)用變得復(fù)雜,把APP的所有狀態(tài)都放在一個(gè)reducer中處理會(huì)造成reducer函數(shù)非常龐大,因此需要對 reducer 函數(shù) 進(jìn)行拆分,拆分后的每一個(gè)子reducer獨(dú)立負(fù)責(zé)管理 APP state 的一部分。combineReducers 輔助函數(shù)的作用是,把多個(gè)不同子reducer 函數(shù)合并成一個(gè)最終的根reducer ,最后將根 reducer 作為createStore的參數(shù)就可以創(chuàng)建store對象了。合并后的 reducer 可以調(diào)用各個(gè)子 reducer,并把它們的結(jié)果合并成一個(gè) state 對象。state 對象的結(jié)構(gòu)由傳入的多個(gè) reducer 的 key 決定。
最終,state 對象的結(jié)構(gòu)會(huì)是這樣的:
{
reducer1: ...
reducer2: ...
}
使用方法如下所示
import { combineReducers } from 'redux';
import Strolling from './strollingReducer';
import Foods from './foodsReducer';
import FoodsList from './foodsListReducer';
import FoodCompare from './foodCompareReducer';
import FoodInfo from './foodInfoReducer';
import Search from './searchReducer';
import User from './userReducer';
export default rootReducer = combineReducers({
Strolling,
Foods,
FoodsList,
FoodCompare,
FoodInfo,
Search,
User,
})
// export default rootReducer = combineReducers({
// Strolling:Strolling,
// Foods:Foods,
// FoodsList:FoodsList,
// FoodCompare:FoodCompare,
// FoodInfo:FoodInfo,
// Search:Search,
// User:User,
// })
// export default function rootReducer(state = {},action){
// return{
// Strolling: Strolling(state.Strolling,action),
// Foods:Foods(state.Foods,action),
// FoodsList:FoodsList(state.FoodsList,action),
// FoodCompare:FoodCompare(state.FoodCompare,action),
// FoodInfo:FoodInfo(state.FoodInfo,action),
// Search:Search(state.Search,action),
// User:User(state.User,action)
// }
// }
//以上三種方式是等價(jià)的,key可以設(shè)置也可以省略
注意:我們不一定非要用combineReducers來組合子reducer,我們可以自定義類似功能的方法來組合,state的結(jié)構(gòu)完全由我們決定。
store
一個(gè)應(yīng)用只有一個(gè)store,store 就是用來維持應(yīng)用所有的 state 樹 的一個(gè)對象。 改變 store 內(nèi) state 的惟一途徑是對它 dispatch 一個(gè) action,它有三個(gè)函數(shù)
-
getState()
返回應(yīng)用當(dāng)前的 state 樹。 -
dispatch(action)
分發(fā) action。這是觸發(fā) state 變化的惟一途徑。
會(huì)使用當(dāng)前 getState() 的結(jié)果和傳入的 action 以同步方式的調(diào)用 store 的 reduce 函數(shù)。返回值會(huì)被作為下一個(gè) state。從現(xiàn)在開始,這就成為了 getState() 的返回值,同時(shí)變化監(jiān)聽器(change listener)會(huì)被觸發(fā)。 -
subscribe(listener)
當(dāng)state樹發(fā)生變化的時(shí)候store會(huì)調(diào)用subscribe函數(shù),我們可以傳一個(gè)我們訂制的函數(shù)作為參數(shù)來進(jìn)行處理
參數(shù):一個(gè)函數(shù)
返回值:返回一個(gè)解綁定函數(shù)//添加監(jiān)聽 let unsubscribe = store.subscribe(handleChange) //解除監(jiān)聽 unsubscribe() -
replaceReducer(nextReducer)
替換 store 當(dāng)前用來計(jì)算 state 的 reducer。
這是一個(gè)高級 API。只有在你需要實(shí)現(xiàn)代碼分隔,而且需要立即加載一些 reducer 的時(shí)候才可能會(huì)用到它。在實(shí)現(xiàn) Redux 熱加載機(jī)制的時(shí)候也可能會(huì)用到。
react-redux基礎(chǔ)
前言已經(jīng)提到過react-redux的由來,這里在啰嗦一下,react-redux是redux作者專門為react訂制的,這樣使用起來更方便,我們只需在我們的組件中通過屬性props獲取dispatch方法,就可以直接向store發(fā)送一個(gè)action,而不需要再獲取store對象,通過store.dispatch方法發(fā)送。
react-redux有兩寶,provider和connect,下面詳細(xì)介紹一下。
Provider:
有一個(gè)store屬性,我們要將應(yīng)用的根組件放到Provider標(biāo)簽中,這樣應(yīng)用的所有的子組件就可以通過context來獲取store對象了,但是我們一般不會(huì)通過此法來獲取store對象,Provider是為了給connect函數(shù)使用的,這樣才能通過connect函數(shù)的參數(shù)獲取到store的state和dispatch了。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect是一個(gè)高階函數(shù),connect()本身會(huì)返回一個(gè)函數(shù)變量(假如名字為func),給這個(gè)函數(shù)變量傳遞一個(gè)參數(shù)func(MainContainer)會(huì)生成一個(gè)MainContainer容器組件,形如下面的寫法:
export default connect((state) => {
const { Main } = state;
return {
Main
}
})(MainContainer);
參數(shù)一:[mapStateToProps(state, [ownProps]): stateProps] (Function)
如果定義該參數(shù),組件將會(huì)監(jiān)聽 Redux store 的變化。任何時(shí)候,只要 Redux store 發(fā)生改變,mapStateToProps 函數(shù)就會(huì)被調(diào)用。該回調(diào)函數(shù)必須返回一個(gè)純對象,這個(gè)對象會(huì)與組件的 props 合并。如果你省略了這個(gè)參數(shù),你的組件將不會(huì)監(jiān)聽 Redux store。如果指定了該回調(diào)函數(shù)中的第二個(gè)參數(shù) ownProps,則該參數(shù)的值為傳遞到組件的 props,而且只要組件接收到新的 props,mapStateToProps 也會(huì)被調(diào)用(例如,當(dāng) props 接收到來自父組件一個(gè)小小的改動(dòng),那么你所使用的 ownProps 參數(shù),mapStateToProps 都會(huì)被重新計(jì)算)。
參數(shù)二:[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):
如果傳遞的是一個(gè)對象,那么每個(gè)定義在該對象的函數(shù)都將被當(dāng)作 Redux action creator,而且這個(gè)對象會(huì)與 Redux store 綁定在一起,其中所定義的方法名將作為屬性名,合并到組件的 props 中。如果傳遞的是一個(gè)函數(shù),該函數(shù)將接收一個(gè) dispatch 函數(shù),然后由你來決定如何返回一個(gè)對象,這個(gè)對象通過 dispatch 函數(shù)與 action creator 以某種方式綁定在一起(提示:你也許會(huì)用到 Redux 的輔助函數(shù) bindActionCreators())。
如果你省略這個(gè) mapDispatchToProps 參數(shù),默認(rèn)情況下,dispatch 會(huì)注入到你的組件 props 中。如果指定了該回調(diào)函數(shù)中第二個(gè)參數(shù) ownProps,該參數(shù)的值為傳遞到組件的 props,而且只要組件接收到新 props,mapDispatchToProps 也會(huì)被調(diào)用。
參數(shù)三:[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)
如果指定了這個(gè)參數(shù),mapStateToProps() 與 mapDispatchToProps() 的執(zhí)行結(jié)果和組件自身的 props 將傳入到這個(gè)回調(diào)函數(shù)中。該回調(diào)函數(shù)返回的對象將作為 props 傳遞到被包裝的組件中。你也許可以用這個(gè)回調(diào)函數(shù),根據(jù)組件的 props 來篩選部分的 state 數(shù)據(jù),或者把 props 中的某個(gè)特定變量與 action creator 綁定在一起。如果你省略這個(gè)參數(shù),默認(rèn)情況下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的結(jié)果。
[options] (Object) 如果指定這個(gè)參數(shù),可以定制 connector 的行為。
參數(shù)四:[options] (Object) 如果指定這個(gè)參數(shù),可以定制 connector 的行為。
[pure = true] (Boolean): 如果為 true,connector 將執(zhí)行 shouldComponentUpdate 并且淺對比 mergeProps 的結(jié)果,避免不必要的更新,前提是當(dāng)前組件是一個(gè)“純”組件,它不依賴于任何的輸入或 state 而只依賴于 props 和 Redux store 的 state。默認(rèn)值為 true。
[withRef = false] (Boolean): 如果為 true,connector 會(huì)保存一個(gè)對被包裝組件實(shí)例的引用,該引用通過 getWrappedInstance() 方法獲得。默認(rèn)值為 false。
redux-redux使用
上面說了provider和connect方法,下面是實(shí)用講解
創(chuàng)建store對象的js文件
下面的代碼里包括應(yīng)用中間件redux-thunk,和創(chuàng)建store對象兩步,這里有更多關(guān)于中間件的詳情
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootRudcer';
//使用thunk中間件
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
//創(chuàng)建store對象,一個(gè)APP只有一個(gè)store對象
let store = createStoreWithMiddleware(rootReducer);
export default store;
程序的入口文件
import React from 'react';
import { Provider } from 'react-redux';
import store from './store/store';
import App from './containers/app';
export default class Root extends React.Component {
render() {
return (
//將APP的根視圖組件包含在provider標(biāo)簽中
<Provider store = {store} >
<App />
</Provider>
)
}
}
在容器組件中,將redux和組件關(guān)聯(lián)起來生成一個(gè)容器組件,這里是redux與組件關(guān)聯(lián)的地方,大多數(shù)童鞋使用redux最迷惑的地方估計(jì)就在這一塊了。
import React from 'react';
import {connect} from 'react-redux';
import Brand from '../Components/Brand';
//BrandContainer容器組件
class BrandContainer extends React.Component {
render() {
return (
//把容器組件的屬性傳遞給UI組件
<Brand {...this.props} />
)
}
}
export default connect((state) => {
const { BrandReducer } = state;
return {
BrandReducer
}
})(BrandContainer);
這樣UI組件Brand中就可以通過屬性獲取dispatch方法以及處理后的最新state了
const {dispatch, BrandReducer} = this.props;
下面來解釋一下上面的代碼
將當(dāng)前的BrandContainer組件關(guān)聯(lián)起來,上面介紹了store中的state對象的結(jié)構(gòu)會(huì)是這樣的:
{
reducer1: ...
reducer2: ...
}
所以可以通過解構(gòu)的方式,獲取對應(yīng)模塊的state,如下面的const { BrandReducer } = state;
下面這一塊代碼的作用就是將store中state傳遞給關(guān)聯(lián)的容器組件中,當(dāng)store中的state發(fā)生變化的時(shí)候,connect的第一參數(shù)mapStateToProps回調(diào)函數(shù)就會(huì)被調(diào)用,并且將該回調(diào)函數(shù)的返回值映射成其關(guān)聯(lián)組件的一個(gè)屬性,這樣容器組件的屬性就會(huì)發(fā)生變化,而UI組件又通過{...this.props}將容器組件的屬性傳遞給了UI組件,所以UI組件的屬性也會(huì)發(fā)生變化,我們知道屬性的變化會(huì)導(dǎo)致UI組件重新render。好了,我們就能知道為什么我們在UI組件中dispatch一個(gè)action后UI組件能更新了,因?yàn)閁I組件的屬性發(fā)生變化導(dǎo)致RN重繪了UI。
react native 組件的生命周期
弄明白了這個(gè)圖我認(rèn)為你就能基本掌握RN了(圖片來自互聯(lián)網(wǎng),【注意圖中有錯(cuò)誤】最后end的時(shí)候是componentWillUnmount)
項(xiàng)目的推薦目錄
這種結(jié)構(gòu)適合業(yè)務(wù)邏輯不太復(fù)雜的中小型項(xiàng)目,其優(yōu)點(diǎn)是邏輯模塊清晰,缺點(diǎn)是文件目錄跨度較大,對于大型項(xiàng)目建議按項(xiàng)目的功能模塊來劃分。

【小型項(xiàng)目建議結(jié)構(gòu)】
utils -- 定義一些工具類,比如網(wǎng)絡(luò)請求、本地緩存類、加解密工具類等
common -------- 定義一些通用樣式,以及通用常量,如actionType等
components ----- 定義一些通用組件
pages ------------- 業(yè)務(wù)組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
store --------------- 定義把子reducer綁定成一個(gè)rootReducer來創(chuàng)建store
navigator---------- 注冊路由
root.js ------------ 根組件
【復(fù)雜項(xiàng)目建議結(jié)構(gòu)】
utils -- 定義一些工具類,比如網(wǎng)絡(luò)請求、本地緩存類、加解密工具類等
common -------- 定義一些通用樣式,以及通用常量,如actionType等
components ----- 定義一些通用組件
業(yè)務(wù)模塊一:
pages ------------- 業(yè)務(wù)組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
業(yè)務(wù)模塊二:
pages ------------- 業(yè)務(wù)組件
containers ------- 定義redux的容器組件
actions ------------ 定義一系列action creator
reduces ---------- 定義reducer
navigator---------- 注冊路由
store --------------- 定義把子reducer綁定成一個(gè)rootReducer來創(chuàng)建store
root.js ------------ 根組件
熱更新
目前市場主流的熱更新庫為code-push和jspatch,但現(xiàn)在發(fā)現(xiàn)用了jspatch庫上架AppStore會(huì)被拒,所以這里主要用code-push來做講解。
code-push是微軟推出的一個(gè)熱更新庫。。。。。。
相關(guān)文章
React 實(shí)踐心得:react-redux 之 connect 方法詳解
Redux 入門教程(一):基本用法
redux中文文檔
react + redux 完整的項(xiàng)目,同時(shí)寫一下個(gè)人感悟
注:部分圖片來源于互聯(lián)網(wǎng)