前言
最近在學 React-Native
,卡在 React-Redux
上了,費了些時間和功夫,對其原理和資料流向瞭解了一點兒,畫了幅圖,解釋下。希望看這篇文章的人最好對 Redux
有些瞭解,假如不瞭解,可以去看下阮一峰的文章。有些解釋是個人理解,不是很嚴謹,假如有錯誤的地方,煩請指正。
資料流向圖
以下是來自阮一峰部落格上的程式碼,經過一些修改以適配 React-Native
。
root.js// 建立一個store全域性管理state和操作const store = createStore(reducer);
// Provider在根元件<
App>
外面包了一層,App的所有子元件就預設都可以拿到state,通過元件的props傳遞export default class Root extends Component {
render() {
return ( <
Provider store={store
}>
<
App/>
<
/Provider>
)
}
}複製程式碼
app.js// view只提供UI元件,沒有自己的state和操作class Counter extends Component {
render() {
// View的狀態來自於props,props傳遞的是store中的state const {
value, onIncreaseClick
} = this.props return ( <
div>
<
span>
{value
}<
/span>
<
button onClick={onIncreaseClick
}>
Increase<
/button>
<
/div>
)
}
}// 將UI元件和容器元件連線起來const App = connect( state =>
({
value: state.count //輸入,將store中的state通過props輸入
}), dispatch =>
({
// 輸出,將action作為props繫結到View上,使用者操作型別在此分發出去 onIncreaseClick: () =>
dispatch(increaseAction.increase())
}))(Counter)export default App複製程式碼
increaseAction.js// 定義action的型別export const INCREMENT = 'INCREMENT';
// 執行操作,返回包含action型別或data的新物件export function increase(params) {
// 這兒可以執行網路請求,資料計算等操作, return {
type: INCREMENT, data:data
};
}複製程式碼
counterReducer.js// 提供一個初始的狀態initState={
count: 0
}// 通過判斷Action的型別,返回新的資料改變後的state物件,即使沒有任何狀態的改變,也要返回一個物件export default function counter(state = initState, action) {
const count = state.count switch (action.type) {
case INCREMENT: return {
count: count + 1
} default: return state
}
}複製程式碼
資料的流向可以看下圖,先以一個View
為例子,待會兒講多個,這幅圖看懂了,React-Redux
也就明白的差不多了。
我們尋本溯源,一點點來看這幅圖。通過問題來解疑答惑:
1、View
元件只提供UI,沒有state
和操作,那麼什麼導致了介面的變化?
是View
本身的props
,我們知道元件的初始狀態由props
決定,雖然沒有了自己的state
,假如他的props
發生改變,介面也會發生變化。
2、View
的props
內容由什麼提供,多個View
中的props
如何區分?
由應用中全域性唯一store
中的state
提供,所有的狀態,儲存在一個物件裡面,通過key區分。<
這行程式碼實現了為應用繫結唯一的
Provider store={store
}>
<
App/>
<
/Provider>store
。
3、store
是怎麼來的?
通過 store = createStore(reducer)
建立,reducer
返回的正好是變化後的state物件。
前三個問題解釋了上圖左半部分的資料流向,reducer——(store/state)——provider——(state/props)——view
4、action
是如何與reducer
繫結的,或者說,reducer(state,action)
這個函式中的action是怎麼來的?
是store.dispatch(action)
內部處理的,先看下createStore(reducer)
這個函式,簡略程式碼如下:
function createStore = ( reducer ) =>
{
let currentState;
// 內部的狀態 let listeners = [];
//所有的監聽者 const getState = () =>
currentState;
// 獲取store中的state // dispatch的操作就是內部執行reducer()函式,action和reducer在這兒產生交集,並且通知所有的監聽者 const dispatch = ( action ) =>
{
currentState = reducer(state, action);
// 更新state listeners.forEach(listener =>
listener());
} // 訂閱事件 const subscribe = ( listener ) =>
{
listeners.push(listener);
return ()=>
{
listeners = listeners.filter(l =>
l !== listener)
}
} return {
getState, dispatch, subscribe
}
}複製程式碼
為什麼沒有顯示的寫出呼叫的是store中
的 dispatch
,這個全都是React-Redux
中 connect
和 Provider
的功勞了,假如不用他們,上面app.js
中的程式碼應該如下:
class Counter extends Component{
componentWillMount(){
// 訂閱狀態變化 store.subscribe((state)=>
this.setState(state))
} render() {
return ( <
div>
<
span>
{value
}<
/span>
// 點選後dispatch事件型別 <
button onClick={()=>
store.dispatch(increaseAction.increase())
}>
Increase<
/button>
<
/div>
)
}
}複製程式碼
5、reducer()
函式執行之後,是如何更改state
的?
見問題4中createStore
的程式碼,簡化的可以寫成:
function createStore = ( reducer ) =>
{
let currentState;
// 內部的狀態 const getState = () =>
currentState;
// 獲取store中的state // 更新state const dispatch = ( action ) =>
{
currentState = reducer(state, action);
} return {
getState, dispatch,
}
}複製程式碼
以上兩個問題解決了解釋了上圖右半部分的資料流向,view——(action)——dispatch——(action)——reducer
,兩個資料迴圈合在一起,就是一個圓,完成了生命的大和諧。如下圖:
多個Reducer
看完上面的分析,我們再擴充下,上圖中只有一個reducer
,正常的app中有很多View,自然有很多相對應的reducer
,那麼一個介面的action
是如何與其對應的reducer
繫結的呢?
假如上面的專案中新增了一個loginReducer.js
檔案,程式碼如下:
loginReducer.js// 提供一個初始的狀態initState={
login: false
}// 通過判斷Action的型別,返回新的資料改變後的state物件,即使沒有任何狀態的改變,也要返回一個物件export default function login(state = initState, action) {
const login = state.login switch (action.type) {
case INCREMENT: return {
login: !login
} default: return state
}
}複製程式碼
這個reducer
就執行一個操作,收到 INCREMENT
這個操作型別,登入狀態反轉一次。假如我再點選那個按鈕,count這個數字增加的同時,登入狀態會不會發生變化呢?答案是會!那為什麼呢?
會的前提是:你用到了下面的程式碼:
const rootReducer = combineReducers({
counter: counter, login:login
});
store=createStore(rootReducer);
複製程式碼
combineReducers
顧名思義就是合併reducer
,所謂的合併,就是把reducer
函式物件整合為單一reducer
函式,它會遍歷所有的子reducer
成員,並把結果整合進單一狀態樹,所以最後只有一個reducer
,重複一遍,最後只有一個reducer
函式!combineReducers
粗略的程式碼如下:
export default function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers) var finalReducers = {
} //提取reducers中value值為function的部分 for (var i = 0;
i <
reducerKeys.length;
i++) {
var key = reducerKeys[i] if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
} var finalReducerKeys = Object.keys(finalReducers) return function combination(state = {
}, action={
}) {
var hasChanged = false var nextState = {
} /** * 遍歷訪問finalReducers */ for (var i = 0;
i <
finalReducerKeys.length;
i++) {
var key = finalReducerKeys[i] var reducer = finalReducers[key] /** *將state按照reducer的名字分離 * 每個key都對應著state */ var previousStateForKey = state[key];
var nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey
} return hasChanged ? nextState : state
}
}複製程式碼
上面的程式碼可以看出,當dispatch
一個action
,所有的reducer
函式都會執行一遍,通過action.type
修改對應的state
,從而所有訂閱相應state
的View
都會發生變化。所以上面問題的答案就是:會。最後再放一個圖,也就是多個reducer
和action
時的資料流向圖。
圖上可以看出,store
,state
,reducer
,action
其實最後都只有一個,我們只是為了程式碼邏輯將其分為多個,層次分明,便於開發和閱讀。
總結
一句話總結,View
只負責UI介面,不存在私有的state
和操作,redux
將View
中的state
和操作集中起來在store
中管理,然後通過props
將修改後的state
內容傳遞給View
,介面發生變化。使用者操作介面,View
通過dispatch
執行相關操作,然後將ActionType
和Data
交由reducer
函式,根據ActionType
和Data
修改state
。
致謝
最後
喜歡的請點贊和關注。
歡迎進入我的GitHub主頁,喜歡的可以follow
我。
來源:https://juejin.im/post/5acdbe8f51882548fe4a7af1?utm_medium=fe&utm_source=weixinqun