在使用react的過程中,用redux來管理應用中的狀態,使應用流更清晰的同時也會有小小的疑惑,比如reducer在redux中時怎麼發揮作用的,為什麼只要寫好reducer,和dispatch特定action配合,store的狀態就會更新了,而react元件又是如何和store的狀態產生關係的,為什麼隨著store狀態的更新,react元件會被自動更新,下面就從redux的原始碼開始來總結下這其中的緣由~
redux管理狀態的原理
redux是繼承自flux體系,但它放棄了dispatcher,無需使用 event emitters(事件傳送器)(在dispatcher中特定action邏輯裡觸發事件,元件裡監聽事件),而使用純 reducer來代替,那麼reducer是如何被排程的,又是如何影響狀態更新的,不妨通過原始碼的邏輯來了解和加深一下~
從redux原始碼看action,reducer,和store之間的關係
reducer通常是由我們自己來寫,在呼叫createStore
函式生成store時來傳入這個reducer
,後續呼叫store
的dispatch
方法來觸發action
時,則reducer
函式會自動被呼叫來解析actin
更新state
,這一切的核心都在crateStore
方法中
export default function createStore(reducer, preloadedState, enhancer) {
...
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function getState() {
...
}
function subscribe(listener) {
...
}
function dispatch(action) {
...
}
function replaceReducer(nextReducer) {
...
}
function observable() {
...
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
複製程式碼
可以看到createStore方法返回一個屬性集合,我們所呼叫的redux的相關方法都是定義在createStroe方法內部,最後被通過這個屬性集合中暴露出來,如處理action的dispath方法,同currentReducer,currentState是createStore方法中的私有變數,由dispath,subscribe,getState等方法共享,我們設定的reducer,redux的state狀態,以及state改變之後應該自動觸發哪些函式,這些邏輯都是通過這幾個內部變數和函式來實現的,下面先來看一下幾個核心方法,由我們直接接觸到的dispath開始~
dispacth處理action
function dispatch(action) {
...
try {
//將flag置為true,表明處於分發邏輯中
isDispatching = true
//currentReducer即為函入的reducer函式,這裡會自動呼叫currentReducer函式,並將返回值賦給currentState
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//呼叫監聽函式
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
複製程式碼
可以看到,在dispath中呼叫reducer方法處理action之後,返回值(新的state)會直接賦值給currentState
,由此可以推測currentState
應該就是getState
要返回的狀態
getState返回當前的狀態
function getState() {
if (isDispatching) {
throw new Error(
...
)
}
return currentState //直接返回currentState內部變數
}
複製程式碼
subscribe訂閱狀態
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(...)
}
if (isDispatching) {
throw new Error(
...
)
}
let isSubscribed = true
...
nextListeners.push(listener)
return function unsubscribe() {
...
}
複製程式碼
在subscribe中,傳入的listener函式會被新增進nextListeners陣列中,當dispatch方法被呼叫時自動觸發,react-redux的狀態更新時,UI自動更新的特性是通過subscribe來實現的
通過redux來管理react的狀態
首先,設想我們不知道react-redux庫來連線react和redux,來試一下單純的通過redux作為react元件的狀態管理器
建立store
//首先,建立一個reducer處理函式
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return ...
case ADD_TODO:
return ...
default:
return state
}
}
//建立store
const store = createStore(
todoApp
);
複製程式碼
各元件之間共享store的狀態
要在各元件之間共享變數store有兩種方式可取,一種是通過props共享,一種是通過context實現,其中props共享變數需要每一層元件之間層層傳遞該變數,這樣做無疑很麻煩,尤其是元件之前巢狀層次比較深的時候,所以我們這裡用react的context屬性來實現共享store
根元件提供context
class App extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return <ToDoList />;
}
}
App.childContextTypes = {
store: React.PropTypes.object
}
複製程式碼
子元件獲取context
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
複製程式碼
如上所示,在需要獲取store狀態的元件中,在元件載入完成後需要獲取關心得context的變數值store,同時訂閱事件,當store的狀態變化後觸發元件本身的強制更新,而render中只需用store.getState
獲取store的狀態值即可
用react-redux來簡化元件的寫法
上例中寫了一個元件的實現還好,但當元件多的時候,每個元件都需寫自己的獲取context,訂閱事件強制更新自身,獲取state,這樣的樣板程式碼,實際是沒必要的,完全可以把這部分抽象出來,而react-redux就幫我們做了這些,讓我們省去了自定義context和訂閱事件,獲取state等操作
省去自定義含有context屬性的根元件
要利用redux
來管理狀態,需要在祖先元件的context
屬性中指定store
,而這一定式化的操作可以有react-redux
庫中的Provider來完成
,示例如下
<Provider store={store}>
<App />
</Provider>
複製程式碼
省去手動訂閱store的狀態變化事件
上節中提到過,要實現store的狀態更新後能自動更新react元件,則元件需在掛載後呼叫store
的subscribe
方法來訂閱store
中狀態的變更,而這塊兒樣板程式碼則可以由react-redux
庫中的connect
建立的容器元件來自動完成
class TodoList extends Component {
render(){
...
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
複製程式碼
其中mapStateToProps提供從Redux store state到展示元件的 props的對映 ,mapDispatchToProps接收dispatch方法並提供期望注入到展示元件的 props 中的回撥方法。