react-redux
比較簡單的去理解react-redux https://www.jianshu.com/p/7a71181a7aa0
redux原理分析 https://segmentfault.com/a/1190000012976767
react + redux 完整的項目,同時寫一下個人感悟(不是我的 233)
https://segmentfault.com/a/1190000007642740
redux官方文檔(中文)http://www.redux.org.cn/docs/introduction/Motivation.html
react-redux 中文 https://segmentfault.com/a/1190000017064759?utm_source=tag-newest
注:redux和react-redux是不同的,所以網上看到的Store提供的方法(像getState()、subscribe())都被封裝起來了
React-redux 就是把 Redux 這種架構模式和 React.js 結合起來的一個庫,就是 Redux 架構在 React.js 中的體現
React.js的context
這一節的內容其實是講一個react當中一個你可能永遠用不到的特性——context,但是它對你理解react-redux很有好處。那麼context是幹什麼的呢?看下圖:
假設現在這個組件樹代表的應用是用戶可以自主換主題色的,每個子組件會根據主題色的不同調整自己的字體顏色。“主題色”這個狀態是所有組件共享的狀態,根據狀態提升中所提到的,需要把這個狀態提升到根節點的 Index 上,然後把這個狀態通過 props 一層層傳遞下去:
如果要改變主題色,在 Index 上可以直接通過 this.setState({ themeColor: 'red' }) 來進行。這樣整顆組件樹就會重新渲染,子組件也就可以根據重新傳進來的 props.themeColor 來調整自己的顏色。
但這裏的問題也是非常明顯的,我們需要把 themeColor 這個狀態一層層手動地從組件樹頂層往下傳,每層都需要寫 props.themeColor。如果我們的組件樹很層次很深的話,這樣維護起來簡直是災難。
如果這顆組件樹能夠全局共享這個狀態就好了,我們要的時候就去取這個狀態,不用手動地傳:
就像這樣,Index 把 state.themeColor 放到某個地方,這個地方是每個 Index 的子組件都可以訪問到的。當某個子組件需要的時候就直接去那個地方拿就好了,而不需要一層層地通過 props 來獲取。不管組件樹的層次有多深,任何一個組件都可以直接到這個公共的地方提取 themeColor 狀態。
React.js 的 context 就是這麼一個東西,某個組件只要往自己的 context 裏面放了某些狀態,這個組件之下的所有子組件都直接訪問這個狀態而不需要通過中間組件的傳遞。一個組件的 context 只有它的子組件能夠訪問。(redux的功能其實和context一樣啦)
Redux基本原則
單一數據源
整個應用的 state 被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個 store 中。
這讓同構應用開發變得非常容易。來自服務端的 state 可以在無需編寫更多代碼的情況下被序列化並注入到客戶端中。由於是單一的 state tree ,調試也變得非常容易。在開發中,你可以把應用的 state 保存在本地,從而加快開發速度。此外,受益於單一的 state tree ,以前難以實現的如“撤銷/重做”這類功能也變得輕而易舉。
State 是隻讀的
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。
這樣確保了視圖和網絡請求都不能直接修改 state,相反它們只能表達想要修改的意圖。因爲所有的修改都被集中化處理,且嚴格按照一個接一個的順序執行,因此不用擔心 race condition 的出現。 Action 就是普通對象而已,因此它們可以被日誌打印、序列化、儲存、後期調試或測試時回放出來。
使用純函數來執行修改
爲了描述 action 如何改變 state tree ,你需要編寫 reducers。
Reducer 只是一些純函數,它接收先前的 state 和 action,並返回新的 state。剛開始你可以只有一個 reducer,隨着應用變大,你可以把它拆成多個小的 reducers,分別獨立地操作 state tree 的不同部分,因爲 reducer 只是函數,你可以控制它們被調用的順序,傳入附加數據,甚至編寫可複用的 reducer 來處理一些通用任務,如分頁器。
Redux數據流程圖
Redux由三部分組成:store、reducer、action。
簡單寫一下三部分的作用
store是一個對象,它有四個主要的方法:
1、dispatch:
用於action的分發——在createStore中可以用middleware中間件對dispatch進行改造,比如當action傳入dispatch會立即觸發reducer,有些時候我們不希望它立即觸發,而是等待異步操作完成之後再觸發,這時候用redux-thunk對dispatch進行改造,以前只能傳入一個對象,改造完成後可以傳入一個函數,在這個函數裏我們手動dispatch一個action對象,這個過程是可控的,就實現了異步。
2、subscribe:
監聽state的變化——這個函數在store調用dispatch時會註冊一個listener監聽state變化,當我們需要知道state是否變化時可以調用,它返回一個函數,調用這個返回的函數可以註銷監聽。
let unsubscribe = store.subscribe(() => {console.log('state發生了變化')})
3、getState:
獲取store中的state——當我們用action觸發reducer改變了state時,需要再拿到新的state裏的數據,畢竟數據纔是我們想要的。getState主要在兩個地方需要用到,一是在dispatch拿到action後store需要用它來獲取state裏的數據,並把這個數據傳給reducer,這個過程是自動執行的,二是在我們利用subscribe監聽到state發生變化後調用它來獲取新的state數據,如果做到這一步,說明我們已經成功了。
4、replaceReducer:
替換reducer,改變state修改的邏輯。
store可以通過createStore()方法創建,接受三個參數,經過combineReducers合併的reducer和state的初始狀態以及改變dispatch的中間件,後兩個參數並不是必須的。store的主要作用是將action和reducer聯繫起來並改變state。
action:
action是一個對象,其中type屬性是必須的,同時可以傳入一些數據。action可以用actionCreactor進行創造。dispatch就是把action對象發送出去。
reducer:
reducer是一個函數,它接受一個state和一個action,根據action的type返回一個新的state。根據業務邏輯可以分爲很多個reducer,然後通過combineReducers將它們合併,state樹中有很多對象,每個state對象對應一個reducer,state對象的名字可以在合併時定義。
像這個樣子:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
combineReducers:
其實它也是一個reducer,它接受整個state和一個action,然後將整個state拆分發送給對應的reducer進行處理,所有的reducer會收到相同的action,不過它們會根據action的type進行判斷,有這個type就進行處理然後返回新的state,沒有就返回默認值,然後這些分散的state又會整合在一起返回一個新的state樹。
接下來分析一下整體的流程,首先調用store.dispatch將action作爲參數傳入,同時用getState獲取當前的狀態樹state並註冊subscribe的listener監聽state變化,再調用combineReducers並將獲取的state和action傳入。combineReducers會將傳入的state和action傳給所有reducer,reducer會根據state的key值獲取與自己對應的state,並根據action的type返回新的state,觸發state樹的更新,我們調用subscribe監聽到state發生變化後用getState獲取新的state數據。
redux的state和react的state兩者完全沒有關係,除了名字一樣。
以下開始介紹react-redux
UI 組件和容器組件
UI 組件有以下幾個特徵。
- 只負責 UI 的呈現,不帶有任何業務邏輯
- 沒有狀態(即不使用this.state這個變量)
- 所有數據都由參數(this.props)提供
- 不使用任何 Redux 的 API
容器組件的特徵恰恰相反。
- 負責管理數據和業務邏輯,不負責 UI 的呈現
- 帶有內部狀態
- 使用 Redux 的 API
React-Redux 規定,所有的 UI 組件都由用戶提供,容器組件則是由 React-Redux 自動生成。也就是說,用戶負責視覺層,狀態管理則是全部交給它。
connect()
React-Redux 提供connect方法,用於從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
上面代碼中,TodoList是 UI 組件,VisibleTodoList就是由 React-Redux 通過connect方法自動生成的容器組件。
但是,因爲沒有定義業務邏輯,上面這個容器組件毫無意義,只是 UI 組件的一個單純的包裝層。爲了定義業務邏輯,需要給出下面兩方面的信息。
(1)輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數
(2)輸出邏輯:用戶發出的動作如何變爲 Action 對象,從 UI 組件傳出去。
因此,connect方法的完整 API 如下。
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
上面代碼中,connect方法接受兩個參數:mapStateToProps和mapDispatchToProps。它們定義了 UI 組件的業務邏輯。前者負責輸入邏輯,即將state映射到 UI 組件的參數(props),後者負責輸出邏輯,即將用戶對 UI 組件的操作映射成 Action。
mapStateToProps()
mapStateToProps是一個函數。它的作用就是像它的名字那樣,建立一個從(外部的)state對象到(UI 組件的)props對象的映射關係。
作爲函數,mapStateToProps執行後應該返回一個對象,裏面的每一個鍵值對就是一個映射。請看下面的例子。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
上面代碼中,mapStateToProps是一個函數,它接受state作爲參數,返回一個對象。這個對象有一個todos屬性,代表 UI 組件的同名參數,後面的getVisibleTodos也是一個函數,可以從state算出 todos 的值。
下面就是getVisibleTodos的一個例子,用來算出todos。
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,重新計算 UI 組件的參數,從而觸發 UI 組件的重新渲染。
mapStateToProps的第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
// 容器組件的代碼
// <FilterLink filter="SHOW_ALL">
// All
// </FilterLink>
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
使用ownProps作爲參數後,如果容器組件的參數發生變化,也會引發 UI 組件重新渲染。
connect方法可以省略mapStateToProps參數,那樣的話,UI 組件就不會訂閱Store,就是說 Store 的更新不會引起 UI 組件的更新。
mapDispatchToProps()
mapDispatchToProps是connect函數的第二個參數,用來建立 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當作 Action,傳給 Store。它可以是一個函數,也可以是一個對象。
如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
從上面代碼可以看到,mapDispatchToProps作爲函數,應該返回一個對象,該對象的每個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。
如果mapDispatchToProps是一個對象,它的每個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。舉例來說,上面的mapDispatchToProps寫成對象就是下面這樣。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
<Provider> 組件
connect方法生成容器組件以後,需要讓容器組件拿到state對象,才能生成 UI 組件的參數。
一種解決方法是將state對象作爲參數,傳入容器組件。但是,這樣做比較麻煩,尤其是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。
React-Redux 提供Provider組件,可以讓容器組件拿到state。
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')
)
上面代碼中,Provider在根組件外面包了一層,這樣一來,App的所有子組件就默認都可以拿到state了。
它的原理是React組件的context屬性,請看源碼。
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
上面代碼中,store放在了上下文對象context上面。然後,子組件就可以從context拿到store,代碼大致如下。
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
}