redux入門
什麼是redux
Redux
是 JavaScript 狀態容器,提供可預測化的狀態管理。
什麼時候使用redux
-
某個元件的狀態,需要共享
-
某個狀態需要在任何地方都可以拿到
-
一個元件需要改變全域性狀態
-
一個元件需要改變另一個元件的狀態
如果你的UI層非常簡單,沒有很多互動,Redux 就是不必要的,用了反而增加複雜性。
基礎和概念
state
當使用普通物件來描述應用的 state 時:
{ loginName: '', visibilityFilter: 'SHOW_COMPLETED' }
這個物件就像 “Model”,區別是它並沒有 setter(修改器方法)。因此其它的程式碼不能隨意修改它,造成難以復現的 bug。
action
-
Action
是把資料從應用傳到 store 的有效載荷。 -
它是 store 資料的唯一來源。
-
要想更新 state 中的資料,你需要發起一個
action
。
Action
就是一個普通 JavaScript 物件,像這樣:
{type: 'SET_NAME', text: 'tom'}, {type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL'}
強制使用 action 來描述所有變化帶來的好處是可以清晰地知道應用中到底發生了什麼。如果一些東西改變了,就可以知道為什麼變。action 就像是描述發生了什麼的指示器。
注:
在redux中約定,action 內必須使用一個字串型別的 type 欄位來表示將要執行的動作。多數情況下,type 會被定義成字串常量。當應用規模越來越大時,建議使用單獨的模組或檔案來存放 action。
reducer
把 action 和 state 串起來,開發一些函式,這就是 reducer。
reducer 只是一個接收 state 和 action,並返回新的 state 的函式。
function loginName(state = '', action) { if (action.type === 'SET_NAME') { return action.filter; } else { return state; } } function visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter; } else { return state; } }
再開發一個 reducer 呼叫這兩個 reducer,進而來管理整個應用的 state:
function reducers(state = {}, action) { return { loginName: loginName(state.loginName, action), visibilityFilter: visibilityFilter(state.visibilityFilter, action) }; }
注:
reducer
純淨非常重要。永遠不要在 reducer 裡做這些操作:
-
修改傳入引數;
-
執行有副作用的操作,如 API 請求和路由跳轉;
-
呼叫非純函式,如 Date.now() 或 Math.random()。
每個 reducer 只負責管理全域性 state 中它負責的一部分。每個 reducer 的 state 引數都不同,分別對應它管理的那部分 state 資料。
這差不多就是 Redux 思想的全部。redux 就是提供一些簡單的工具來簡化這種模式。
redux 三大原則
-
單一資料來源
整個應用的
state
被儲存在一棵 object tree 中,並且這個 object tree 只存在於唯一一個store
中。 -
State 是隻讀的
唯一改變 state 的方法就是觸發
action
,action 是一個用於描述已發生事件的普通物件。在 default 情況下返回舊的 state。遇到未知的 action 時,一定要返回舊的 state。不要使用 Object.assign(state, newData),應該使用 Object.assign({}, state, newData) -
使用純函式來執行修改
為了描述 action 如何改變 state tree ,你需要編寫
reducers
redux API
createStore(reducer, [preloadedState], enhancer)
建立一個 Reduxstore
來以存放應用中所有的 state。
應用中應有且僅有一個 store。
引數
-
reducer
(Function) : 接收兩個引數,分別是當前的 state 樹和要處理的action
,返回新的state 樹
-
preloadedState
(any) : 初始時的 state。如果你使用combineReducers
建立reducer
,它必須是一個普通物件,與傳入的 keys 保持同樣的結構。否則,你可以自由傳入任何reducer
可理解的內容。 -
enhancer
(Function) : Store enhancer 是一個組合 store creator 的高階函式,返回一個新的強化過的 store creator。這與 middleware 相似,它也允許你通過複合函式改變 store 介面。
返回值
Store
: 儲存了應用所有 state 的物件。
Store
Store 就是用來維持應用所有的state 樹 的一個物件。
Store 不是類。它只是有幾個方法的物件。 要建立它,只需要把根部的 reducing 函式 傳遞給createStore
。
Store 方法 | 介紹 | 引數 | 返回值 |
---|---|---|---|
getState()
|
得到state | -- | (any): 應用當前的 state 樹。 |
dispatch
|
分發 action。這是觸發 state 變化的惟一途徑。 | action (Object) | (Object): 要 dispatch 的 action。 |
subscribe
|
一個變化監聽器。每當 dispatch action 的時候就會執行,state 樹中的一部分可能已經變化。 |
listener
(Function
): 每當 dispatch action 的時候都會執行的回撥。state 樹中的一部分可能已經變化。你可以在回撥函式裡呼叫ofollow,noindex">
getState()
來拿到當前 state。 |
(Function): 一個可以解綁變化監聽器的函式。 |
replaceReducer(nextReducer)
|
替換 store 當前用來計算 state 的 reducer。這是一個高階 API。只有在你需要實現程式碼分隔,而且需要立即載入一些 reducer 的時候才可能會用到它。 | reducer (Function) store 會使用的下一個 reducer。 | --- |
combineReducers(reducers)
combineReducers 輔助函式的作用是,把一個由多個不同 reducer 函式作為 value 的 object,合併成一個最終的 reducer 函式。
合併後的 reducer 可以呼叫各個子 reducer,並把它們返回的結果合併成一個 state 物件。由 combineReducers() 返回的 state 物件,會將傳入的每個 reducer 返回的 state 按其傳遞給 combineReducers() 時對應的 key 進行命名。
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer}) // rootReducer 將返回如下的 state 物件 { potato: { // ... potatoes, 和一些其他由 potatoReducer 管理的 state 物件 ... }, tomato: { // ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 物件,比如說 sauce 屬性 ... } }
引數
reducers (Object): 一個物件,它的值(value)對應不同的 reducer 函式,這些 reducer 函式後面會被合併成一個。
返回值
(Function):一個呼叫 reducers 物件裡所有 reducer 的 reducer,並且構造一個與 reducers 物件結構相同的 state 物件。
注:
每個傳入 combineReducers 的 reducer 都需滿足以下規則:
-
所有未匹配到的 action,必須把它接收到的第一個引數也就是那個 state 原封不動返回。
-
永遠不能返回 undefined。當過早 return 時非常容易犯這個錯誤,為了避免錯誤擴散,遇到這種情況時 combineReducers 會拋異常。
-
如果傳入的 state 就是 undefined,一定要返回對應 reducer 的初始 state。根據上一條規則,初始 state 禁止使用 undefined。使用 ES6 的預設引數值語法來設定初始 state 很容易,但你也可以手動檢查第一個引數是否為 undefined。
示例
//reducers/todos.js export default function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([action.text]) default: return state } } //reducers/counter.js export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } } //reducers/index.js import { combineReducers } from 'redux' import todos from './todos' import counter from './counter' export default combineReducers({ todos, counter }) //App.js import { createStore } from 'redux' import reducer from './reducers/index' let store = createStore(reducer) console.log(store.getState()) // { //counter: 0, //todos: [] // } store.dispatch({ type: 'ADD_TODO', text: 'Use Redux' }) console.log(store.getState()) // { //counter: 0, //todos: [ 'Use Redux' ] // }
applyMiddleware(...middlewares)
使用包含自定義功能的 middleware 來擴充套件 Redux 是一種推薦的方式。Middleware 可以讓你包裝 store 的
dispatch
方法來達到你想要的目的。
引數
-
...middlewares
(arguments ): 遵循 Reduxmiddleware API 的函式。每個 middleware 接受Store
的dispatch
和getState
函式作為命名引數,並返回一個函式。該函式會被傳入 被稱為next
的下一個 middleware 的 dispatch 方法,並返回一個接收 action 的新函式,這個函式可以直接呼叫next(action)
,或者在其他需要的時刻呼叫,甚至根本不去呼叫它。呼叫鏈中最後一個 middleware 會接受真實的 store 的dispatch
方法作為next
引數,並藉此結束呼叫鏈。所以,middleware 的函式簽名是({ getState, dispatch }) => next => action
。
使用
import { applyMiddleware, createStore, compose } from 'redux' import reducers from './reducers' import {createLogger} from 'redux-logger' import middlePromise from 'redux-promise' // import thunk from 'redux-thunk' // 模擬 logger const logger = store => next => action =>{ console.log('prev state',store.getState()) console.log('dispatch',action); let result = next(action); console.log('next state',store.getState()); return result; } conststore = createStore(reducers, applyMiddleware(middlePromise, logger)) export default store // 非同步 import { applyMiddleware, createStore, compose } from 'redux' import reducers from './reducers' import {createLogger} from 'redux-logger' // import middlePromise from 'redux-promise' import thunk from 'redux-thunk' // 模擬 logger const logger = store => next => action =>{ console.log('prev state',store.getState()) console.log('dispatch',action); let result = next(action); console.log('next state',store.getState()); return new Promise((resolve, reject) => { resolve(result) }); } conststore = createStore(reducers, compose(applyMiddleware(thunk, logger))) export default store
注:
- 有的中介軟體有次序要求,使用前要查一下文件
bindActionCreators(actionCreators, dispatch)
把一個 value 為不同 action creator 的物件,轉成擁有同名 key 的物件。同時使用 dispatch對每個 action creator 進行包裝,以便可以直接呼叫它們。
引數
-
actionCreators
(Function orObject ): 一個 action creator,或者一個 value 是 action creator 的物件。 -
dispatch
(Function ): 一個由 Store例項提供的 dispatch函式。
返回值
(Function or Object): 一個與原物件類似的物件,只不過這個物件的 value 都是會直接 dispatch 原 action creator 返回的結果的函式。如果傳入一個單獨的函式作為 actionCreators,那麼返回的結果也是一個單獨的函式。
示例在react-redux中講解
compose(...functions)
從右到左來組合多個函式。
當需要把多個store
增強器 依次執行的時候,需要用到它。
引數
(arguments):需要合成的多個函式。預計每個函式都接收一個引數。它的返回值將作為一個引數提供給它左邊的函式,以此類推。 例外是最右邊的引數可以接受多個引數,因為它將為由此產生的函式提供簽名。(compose(funcA, funcB, funcC) 形象為 compose(funcA(funcB(funcC()))))
返回值
(Function): 從右到左把接收到的函式合成後的最終函式。
react-redux 基本使用
React-Redux 將所有元件分成兩大類:UI 元件(presentational component)和容器元件(container component)。
UI 元件
有以下幾個特徵。
- 只負責 UI 的呈現,不帶有任何業務邏輯
- 沒有狀態(即不使用this.state這個變數)
- 所有資料都由引數(this.props)提供
- 不使用任何 Redux 的 API
示例
const Title = props => <h1>{props.title}</h1>;
容器元件
- 負責管理資料和業務邏輯,不負責 UI 的呈現
- 帶有內部狀態
- 使用 Redux 的 API
React-Redux 規定,所有的 UI 元件都由使用者提供,容器元件則是由 React-Redux 自動生成。
connect()
React-Redux 提供connect方法,用於從 UI 元件生成容器元件。connect的意思,就是將這兩種元件連起來。
示例
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(Title)
connect方法接受兩個引數:mapStateToProps
和mapDispatchToProps
。它們定義了 UI 元件的業務邏輯。前者負責輸入邏輯,即將state對映到 UI 元件的引數(props),後者負責輸出邏輯,即將使用者對 UI 元件的操作對映成 Action。
connect引數 | 介紹 | 引數 |
---|---|---|
mapStateToProps | mapStateToProps是一個函式。它的作用就是像它的名字那樣,建立一個從(外部的)state物件到(UI 元件的)props物件的對映關係。 |
第一個永遠是state
;第二個引數ownProps
:代表容器元件的props物件 |
mapDispatchToProps | 用來建立 UI 元件的引數到store.dispatch方法的對映 |
dispatch
和ownProps
(容器元件的props物件)兩個引數 |
示例
const mapStateToProps = (state, ownProps) => { return { state: state, title: state.increment + ownProps.title } } const mapDispatchToProps = (dispatch, ownProps) => { return { actions: bindActionCreators(actionCreators, dispatch), ownPropsClick: () => { dispatch(actionCreators.increment()) console.log(ownProps.title) } } } // mapDispatchToProps 也可以是一個物件 // const mapDispatchToProps = { //ownPropsClick: (filter) => { //return { //type: 'INCREMENT', //filter: filter //} //} // } export default connect(mapStateToProps, mapDispatchToProps)(Test)
<Provider store>
<Provider store> 使元件層級中的 connect() 方法都能夠獲得 Redux store。正常情況下,你的根元件應該巢狀在 <Provider> 中才能使用 connect() 方法。
屬性
-
store
(Redux Store ): 應用程式中唯一的 Redux store 物件 -
children
(ReactElement ) 元件層級的根元件。
示例
ReactDOM.render( <Provider store={store}> <App/> </Provider>, rootEl )
redux的使用先講到這裡,如有錯誤之處希望大家積極留言!