注:這篇是16年10月的文章,搬運自本人 blog...
https://github.com/BuptStEve/...javascript
參考資料html
首先要明確一點,雖然 redux 是由 flux 演變而來,但咱們徹底能夠而且也應該拋開 react 進行學習,這樣能夠避免一開始就陷入各類細節之中。前端
因此推薦使用 jsbin 進行調試學習,或者使用 create-react-app 做爲項目腳手架。java
Redux is a predictable state container for JavaScript apps.
Redux 是一個 JavaScript 狀態容器,提供可預測化的狀態管理。
先不要在乎那些細節node
純函數:簡單的說就是對於一樣的輸入老是返回一樣的輸出,而且沒有反作用的函數。(推薦學習瞭解下函數式編程)
- 隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 須要管理比任什麼時候候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成還沒有持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。
- 管理不斷變化的 state 很是困難。若是一個 model 的變化會引發另外一個 model 變化,那麼當 view 變化時,就可能引發對應 model 以及另外一個 model 的變化,依次地,可能會引發另外一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在何時,因爲什麼緣由,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。
- 若是這還不夠糟糕,考慮一些來自前端開發領域的新需求,如更新調優、服務端渲染、路由跳轉前請求數據等等。前端開發者正在經受史無前例的複雜性,難道就這麼放棄了嗎?固然不是。
- 這裏的複雜性很大程度上來自於:咱們老是將兩個難以釐清的概念混淆在一塊兒:變化和異步。 我稱它們爲曼妥思和可樂。若是把兩者分開,能作的很好,但混到一塊兒,就變得一團糟。一些庫如 React 試圖在視圖層禁止異步和直接操做 DOM 來解決這個問題。美中不足的是,React 依舊把處理 state 中數據的問題留給了你。Redux就是爲了幫你解決這個問題。
- 跟隨 Flux、CQRS 和 Event Sourcing 的腳步,經過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測。這些限制條件反映在 Redux 的 三大原則中。
簡單總結就是使用 Redux 咱們就能夠沒有蛀牙(大霧)react
整個應用的 state 被儲存在一棵 object tree 中,而且這個 object tree 只存在於惟一一個 store 中。git
唯一改變 state 的方法就是觸發 action,action 是一個用於描述已發生事件的普通對象。es6
由於全部的修改都被集中化處理,且嚴格按照一個接一個的順序執行,(dispatch 同步調用 reduce 函數)所以不用擔憂 race condition 的出現。 Action 就是普通對象而已,所以它們能夠被日誌打印、序列化、儲存、後期調試或測試時回放出來。github
爲了描述 action 如何改變 state tree ,你須要編寫 reducer。編程
Reducer 只是純函數,它接收先前的 state 和 action,並返回新的 state。剛開始你能夠只有一個 reducer,隨着應用變大,你能夠把它拆成多個小的 reducers,分別獨立地操做 state tree 的不一樣部分。
Action 就是一個普通的 JavaScript Object。
redux 惟一限制的一點是必須有一個 type 屬性用來表示執行哪一種操做,值最好用字符串,而不是 Symbols,由於字符串是可被序列化的。
其餘屬性用來傳遞這次操做所需傳遞的數據,redux 對此不做限制,可是在設計時能夠參照 Flux 標準 Action。
簡單總結 Flux Standard action 就是
- 一個 action 必須是一個 JavaScript Object,而且有一個 type 屬性。
- 一個 action 能夠有 payload/error/meta 屬性。
- 一個 action 不能有其餘屬性。
Reducer 的工做就是接收舊的 state 和 action,返回新的 state。
(previousState, action) => newState
之因此稱做 reducer 是由於它將被傳遞給 Array.prototype.reduce(reducer, ?initialValue)
方法。保持 reducer 純淨很是重要。永遠不要在 reducer 裏作這些操做:
Store 就是用來維持應用全部的 state 樹的一個對象。
在 redux 中只有一個 store(區別於 flux 的多個 store),在 store 中保存全部的 state,能夠把它當成一個封裝了 state 的類。而除了對其 dispatch 一個 action 之外沒法改變內部的 state。
在實際操做中咱們只須要把根部的 reducer 函數傳遞給 createStore 就能夠獲得一個 store。
import { createStore } from 'redux'; function reducer(state, action) { switch (action.type) { case 'SOME_ACTION': // 一些操做 return newState; // 返回新狀態 default: return state; } } const store = createStore(reducer);
redux 中提供了這幾個 api 操做 store
返回當前的整個 state 樹。
分發 action 給對應的 reducer。
該函數會調用 getState() 和傳入的 action 以【同步】的方式調用 store 的 reduce 函數,而後返回新的 state。從而 state 獲得了更新,而且變化監聽器(change listener)會被觸發。(對於異步操做則將其放到了 action creator 這個步驟)
爲 store 添加一個變化監聽器,每當 dispatch 的時候就會執行,你能夠在 listener(回調函數)中使用 getState() 來獲得當前的 state。
這個 api 設計的挺有意思,它會返回一個函數,而你執行這個函數後就能夠取消訂閱。
替換 store 當前用來計算 state 的 reducer。
這是一個高級 API。只有在你須要實現代碼分隔,並且須要當即加載一些 reducer 的時候纔可能會用到它。在實現 Redux 熱加載機制的時候也可能會用到。
忽略各類類型判斷,實現一個最簡的 createStore 能夠用如下代碼。參考資料
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); // 調用 reducer listeners.forEach(listener => listener()); // 調用全部變化監聽器 }; const subscribe = (listener) => { listeners.push(listener); return () => { // 返回解除監聽函數 listeners = listeners.filter(l => l !== listener); }; } dispatch({}); // 初始化 return { getState, dispatch, subscribe }; };
{% iframe http://jsbin.com/kejezih/edit... 100% 600 %}
{% iframe http://jsbin.com/jihara/edit?... 100% 600 %}
實現一樣功能的 Counter
{% iframe http://jsbin.com/qalevu/edit?... 100% 800 %}
在添加 react-redux 以前,爲了體會下 react-redux 的做用,首先來實現一個比計數器更復雜一點兒的 TodoApp 栗子~
組件通常分爲
- | 容器組件 | 展現組件 |
---|---|---|
Location | 最頂層,路由處理 | 中間和子組件 |
Aware of Redux | 是 | 否 |
讀取數據 | 從 Redux 獲取 state | 從 props 獲取數據 |
修改數據 | 向 Redux 派發 actions | 從 props 調用回調函數 |
最佳實踐通常是由容器組件負責一些數據的獲取,進行 dispatch 等操做。而展現組件組件不該該關心邏輯,全部數據都經過 props 傳入。
這樣才能達到展現組件能夠在多處複用,在具體複用時就是經過容器組件將其包裝,爲其提供所需的各類數據。
一個 TodoApp 包含了三個部分:
State 應該包含:
filter:過濾 todos 的條件
todos:全部的 todo
然而傳到應用中的 props 只須要:
Action 應該有三種:
// 暫且使用數字做爲 id let nextTodoId = 0; /*-- action creators --*/ const addTodo = (text) => ( { type: 'ADD_TODO', id: nextTodoId++, text } ); const toggleTodo = (id) => ( { type: 'TOGGLE_TODO', id } ); const setVisibilityFilter = (filter) => ( { type: 'SET_VISIBILITY_FILTER', filter } );
// 默認初始狀態 const initialState = { filter: 'SHOW_ALL', todos: [] }; function rootReducer(state = initialState, action) { switch (action.type) { case 'ADD_TODO': // 對象解構 const { id, text } = action; return { ...state, todos: [ ...state.todos, { id, text, completed: false }, ], }; case 'TOGGLE_TODO': return { ...state, todos: state.todos.map(todo => { if (todo.id !== action.id) return todo; return { ...todo, completed: !todo.completed, }; }), }; case 'SET_VISIBILITY_FILTER': return { ...state, filter: action.filter, }; default: return state; } }
注意!
- 不要直接修改原有的 state,而是返回一個新的 state。可使用 Object.assign() 新建一個新的 state。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),由於它會改變第一個參數的值。你必須把第一個參數設置爲空對象。你也能夠開啓對 ES7 提案對象展開運算符的支持, 從而使用 { ...state, ...newState } 達到相同的目的。
- 在 default 的狀況下返回舊的 state,用來兼容遇到未知的 action 這樣的錯誤。
拆分 reducer
目前代碼看着比較冗長,其實在邏輯上 todos 的處理和 filter 的處理應該分開,因此在 state 沒有互相耦合時,能夠將其拆分,從而讓 reducer 精細地對於對應 state 的子樹進行處理。
// 處理單個 todo const todoReducer = (state, action) => { switch (action.type) { case 'ADD_TODO': return { id: action.id, text: action.text, completed: false, }; case 'TOGGLE_TODO': if (state.id !== action.id) return state; return { ...state, completed: !state.completed, }; default: return state; } }; // 處理 todos const todosReducer = (state = [], action) => { switch (action.type) { case 'ADD_TODO': return [ ...state, todoReducer(undefined, action), ]; case 'TOGGLE_TODO': return state.map(t => todoReducer(t, action)); default: return state; }; }; // 處理 filter const filterReducer = (state = 'SHOW_ALL', action) => { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter; default: return state; }; }; const rootReducer = (state = initialState, action) => ({ todos: todosReducer(state.todos, action), filter: filterReducer(state.filter, action), });
注意觀察最後的 rootReducer 函數,返回的是一個通過各類 reducer 處理過併合並後的新 state。
然鵝,注意這裏 todos: todos(state.todos, action),
傳入 state.todos,返回的必定也是 todos(由於都是 state 樹上的節點)。
因此 redux 提供了很實用的 combineReducers
api,用於簡化 reducer 的合併。
import { combineReducers } from 'redux'; const rootReducer = combineReducers({ todos: todosReducer, filter: filterReducer, }); // initialState 能夠做爲第二個參數傳入 const store = createStore(rootReducer, initialState);
而且若是 reducer 與 state 節點同名的話(即 todosReducer -> todos)還能經過 es6 的語法更進一步地簡化
import { combineReducers } from 'redux'; const rootReducer = combineReducers({ todos, filter }); // initialState 能夠做爲第二個參數傳入 const store = createStore(rootReducer, initialState);
隨着應用的膨脹,咱們還能夠將拆分後的 reducer 放到不一樣的文件中, 以保持其獨立性並用於專門處理不一樣的數據域。
首先只寫一個根組件 <TodoApp />
,store 經過 props 傳入 TodoApp,並在生命週期的 componentDidMount 和 componentWillUnmount 時分別訂閱與取消訂閱。
import React, { Component } from 'react'; class TodoApp extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.props; this.unsubscribe = store.subscribe( this.forceUpdate.bind(this) ); } // 取消訂閱 componentWillUnmount() { this.unsubscribe(); } // 渲染單個 todo _renderTodo(todo) { const { store } = this.props; return ( <li key={todo.id} onClick={() => store.dispatch(toggleTodo(todo.id))} style={{ textDecoration: todo.completed ? 'line-through' : 'none', cursor: todo.completed ? 'default' : 'pointer', }} > {todo.text} </li> ); } // 根據當前 filter 是否匹配,返回字符串或是 a 連接 _renderFilter(renderFilter, name) { const { store } = this.props; const { filter } = store.getState(); if (renderFilter === filter) return name; return ( <a href='#' onClick={e => { e.preventDefault(); store.dispatch(setVisibilityFilter(renderFilter)) }}> {name} </a> ); } // 根據當前 filter 過濾須要渲染的 todos _getVisibleTodos(todos, filter) { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter(todo => todo.completed); case 'SHOW_ACTIVE': return todos.filter(todo => !todo.completed); default: return todos; } } render() { const { store } = this.props; const { todos, filter } = store.getState(); let input; return ( <div> {/* AddTodo */} <input type="text" ref={node => input = node} /> <button onClick={() => { if (!input.value) return; store.dispatch(addTodo(input.value)); input.value = ''; }}> addTodo </button> {/* TodoList */} <ul> {this._getVisibleTodos(todos, filter) .map(this._renderTodo.bind(this)) } </ul> {/* Footer */} <p> Show: {' '} {this._renderFilter('SHOW_ALL', 'all')} {', '} {this._renderFilter('SHOW_COMPLETED', 'completed')} {', '} {this._renderFilter('SHOW_ACTIVE', 'active')} </p> </div> ); } }
TodoApp 只有根組件
{% iframe http://jsbin.com/bodise/edit?... 100% 800 %}
將全部界面內容全寫在 TodoApp 中實在是太臃腫了,接下來根據以前的分析結果將其分爲如下子組件(全是展現組件)
TodoList
Footer
const AddTodo = ({ onAddClick }) => { let input; return ( <div> <input type="text" ref={node => input = node} /> <button onClick={() => { onAddClick(input.value); input.value = ''; }}> addTodo </button> </div> ); }; const Todo = ({ text, onClick, completed }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none', cursor: completed ? 'default' : 'pointer', }} > {text} </li> ); const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ); const FilterLink = ({ filter, onClick, renderFilter, children }) => { if (renderFilter === filter) return (<span>{children}</span>); return ( <a href='#' onClick={e => { e.preventDefault(); onClick(renderFilter); }}> {children} </a> ); }; const Footer = ({ filter, onFilterClick }) => ( <p> Show: {' '} <FilterLink filter={filter} renderFilter="SHOW_ALL" onClick={onFilterClick} > all </FilterLink> {', '} <FilterLink filter={filter} renderFilter="SHOW_COMPLETED" onClick={onFilterClick} > completed </FilterLink> {', '} <FilterLink filter={filter} renderFilter="SHOW_ACTIVE" onClick={onFilterClick} > active </FilterLink> </p> );
因此 TodoApp 精簡後是這樣~
class TodoApp extends Component { // ... render() { const { store } = this.props; const { todos, filter } = store.getState(); return ( <div> <AddTodo onAddClick={text => { if (!text) return; store.dispatch(addTodo(text)); }} /> <TodoList todos={this._getVisibleTodos(todos, filter)} onTodoClick={id => store.dispatch(toggleTodo(id))} /> <Footer filter={filter} onFilterClick={filter => { store.dispatch(setVisibilityFilter(filter)); }} /> </div> ); } }
如今咱們仍然是以 TodoApp 做爲容器組件,其中各個子組件都是展現組件。
可是這樣作的話一旦子組件須要某個屬性,就須要從根組件層層傳遞下來,好比 FilterLink 中的 filter 屬性。
因此下面咱們增長容器組件,讓展現組件經過容器組件得到所需屬性。
VisibleTodoList(container)
TodoList
Footer
FilterLink(container)
// store.dispatch 又被放回來了, // 由於暫時咱們只在 AddTodo 組件中使用 addTodo 這個 action // 之後增長了新的 form 以後能夠考慮再將 store.dispatch 移出去 const AddTodo = ({ store }) => { let input; return ( <div> <input type="text" ref={node => input = node} /> <button onClick={() => { if (!input.value) return; store.dispatch(addTodo(input.value)); input.value = ''; }}> addTodo </button> </div> ); }; const Todo = ({ text, onClick, completed }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none', cursor: completed ? 'default' : 'pointer', }} > {text} </li> ); const TodoList = ({ todos, onTodoClick }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ); // 容器組件 class VisibleTodoList extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.props; this.unsubscribe = store.subscribe( this.forceUpdate.bind(this) ); } // 取消訂閱 componentWillUnmount() { this.unsubscribe(); } // 根據當前 filter 過濾須要渲染的 todos _getVisibleTodos(todos, filter) { switch (filter) { case 'SHOW_ALL': return todos; case 'SHOW_COMPLETED': return todos.filter(todo => todo.completed); case 'SHOW_ACTIVE': return todos.filter(todo => !todo.completed); default: return todos; } } render() { const { store } = this.props; const { todos, filter } = store.getState(); return ( <TodoList todos={this._getVisibleTodos(todos, filter)} onTodoClick={id => { store.dispatch(toggleTodo(id)) }} /> ); } } // 本來的 FilterLink 改爲 Link,去掉 filter 和 renderFilter 屬性,改成傳入 active const Link = ({ active, onClick, children }) => { if (active) return (<span>{children}</span>); return ( <a href='#' onClick={e => { e.preventDefault(); onClick(); }}> {children} </a> ); }; // 容器組件 class FilterLink extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.props; this.unsubscribe = store.subscribe( this.forceUpdate.bind(this) ); } // 取消訂閱 componentWillUnmount() { this.unsubscribe(); } render() { const { store, renderFilter, children } = this.props; const { filter } = store.getState(); return ( <Link active={filter === renderFilter} onClick={() => store.dispatch( setVisibilityFilter(renderFilter) )} > {children} </Link> ); } } // 展現組件 const Footer = ({ store }) => ( <p> Show: {' '} <FilterLink store={store} renderFilter="SHOW_ALL" > all </FilterLink> {', '} <FilterLink store={store} renderFilter="SHOW_COMPLETED" > completed </FilterLink> {', '} <FilterLink store={store} renderFilter="SHOW_ACTIVE" > active </FilterLink> </p> ); // 在不使用全局變量 store 的狀況下, // 暫時只能經過 props 傳遞進來, // Don't worry~很快就不會這麼麻煩了~ const TodoApp = ({ store }) => ( <div> <AddTodo store={store} /> <VisibleTodoList store={store} /> <Footer store={store} /> </div> );
經過觀察重構後的代碼能夠發現有三點麻煩的地方
_getVisibleTodos
函數讓咱們先來解決第一個麻煩~,利用 React 提供的 context 特性
class Provider extends Component { // 經過該方法向 children 的 context 注入 store getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } // 必需要聲明傳入 context 的 store 的類型 Provider.childContextTypes = { store: React.PropTypes.object, };
自頂向下地看一下如何使用到 TodoApp 中
// 1. 使用 Provider 包裹 TodoApp,並將 store 做爲 props 傳入 ReactDOM.render( <Provider store={createStore(rootReducer, initialState)}> <TodoApp /> </Provider>, document.getElementById('container'), ); // 2. 根組件 TodoApp: 和 store say goodbye~, // 由於 TodoApp 並非容器組件~ const TodoApp = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> </div> ); // 3. AddTodo: 因爲 props 固定做爲第一個傳入子組件的參數, // 因此 { store } 要聲明在第二位,然鵝須要聲明 contextTypes... const AddTodo = (props, { store }) => { // ... }; // 必須聲明 AddTodo.contextTypes = { store: React.PropTypes.object, }; // 4. VisibleTodoList: 從 props 改爲從 context 中獲取 store, // 一樣聲明 contextTypes... class VisibleTodoList extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.context; // props -> context // ... } // ... render() { const { store } = this.context; // props -> context const { todos, filter } = store.getState(); // ... } } // 必須聲明 VisibleTodoList.contextTypes = { store: React.PropTypes.object, }; // -- TodoList 和 Todo 不變 -- // 5. Footer:和 store say goodbye... const Footer = () => ( <p> Show: {' '} <FilterLink renderFilter="SHOW_ALL"> all </FilterLink> {', '} <FilterLink renderFilter="SHOW_COMPLETED"> completed </FilterLink> {', '} <FilterLink renderFilter="SHOW_ACTIVE"> active </FilterLink> </p> ); // 6. FilterLink: 同 VisibleTodoList(props + contextTypes...) class FilterLink extends Component { // 訂閱 store 的變化 componentDidMount() { const { store } = this.context; // props -> context // ... } // ... render() { const { renderFilter, children } = this.props; const { store } = this.context; // props -> context const { filter } = store.getState(); // ... } } // 必須聲明 FilterLink.contextTypes = { store: React.PropTypes.object, }; // -- Link 不變 --
如今中間的非容器組件徹底不用爲了本身的孩子而費勁地傳遞 store={store}
因此以上咱們就實現了簡化版的由 react-redux 提供的第一個組件 <Provider />
。
然鵝,有木有以爲老寫 contextTypes 好煩啊,並且 context 特性並不穩定,因此 context 並不該該直接寫在咱們的應用代碼裏。
計將安出?
恭喜你~面向對象的思想學的很不錯~
雖然 JavaScript 底層各類東西都是面向對象,然而在前端一旦與界面相關,照搬面向對象的方法實現起來會很麻煩...
做爲 react 親生的 mixin 確實在多組件間共享方法提供了一些便利,然而使用 mixin 的組件須要瞭解細節,從而避免狀態污染,因此一旦 mixin 數量多了以後會愈來愈難維護。
Unfortunately, we will not launch any mixin support for ES6 classes in React. That would defeat the purpose of only using idiomatic JavaScript concepts.
因此官方也放棄了在 ES6 class 中對 mixin 的支持。
hocFactory:: W: React.Component => E: React.Component
如上所示 hoc 的構造函數接收一個 W(表明 WrappedComponent)返回一個 E(表明 Enhanced Component),而 E 就是這個高階組件。
假設咱們有一箇舊組件 Comp,然鵝如今接收參數有些變更。
固然你能夠複製粘貼再修改舊組件的代碼...(大俠受窩一拜)
也能夠這麼寫,返回一個新組件來包裹舊組件。
class NewComp extends Component { mapProps(props) { return {/* new props */}; } render() { return (<Comp {...this.mapProps(this.props)} />); } }
然鵝,若是有一樣邏輯的更多的組件須要適配呢???總不能有幾個抄幾遍吧...
因此騷年你據說太高階組件麼~?
// 先返回一個函數,而那個函數再返回新組件 const mapProps = mapFn => Comp => { return class extends Component { render() { return (<Comp {...this.mapFn(this.props)} />); } }; }; const NewComp = mapProps(mapFn)(Comp); // 注意調用了兩次
能夠看到藉助高階組件咱們將 mapFn 和 Comp 解耦合,這樣就算須要再嵌套多少修改邏輯都沒問題~天黑都不怕~
ok,扯了這麼多的淡,終於要說到 connect 了
是噠,你木有猜錯,react-redux 提供的第二個也是最後一個 api —— connect 返回的就是一個高階組件。
使用的時候只須要 connect()(WrappedComponent)
返回的 component 自動就完成了在 componentDidMount 中訂閱 store,在 componentWillUnmount 中取消訂閱和聲明 contextTypes。
這樣就只剩下最後一個麻煩
3.應用其實並不須要渲染全部的 todos,因此內部很麻煩地定義了
_getVisibleTodos
函數
其實 connect 函數的第一個參數叫作 mapStateToProps,做用就是將 store 中的數據提早處理或過濾後做爲 props 傳入內部組件,以便內部組件高效地直接調用。這樣最後一個麻煩也解決了~
然鵝,咱們問本身這樣就夠了麼?並無...
還有最後一個細節,以 FilterLink 爲例。
class FilterLink extends Component { // ... render() { const { store, renderFilter, children } = this.props; const { filter } = store.getState(); return ( <Link active={filter === renderFilter} onClick={() => store.dispatch( setVisibilityFilter(renderFilter) )} > {children} </Link> ); } }
除了從 store 中獲取數據(filter),咱們還從中獲取了 dispatch,以便觸發 action。若是將回調函數 onClick 的內容也加到 props 中,那麼藉助 connect 整個 FilterLink 的邏輯豈不是都被咱們抽象完了?
是噠,connect 的第二個參數叫作 mapDispatchToProps,做用就是將各個調用到 dispatch 的地方都抽象成函數加到 props 中的傳給內部組件。這樣最後一個麻煩終於真的被解決了~
const mapStateToLinkProps = (state, ownProps) => ({ // ownProps 是原組件的 props, // 這裏爲了和高階組件的 props 區分 active: ownProps.renderFilter === state.filter, }); const mapDispatchToLinkProps = (dispatch, ownProps) => ({ onClick() { dispatch( setVisibilityFilter(ownProps.renderFilter) ); }, }); // 注意原 FilterLink 整個都被咱們刪了 const FilterLink = connect( mapStateToLinkProps, mapDispatchToLinkProps )(Link);
TodoApp 使用 react-redux
{% iframe http://jsbin.com/fumihi/edit?... 100% 800 %}
本文從 Redux 的理論基礎和源碼出發,介紹了 Redux 的各項基礎 api。
接着一步一步地介紹如何與 React 進行結合,從過程當中的各個痛點引出 react-redux 的做用和原理。
然鵝,還有好多的坑沒填,好比:大型項目的文件結構、前端路由(react-router)、中間件(middlewares)、網絡請求等各種異步操做、服務器端同構直出...
以上 to be continued...