摘要:二基礎(chǔ)就是一個(gè)普通的。其他屬性用來傳遞此次操作所需傳遞的數(shù)據(jù),對(duì)此不作限制,但是在設(shè)計(jì)時(shí)可以參照標(biāo)準(zhǔn)。對(duì)于異步操作則將其放到了這個(gè)步驟為添加一個(gè)變化監(jiān)聽器,每當(dāng)?shù)臅r(shí)候就會(huì)執(zhí)行,你可以在回調(diào)函數(shù)中使用來得到當(dāng)前的。
注:這篇是16年10月的文章,搬運(yùn)自本人 blog...
https://github.com/BuptStEve/...
參考資料
英文原版文檔
中文文檔
墻裂推薦作者出的教學(xué)視頻 基礎(chǔ)篇
墻裂推薦作者出的教學(xué)視頻 高級(jí)篇
首先要明確一點(diǎn),雖然 redux 是由 flux 演變而來,但我們完全可以并且也應(yīng)該拋開 react 進(jìn)行學(xué)習(xí),這樣可以避免一開始就陷入各種細(xì)節(jié)之中。
所以推薦使用 jsbin 進(jìn)行調(diào)試學(xué)習(xí),或者使用 create-react-app 作為項(xiàng)目腳手架。
一、Redux 是什么?Redux is a predictable state container for JavaScript apps.
Redux 是一個(gè) JavaScript 狀態(tài)容器,提供可預(yù)測(cè)化的狀態(tài)管理。
先不要在意那些細(xì)節(jié)
總的來說,redux 使用 store 保存并管理頁(yè)面中的各種狀態(tài)(state)
當(dāng)需要改變 state 時(shí),使用 dispatch 調(diào)用 action creators 觸發(fā) action
接著使用純函數(shù)(pure function)reducer 來處理這些 action,它會(huì)根據(jù)當(dāng)前 state 和 action 返回(注意這里不是修改)新的 state
view 層可以對(duì)于 state 進(jìn)行訂閱(subscribe),這樣就可以得到新的 state,從而可以刷新界面(所以十分適合數(shù)據(jù)驅(qū)動(dòng)的前端框架)
純函數(shù):簡(jiǎn)單的說就是對(duì)于同樣的輸入總是返回同樣的輸出,并且沒有副作用的函數(shù)。(推薦學(xué)習(xí)了解下函數(shù)式編程)1.1. 為什么選擇 redux?
隨著 JavaScript 單頁(yè)應(yīng)用開發(fā)日趨復(fù)雜,JavaScript 需要管理比任何時(shí)候都要多的 state (狀態(tài))。 這些 state 可能包括服務(wù)器響應(yīng)、緩存數(shù)據(jù)、本地生成尚未持久化到服務(wù)器的數(shù)據(jù),也包括 UI 狀態(tài),如激活的路由,被選中的標(biāo)簽,是否顯示加載動(dòng)效或者分頁(yè)器等等。
管理不斷變化的 state 非常困難。如果一個(gè) model 的變化會(huì)引起另一個(gè) model 變化,那么當(dāng) view 變化時(shí),就可能引起對(duì)應(yīng) model 以及另一個(gè) model 的變化,依次地,可能會(huì)引起另一個(gè) view 的變化。直至你搞不清楚到底發(fā)生了什么。state 在什么時(shí)候,由于什么原因,如何變化已然不受控制。 當(dāng)系統(tǒng)變得錯(cuò)綜復(fù)雜的時(shí)候,想重現(xiàn)問題或者添加新功能就會(huì)變得舉步維艱。
如果這還不夠糟糕,考慮一些來自前端開發(fā)領(lǐng)域的新需求,如更新調(diào)優(yōu)、服務(wù)端渲染、路由跳轉(zhuǎn)前請(qǐng)求數(shù)據(jù)等等。前端開發(fā)者正在經(jīng)受前所未有的復(fù)雜性,難道就這么放棄了嗎?當(dāng)然不是。
這里的復(fù)雜性很大程度上來自于:我們總是將兩個(gè)難以厘清的概念混淆在一起:變化和異步。 我稱它們?yōu)槁姿己涂蓸贰H绻讯叻珠_,能做的很好,但混到一起,就變得一團(tuán)糟。一些庫(kù)如 React 試圖在視圖層禁止異步和直接操作 DOM 來解決這個(gè)問題。美中不足的是,React 依舊把處理 state 中數(shù)據(jù)的問題留給了你。Redux就是為了幫你解決這個(gè)問題。
跟隨 Flux、CQRS 和 Event Sourcing 的腳步,通過限制更新發(fā)生的時(shí)間和方式,Redux 試圖讓 state 的變化變得可預(yù)測(cè)。這些限制條件反映在 Redux 的 三大原則中。
簡(jiǎn)單總結(jié)就是使用 Redux 我們就可以沒有蛀牙(大霧)
擁有可預(yù)測(cè)(predictable)的應(yīng)用狀態(tài),所以應(yīng)用的行為也是可預(yù)測(cè)的
因?yàn)?reducer 是純函數(shù),所以方便對(duì)于狀態(tài)遷移進(jìn)行自動(dòng)化測(cè)試
方便地記錄日志,甚至實(shí)現(xiàn)時(shí)間旅行(time travel)
1.2. 三大原則(哲♂學(xué)) 1.2.1. 單一數(shù)據(jù)源(Single source of truth)整個(gè)應(yīng)用的 state 被儲(chǔ)存在一棵 object tree 中,并且這個(gè) object tree 只存在于唯一一個(gè) store 中。
來自服務(wù)端的 state 可以在無需編寫更多代碼的情況下被序列化并注入到客戶端中
便于調(diào)試,在開發(fā)時(shí)可以將狀態(tài)保存在本地
Undo/Redo 可以輕松實(shí)現(xiàn),從而實(shí)現(xiàn)時(shí)間旅行
1.2.2. State 是只讀的(State is read-only)惟一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象。
因?yàn)樗械男薷亩急患谢幚恚覈?yán)格按照一個(gè)接一個(gè)的順序執(zhí)行,(dispatch 同步調(diào)用 reduce 函數(shù))因此不用擔(dān)心 race condition 的出現(xiàn)。 Action 就是普通對(duì)象而已,因此它們可以被日志打印、序列化、儲(chǔ)存、后期調(diào)試或測(cè)試時(shí)回放出來。
1.2.3. 使用純函數(shù)來執(zhí)行修改(Changes are made with pure functions)為了描述 action 如何改變 state tree ,你需要編寫 reducer。
Reducer 只是純函數(shù),它接收先前的 state 和 action,并返回新的 state。剛開始你可以只有一個(gè) reducer,隨著應(yīng)用變大,你可以把它拆成多個(gè)小的 reducers,分別獨(dú)立地操作 state tree 的不同部分。
二、Redux 基礎(chǔ) 2.1. actionAction 就是一個(gè)普通的 JavaScript Object。
redux 唯一限制的一點(diǎn)是必須有一個(gè) type 屬性用來表示執(zhí)行哪種操作,值最好用字符串,而不是 Symbols,因?yàn)樽址强杀恍蛄谢摹?/p>
其他屬性用來傳遞此次操作所需傳遞的數(shù)據(jù),redux 對(duì)此不作限制,但是在設(shè)計(jì)時(shí)可以參照 Flux 標(biāo)準(zhǔn) Action。
簡(jiǎn)單總結(jié) Flux Standard action 就是
2.2. reducer一個(gè) action 必須是一個(gè) JavaScript Object,并且有一個(gè) type 屬性。
一個(gè) action 可以有 payload/error/meta 屬性。
一個(gè) action 不能有其他屬性。
Reducer 的工作就是接收舊的 state 和 action,返回新的 state。
(previousState, action) => newState
之所以稱作 reducer 是因?yàn)樗鼘⒈粋鬟f給 Array.prototype.reduce(reducer, ?initialValue) 方法。保持 reducer 純凈非常重要。永遠(yuǎn)不要在 reducer 里做這些操作:
修改傳入?yún)?shù);
執(zhí)行有副作用的操作,如 API 請(qǐng)求和路由跳轉(zhuǎn);
調(diào)用非純函數(shù),如 Date.now() 或 Math.random()。
2.3. storeStore 就是用來維持應(yīng)用所有的 state 樹的一個(gè)對(duì)象。
在 redux 中只有一個(gè) store(區(qū)別于 flux 的多個(gè) store),在 store 中保存所有的 state,可以把它當(dāng)成一個(gè)封裝了 state 的類。而除了對(duì)其 dispatch 一個(gè) action 以外無法改變內(nèi)部的 state。
在實(shí)際操作中我們只需要把根部的 reducer 函數(shù)傳遞給 createStore 就可以得到一個(gè) store。
import { createStore } from "redux"; function reducer(state, action) { switch (action.type) { case "SOME_ACTION": // 一些操作 return newState; // 返回新狀態(tài) default: return state; } } const store = createStore(reducer);
redux 中提供了這幾個(gè) api 操作 store
2.3.1. getState返回當(dāng)前的整個(gè) state 樹。
2.3.2. dispatch(action)分發(fā) action 給對(duì)應(yīng)的 reducer。
該函數(shù)會(huì)調(diào)用 getState() 和傳入的 action 以【同步】的方式調(diào)用 store 的 reduce 函數(shù),然后返回新的 state。從而 state 得到了更新,并且變化監(jiān)聽器(change listener)會(huì)被觸發(fā)。(對(duì)于異步操作則將其放到了 action creator 這個(gè)步驟)
2.3.3. subscribe(listener)為 store 添加一個(gè)變化監(jiān)聽器,每當(dāng) dispatch 的時(shí)候就會(huì)執(zhí)行,你可以在 listener(回調(diào)函數(shù))中使用 getState() 來得到當(dāng)前的 state。
這個(gè) api 設(shè)計(jì)的挺有意思,它會(huì)返回一個(gè)函數(shù),而你執(zhí)行這個(gè)函數(shù)后就可以取消訂閱。
2.3.4. replaceReducer(nextReducer)替換 store 當(dāng)前用來計(jì)算 state 的 reducer。
這是一個(gè)高級(jí) API。只有在你需要實(shí)現(xiàn)代碼分隔,而且需要立即加載一些 reducer 的時(shí)候才可能會(huì)用到它。在實(shí)現(xiàn) Redux 熱加載機(jī)制的時(shí)候也可能會(huì)用到。
2.4. createStore忽略各種類型判斷,實(shí)現(xiàn)一個(gè)最簡(jiǎn)的 createStore 可以用以下代碼。參考資料
const createStore = (reducer) => { let state; let listeners = []; const getState = () => state; const dispatch = (action) => { state = reducer(state, action); // 調(diào)用 reducer listeners.forEach(listener => listener()); // 調(diào)用所有變化監(jiān)聽器 }; const subscribe = (listener) => { listeners.push(listener); return () => { // 返回解除監(jiān)聽函數(shù) listeners = listeners.filter(l => l !== listener); }; } dispatch({}); // 初始化 return { getState, dispatch, subscribe }; };2.5. 計(jì)數(shù)器例子
純 JavaScript 不涉及界面(可以在右側(cè) console 中嘗試 store.dispatch)
{% iframe http://jsbin.com/kejezih/edit... 100% 600 %}
增加界面
{% iframe http://jsbin.com/jihara/edit?... 100% 600 %}
三、與 React 進(jìn)行結(jié)合 3.1. 通過 script 標(biāo)簽導(dǎo)入 react實(shí)現(xiàn)同樣功能的 Counter
{% iframe http://jsbin.com/qalevu/edit?... 100% 800 %}
3.2. 用 Redux 和 React 實(shí)現(xiàn) TodoApp在添加 react-redux 之前,為了體會(huì)下 react-redux 的作用,首先來實(shí)現(xiàn)一個(gè)比計(jì)數(shù)器更復(fù)雜一點(diǎn)兒的 TodoApp 栗子~
3.2.1. 分析與設(shè)計(jì)組件一般分為
容器組件(Smart/Container Components)
展示組件(Dumb/Presentational Components)
- | 容器組件 | 展示組件 |
---|---|---|
Location | 最頂層,路由處理 | 中間和子組件 |
Aware of Redux | 是 | 否 |
讀取數(shù)據(jù) | 從 Redux 獲取 state | 從 props 獲取數(shù)據(jù) |
修改數(shù)據(jù) | 向 Redux 派發(fā) actions | 從 props 調(diào)用回調(diào)函數(shù) |
最佳實(shí)踐一般是由容器組件負(fù)責(zé)一些數(shù)據(jù)的獲取,進(jìn)行 dispatch 等操作。而展示組件組件不應(yīng)該關(guān)心邏輯,所有數(shù)據(jù)都通過 props 傳入。
這樣才能達(dá)到展示組件可以在多處復(fù)用,在具體復(fù)用時(shí)就是通過容器組件將其包裝,為其提供所需的各種數(shù)據(jù)。
一個(gè) TodoApp 包含了三個(gè)部分:
頂部的 AddTodo 輸入部分
中間的 TodoList 展示部分
底部的 Footer 過濾部分
State 應(yīng)該包含:
filter:過濾 todos 的條件
SHOW_ALL
SHOW_ACTIVE
SHOW_COMPLETED
todos:所有的 todo
todo:包含 id、text 和 completed
然而傳到應(yīng)用中的 props 只需要:
visibleTodos:過濾后的 todos
filter:過濾條件
Action 應(yīng)該有三種:
ADD_TODO
TOGGLE_TODO
SET_VISIBILITY_FILTER
3.2.2. 編碼實(shí)現(xiàn)// 暫且使用數(shù)字作為 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 } );
// 默認(rèn)初始狀態(tài) const initialState = { filter: "SHOW_ALL", todos: [] }; function rootReducer(state = initialState, action) { switch (action.type) { case "ADD_TODO": // 對(duì)象解構(gòu) 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,而是返回一個(gè)新的 state。可以使用 Object.assign() 新建一個(gè)新的 state。不能這樣使用 Object.assign(state, { visibilityFilter: action.filter }),因?yàn)樗鼤?huì)改變第一個(gè)參數(shù)的值。你必須把第一個(gè)參數(shù)設(shè)置為空對(duì)象。你也可以開啟對(duì) ES7 提案對(duì)象展開運(yùn)算符的支持, 從而使用 { ...state, ...newState } 達(dá)到相同的目的。
在 default 的情況下返回舊的 state,用來兼容遇到未知的 action 這樣的錯(cuò)誤。
拆分 reducer
目前代碼看著比較冗長(zhǎng),其實(shí)在邏輯上 todos 的處理和 filter 的處理應(yīng)該分開,所以在 state 沒有互相耦合時(shí),可以將其拆分,從而讓 reducer 精細(xì)地對(duì)于對(duì)應(yīng) state 的子樹進(jìn)行處理。
// 處理單個(gè) 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 函數(shù),返回的是一個(gè)經(jīng)過各種 reducer 處理過并合并后的新 state。
然鵝,注意這里 todos: todos(state.todos, action), 傳入 state.todos,返回的一定也是 todos(因?yàn)槎际?state 樹上的節(jié)點(diǎn))。
所以 redux 提供了很實(shí)用的 combineReducers api,用于簡(jiǎn)化 reducer 的合并。
import { combineReducers } from "redux"; const rootReducer = combineReducers({ todos: todosReducer, filter: filterReducer, }); // initialState 可以作為第二個(gè)參數(shù)傳入 const store = createStore(rootReducer, initialState);
并且如果 reducer 與 state 節(jié)點(diǎn)同名的話(即 todosReducer -> todos)還能通過 es6 的語法更進(jìn)一步地簡(jiǎn)化
import { combineReducers } from "redux"; const rootReducer = combineReducers({ todos, filter }); // initialState 可以作為第二個(gè)參數(shù)傳入 const store = createStore(rootReducer, initialState);
隨著應(yīng)用的膨脹,我們還可以將拆分后的 reducer 放到不同的文件中, 以保持其獨(dú)立性并用于專門處理不同的數(shù)據(jù)域。
首先只寫一個(gè)根組件
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(); } // 渲染單個(gè) todo _renderTodo(todo) { const { store } = this.props; return (
Show: {" "} {this._renderFilter("SHOW_ALL", "all")} {", "} {this._renderFilter("SHOW_COMPLETED", "completed")} {", "} {this._renderFilter("SHOW_ACTIVE", "active")}
TodoApp 只有根組件
{% iframe http://jsbin.com/bodise/edit?... 100% 800 %}
將所有界面內(nèi)容全寫在 TodoApp 中實(shí)在是太臃腫了,接下來根據(jù)之前的分析結(jié)果將其分為以下子組件(全是展示組件)
AddTodo
TodoList
Todo
Footer
FilterLink
const AddTodo = ({ onAddClick }) => { let input; return (input = node} />); }; const Todo = ({ text, onClick, completed }) => (
Show:
{" "}
所以 TodoApp 精簡(jiǎn)后是這樣~
class TodoApp extends Component { // ... render() { const { store } = this.props; const { todos, filter } = store.getState(); return (); } }{ if (!text) return; store.dispatch(addTodo(text)); }} /> store.dispatch(toggleTodo(id))} />
現(xiàn)在我們?nèi)匀皇且?TodoApp 作為容器組件,其中各個(gè)子組件都是展示組件。
但是這樣做的話一旦子組件需要某個(gè)屬性,就需要從根組件層層傳遞下來,比如 FilterLink 中的 filter 屬性。
所以下面我們?cè)黾尤萜鹘M件,讓展示組件通過容器組件獲得所需屬性。
AddTodo(container)
VisibleTodoList(container)
TodoList
Todo
Footer
FilterLink(container)
Link
// store.dispatch 又被放回來了, // 因?yàn)闀簳r(shí)我們只在 AddTodo 組件中使用 addTodo 這個(gè) action // 以后增加了新的 form 之后可以考慮再將 store.dispatch 移出去 const AddTodo = ({ store }) => { let input; return (input = node} />); }; const Todo = ({ text, onClick, completed }) => (
Show:
{" "}
通過觀察重構(gòu)后的代碼可以發(fā)現(xiàn)有三點(diǎn)麻煩的地方
根組件需要通過 props 將 store 傳給各個(gè)子組件
容器組件都要定義 componentDidMount 進(jìn)行訂閱和 componentWillUnmount 取消訂閱
應(yīng)用其實(shí)并不需要渲染所有的 todos,所以內(nèi)部很麻煩地定義了 _getVisibleTodos 函數(shù)
讓我們先來解決第一個(gè)麻煩~,利用 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(, document.getElementById("container"), ); // 2. 根組件 TodoApp: 和 store say goodbye~, // 因?yàn)?TodoApp 并不是容器組件~ const TodoApp = () => ( ); // 3. AddTodo: 由于 props 固定作為第一個(gè)傳入子組件的參數(shù), // 所以 { 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 = () => (Show: {" "}
); // 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 不變 --all {", "}completed {", "}active
現(xiàn)在中間的非容器組件完全不用為了自己的孩子而費(fèi)勁地傳遞 store={store}
所以以上我們就實(shí)現(xiàn)了簡(jiǎn)化版的由 react-redux 提供的第一個(gè)組件
然鵝,有木有覺得老寫 contextTypes 好煩啊,而且 context 特性并不穩(wěn)定,所以 context 并不應(yīng)該直接寫在我們的應(yīng)用代碼里。
計(jì)將安出?
OOP思維:這還不簡(jiǎn)單?寫個(gè)函數(shù)把容器組件傳進(jìn)去作為父類,然后返回寫好了 componentDidMount,componentWillUnmount 和 contextTypes 的子類不就好啦~
恭喜你~面向?qū)ο蟮乃枷雽W(xué)的很不錯(cuò)~
雖然 JavaScript 底層各種東西都是面向?qū)ο螅欢谇岸艘坏┡c界面相關(guān),照搬面向?qū)ο蟮姆椒▽?shí)現(xiàn)起來會(huì)很麻煩...
React 早期用戶:這還不簡(jiǎn)單?寫個(gè) mixin 豈不美哉~~?
作為 react 親生的 mixin 確實(shí)在多組件間共享方法提供了一些便利,然而使用 mixin 的組件需要了解細(xì)節(jié),從而避免狀態(tài)污染,所以一旦 mixin 數(shù)量多了之后會(huì)越來越難維護(hù)。
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 中對(duì) mixin 的支持。
函數(shù)式(FP):高階組件 High Order Component(下稱 hoc)才是終極解決方案~~
hocFactory:: W: React.Component => E: React.Component
如上所示 hoc 的構(gòu)造函數(shù)接收一個(gè) W(代表 WrappedComponent)返回一個(gè) E(代表 Enhanced Component),而 E 就是這個(gè)高階組件。
假設(shè)我們有一個(gè)舊組件 Comp,然鵝現(xiàn)在接收參數(shù)有些變動(dòng)。
當(dāng)然你可以復(fù)制粘貼再修改舊組件的代碼...(大俠受窩一拜)
也可以這么寫,返回一個(gè)新組件來包裹舊組件。
class NewComp extends Component { mapProps(props) { return {/* new props */}; } render() { return (); } }
然鵝,如果有同樣邏輯的更多的組件需要適配呢???總不能有幾個(gè)抄幾遍吧...
所以騷年你聽說過高階組件么~?
// 先返回一個(gè)函數(shù),而那個(gè)函數(shù)再返回新組件 const mapProps = mapFn => Comp => { return class extends Component { render() { return (); } }; }; const NewComp = mapProps(mapFn)(Comp); // 注意調(diào)用了兩次
可以看到借助高階組件我們將 mapFn 和 Comp 解耦合,這樣就算需要再嵌套多少修改邏輯都沒問題~天黑都不怕~
ok,扯了這么多的淡,終于要說到 connect 了
是噠,你木有猜錯(cuò),react-redux 提供的第二個(gè)也是最后一個(gè) api —— connect 返回的就是一個(gè)高階組件。
使用的時(shí)候只需要 connect()(WrappedComponent) 返回的 component 自動(dòng)就完成了在 componentDidMount 中訂閱 store,在 componentWillUnmount 中取消訂閱和聲明 contextTypes。
這樣就只剩下最后一個(gè)麻煩
3.應(yīng)用其實(shí)并不需要渲染所有的 todos,所以內(nèi)部很麻煩地定義了 _getVisibleTodos 函數(shù)
其實(shí) connect 函數(shù)的第一個(gè)參數(shù)叫做 mapStateToProps,作用就是將 store 中的數(shù)據(jù)提前處理或過濾后作為 props 傳入內(nèi)部組件,以便內(nèi)部組件高效地直接調(diào)用。這樣最后一個(gè)麻煩也解決了~
然鵝,我們問自己這樣就夠了么?并沒有...
還有最后一個(gè)細(xì)節(jié),以 FilterLink 為例。
class FilterLink extends Component { // ... render() { const { store, renderFilter, children } = this.props; const { filter } = store.getState(); return ( store.dispatch( setVisibilityFilter(renderFilter) )} > {children} ); } }
除了從 store 中獲取數(shù)據(jù)(filter),我們還從中獲取了 dispatch,以便觸發(fā) action。如果將回調(diào)函數(shù) onClick 的內(nèi)容也加到 props 中,那么借助 connect 整個(gè) FilterLink 的邏輯豈不是都被我們抽象完了?
是噠,connect 的第二個(gè)參數(shù)叫做 mapDispatchToProps,作用就是將各個(gè)調(diào)用到 dispatch 的地方都抽象成函數(shù)加到 props 中的傳給內(nèi)部組件。這樣最后一個(gè)麻煩終于真的被解決了~
const mapStateToLinkProps = (state, ownProps) => ({ // ownProps 是原組件的 props, // 這里為了和高階組件的 props 區(qū)分 active: ownProps.renderFilter === state.filter, }); const mapDispatchToLinkProps = (dispatch, ownProps) => ({ onClick() { dispatch( setVisibilityFilter(ownProps.renderFilter) ); }, }); // 注意原 FilterLink 整個(gè)都被我們刪了 const FilterLink = connect( mapStateToLinkProps, mapDispatchToLinkProps )(Link);
TodoApp 使用 react-redux
{% iframe http://jsbin.com/fumihi/edit?... 100% 800 %}
本文從 Redux 的理論基礎(chǔ)和源碼出發(fā),介紹了 Redux 的各項(xiàng)基礎(chǔ) api。
接著一步一步地介紹如何與 React 進(jìn)行結(jié)合,從過程中的各個(gè)痛點(diǎn)引出 react-redux 的作用和原理。
然鵝,還有好多的坑沒填,比如:大型項(xiàng)目的文件結(jié)構(gòu)、前端路由(react-router)、中間件(middlewares)、網(wǎng)絡(luò)請(qǐng)求等各類異步操作、服務(wù)器端同構(gòu)直出...
以上 to be continued...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/96876.html
摘要:項(xiàng)目地址下載完項(xiàng)目然后即可基于的項(xiàng)目,主要是為了學(xué)習(xí)實(shí)戰(zhàn)。數(shù)據(jù)都是固定的,從餓了么接口臨時(shí)抓的,模擬了一個(gè)的異步數(shù)據(jù)延遲,感謝餓了么。詳細(xì)信息可以看上面的官方文檔,我這里就簡(jiǎn)單說一下我這個(gè)項(xiàng)目的應(yīng)用。 react-ele-webapp 項(xiàng)目地址 :https://github.com/kliuj/reac... run 下載完項(xiàng)目npm install然后npm run dev 即可 ...
摘要:不斷更新筆記效果有待進(jìn)一步完善搭建一個(gè)基于的多人功能登錄注冊(cè)上傳頭像發(fā)表博文發(fā)表留言參考自前端部分以的腳手架搭起的全家桶后端采用開發(fā)環(huán)境開發(fā)環(huán)境要求以上目錄結(jié)構(gòu)如何運(yùn)行后端默認(rèn)配置在中請(qǐng)確保本地端口默認(rèn)可用發(fā)布到目錄中默 Full-stack-blog(不斷更新筆記) 效果Demo(有待進(jìn)一步完善)搭建一個(gè)基于Koa2的多人blog功能(登錄注冊(cè)上傳頭像,發(fā)表博文,發(fā)表留言)參考自ht...
摘要:全家桶仿簡(jiǎn)書部分功能前言前段時(shí)間接觸了下,一直想要自己寫一個(gè)小練手。在眾多應(yīng)用中,考慮之后選擇了簡(jiǎn)書來模仿,這段時(shí)間就利用了工作之余的時(shí)間進(jìn)行開發(fā)。在這里簡(jiǎn)單敘述一下我仿簡(jiǎn)書部分布局以及功能實(shí)現(xiàn)的過程,僅做學(xué)習(xí)用途。 React-全家桶仿簡(jiǎn)書部分功能 前言 前段時(shí)間接觸了下React,一直想要自己寫一個(gè)小Demo練手。在眾多應(yīng)用中,考慮之后選擇了簡(jiǎn)書來模仿,這段時(shí)間就利用了工作之余的時(shí)...
摘要:今天給大家?guī)砹撕贸绦騿T實(shí)戰(zhàn)項(xiàng)目商城管理后臺(tái)。配合項(xiàng)目學(xué)習(xí)會(huì)讓你更快掌握它的使用方法下面就來看看好程序員這套實(shí)戰(zhàn)項(xiàng)目課程介紹好程序員項(xiàng)目本項(xiàng)目是一個(gè)使用開發(fā)的商城系統(tǒng)的管理后臺(tái),里面登錄判斷,接口調(diào)用,數(shù)據(jù)展示和編輯,文件上傳等后臺(tái)功能。 眾所周知,項(xiàng)目經(jīng)驗(yàn)對(duì)于一個(gè)程序員變得越來越重要。在面...
摘要:本系列文章主要是介紹一些概念原理深入,適合有點(diǎn)基礎(chǔ)的初學(xué)者觀看。是狀態(tài)容器,提供可預(yù)測(cè)化的狀態(tài)管理。使用單向數(shù)據(jù)流,這意味著只能父組件傳遞給子組件。工作流工作流如下圖關(guān)于數(shù)據(jù)流的原理還有大佬不懂這個(gè)話,大佬可以點(diǎn)擊傳送門 本系列文章主要是介紹redux一些概念原理深入,適合有點(diǎn)react基礎(chǔ)的初學(xué)者觀看。分別講述了Reudx、React Hooks等內(nèi)容。部分內(nèi)容涉及源碼解析。 wh...
閱讀 3870·2021-09-10 11:22
閱讀 2325·2021-09-03 10:30
閱讀 3660·2019-08-30 15:55
閱讀 1873·2019-08-30 15:44
閱讀 840·2019-08-30 15:44
閱讀 582·2019-08-30 14:04
閱讀 3042·2019-08-29 17:18
閱讀 1262·2019-08-29 15:04