redux狀態管理的容器。react
// 定義常量 const INCREMENT = 'INCREMENT' const DECREMENT = 'DECREMENT' // 編寫處理器函數 const initState = { num: 0 } function reducer(state = initState, action) { switch (action.type) { case INCREMENT: return { num: state.num + 1 } case DECREMENT: return { num: state.num - 1 } default: return state } } // 建立容器 const store = createStore(reducer)
reducer函數須要判斷動做的類型去修改狀態,須要注意的是函數必需要有返回值。此函數第一個參數是state
狀態,第二個參數是action
動做,action
參數是個對象,對象裏面有一個不爲undefined
的type
屬性,就是根據這個屬性去區分各類動做類型。
在組件中這樣使用redux
const actions = { increment() { return { type: INCREMENT } }, decrement() { return { type: DECREMENT } } } class Counter extends Component { constructor(props) { super(props); // 初始化狀態 this.state = { num: store.getState().num } } componentDidMount() { // 添加訂閱 this.unsubscribe = store.subscribe(() => { this.setState({ num: store.getState().num }) }) } componentWillUnmount() { // 取消訂閱 this.unsubscribe() } increment = () => { store.dispatch(actions.increment()) } decrement = () => { store.dispatch(actions.decrement()) } render() { return ( <div> <span>{this.state.num}</span> <button onClick={this.increment}>加1</button> <button onClick={this.decrement}>減1</button> </div> ); } }
咱們都知道組件中的state
和props
改變都會致使視圖更新,每當容器裏面的狀態改變須要修改state
,此時就須要用到store
中的subscribe
訂閱這個修改狀態的方法,該方法的返回值是取消訂閱,要修改容器中的狀態要用store
中的dispatch
表示派發動做類型,store
中的getState
表示獲取容器中的狀態。
爲了防止本身手動調用 store.dispatch
,通常會使用redux的這個 bindActionCreators
方法來自動綁定 dispatch
方法,用法以下。app
let actions = { increment() { return { type: INCREMENT } }, decrement() { return { type: DECREMENT } } } actions = bindActionCreators(actions, store.dispatch) class Counter extends Component { constructor(props) { super(props); // 初始化狀態 this.state = { num: store.getState().num } } componentDidMount() { // 添加訂閱 this.unsubscribe = store.subscribe(() => { this.setState({ num: store.getState().num }) }) } componentWillUnmount() { // 取消訂閱 this.unsubscribe() } increment = () => { actions.increment() } decrement = () => { actions.decrement() } render() { return ( <div> <span>{this.state.num}</span> <button onClick={this.increment}>加1</button> <button onClick={this.decrement}>減1</button> </div> ); } } export default Counter;
這個庫是鏈接庫,用來和react和redux進行關聯的,上面使用redux的時候發現一個痛點就是要訂閱設置狀態的方法還要取消訂閱,而react-redux卻能夠經過props自動完成這個功能。dom
import {Provider} from 'react-redux' import {createStore} from 'redux' const INCREMENT = 'INCREMENT' const DECREMENT = 'DECREMENT' const initState = { num: 0 } function reducer(state = initState, action) { switch (action.type) { case INCREMENT: return { num: state.num + 1 } case DECREMENT: return { num: state.num - 1 } default: return state } } const store = createStore(reducer) ReactDOM.render(( <Provider store={store}> <Counter /> </Provider> ), document.getElementById('root'))
Provider
是個高階組件,須要傳入store參數做爲store屬性,高階組件包裹使用狀態的組件。這樣就完成了跨組件屬性傳遞。
import {connect} from 'react-redux' const INCREMENT = 'INCREMENT' const DECREMENT = 'DECREMENT' let actions = { increment() { return { type: INCREMENT } }, decrement() { return { type: DECREMENT } } } class Counter extends Component { render() { return ( <div> <span>{this.props.num}</span> <button onClick={() => this.props.increment()}>加1</button> <button onClick={() => this.props.decrement()}>減1</button> </div> ); } } const mapStateToProps = state => { return state } const mapDispatchToProps = actions export default connect(mapStateToProps, mapDispatchToProps)(Counter);
組件中使用connect
方法關聯組件和容器,這個高階函數,須要執行兩次,第一次須要傳入兩個參數,第一個參數是將狀態映射爲屬性,第二個是將action
映射爲屬性,第二次須要傳入組件做爲參數。
該參數是個函數返回對象的形式,參數是store中的 state
,能夠用來篩選咱們須要的屬性,防止組件屬性太多,難以維護ide
好比咱們狀態是這樣的{ a: 1, b: 2 }
想改爲這樣的{ a: 1 }
,使用以下函數
const mapStateToProps = state => { return { a: state.a } }
這個方法將action中的方法映射爲屬性,參數是個函數返回對象的形式,參數是store中的 dispatch
,能夠用來篩選action
學習
let actions = { increment() { return { type: INCREMENT } }, decrement() { return { type: DECREMENT } } }
如今action
中有兩個方法,咱們只須要一個的話就能夠這麼作了。this
const mapDispatchToProps = dispatch => { return { increment: (...args) => dispatch(actions.increment(...args)) } }
如今你已經掌握了react和react-redux兩個庫的使用,而且知道他們的做用分別是幹什麼的,那麼咱們就看看原理,先學習redux原理,先寫一個createStore
方法。spa
import createStore from './createStore' export { createStore }
回顧一下createStore
是怎麼使用的,使用的時候須要傳入一個處理器reducer
函數,根據動做類型修改狀態而後返回狀態,只有在調用dispatch
方法修改狀態的時候纔會執行reducer
才能獲得新狀態。prototype
import isPlainObject from './utils/isPlainObject' import ActionTypes from './utils/actionTypes' function createStore(reducer, preloadedState) { let currentState = preloadedState function getState() { return currentState } function dispatch(action) { // 判斷是不是純對象 if (!isPlainObject(action)) { throw new Error('類型錯誤') } // 計算新狀態 currentState = currentReducer(currentState, action) } dispatch({ type: ActionTypes.INIT }) return { dispatch, getState } } export default createStore
在調用 dispatch
方法的時候,須要傳入一個對象,而且有個 type
屬性,爲了保證傳入的參數的正確性,調用了isPlainObject
方法,判斷是不是一個對象。
function isPlainObject (obj) { if (typeof obj !== 'object' || obj === null) { return false } let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto } export default isPlainObject
該方法的原理就是判斷這個對象的原型和 Object.prototype
是否相等。
redux中還有訂閱和取消訂閱的方法,每當狀態改變執行訂閱的函數。發佈訂閱是咱們再熟悉不過的原理了,我就很少說了。
function createStore(reducer, preloadedState) { let currentState = preloadedState let currentReducer = reducer let currentListeners = [] let nextListeners = currentListeners // 拷貝 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } function getState() { return currentState } function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('類型錯誤') } // 訂閱 ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } function dispatch(action) { // 判斷是不是純對象 if (!isPlainObject(action)) { throw new Error('類型錯誤') } // 計算新狀態 currentState = currentReducer(currentState, action) // 發佈 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } } dispatch({ type: ActionTypes.INIT }) return { dispatch, getState, subscribe } }
ensureCanMutateNextListeners
的做用是,若是是在 listeners
被調用期間發生訂閱(subscribe)或者解除訂閱(unsubscribe),在本次通知中並不會當即生效,而是在下次中生效。
代碼裏面有個值得注意的是調用了一次dispatch
方法,派發一次動做,目的是爲了獲得默認值,並且爲了這個動做類型不同凡響,防止定義的類型衝突,因此redux這麼來寫。
const randomString = () => Math.random().toString(36).substring(7).split('').join('.') const ActionTypes = { INIT: `@@redux/INIT${randomString()}` } export default ActionTypes
bindActionCreators
在上面已經介紹了他的做用,就是爲每一個方法自動綁定dispatch
方法。
export default function bindActionCreators(actionCreators, dispatch) { function bindActionCreator(actionCreators, dispatch) { return function () { return dispatch(actionCreators.apply(this, arguments)) } } if (typeof actionCreators === 'function') { bindActionCreator(actionCreators, dispatch) } const boundActionCreator = {} for (const key in actionCreators) { boundActionCreator[key] = bindActionCreator(actionCreators[key], dispatch) } return boundActionCreator }