redux無疑已是react應用的標配了,也確實好用,這裏就redux的基礎點說明一下,包括createStore, combineReducers,provider, connectjavascript
const createStore = (reducer) => { let state let listeners = [] const getState = () => state const dispatch = (action) => { state = reducer(state, action) listeners.forEach(listener => listener()) } const subscribe = (listener) => { listeners.push(listener) return () => { listeners = listeners.filter(l => l !== listener) } } dispatch({}) // to get initial state return {getState, dispatch, subscribe} } // import {createStore} from 'redux'
另一個class版本的Store, 更接近真實的store, 構造函數傳遞兩個參數,reducers和initialStatejava
const store = new Store(reducers, initialState);
export class Store { private subscribers: Function[]; private reducers: { [key: string]: Function }; private state: { [key: string]: any }; constructor(reducers = {}, initialState = {}) { this.subscribers = []; this.reducers = reducers; this.state = this.reduce(initialState, {}); } get value() { return this.state; } subscribe(fn) { this.subscribers = [...this.subscribers, fn]; fn(this.value); return () => { this.subscribers = this.subscribers.filter(sub => sub !== fn); }; } dispatch(action) { this.state = this.reduce(this.state, action); this.subscribers.forEach(fn => fn(this.value)); } private reduce(state, action) { const newState = {}; for (const prop in this.reducers) { newState[prop] = this.reducers[prop](state[prop], action); } return newState; } }
和第一種function定義createStore還有一處不一樣,subscribe(fn)後執行了fn(this.value),就不須要等到dispatch後更新狀態,至關於初始化狀態react
const combineReducers = (reducers) => { return (state = {}, action) => { return Object.keys(reducers).reduce( (nextState, key) => { nextState[key] = reducers[key](state[key], action); return nextState; }, {}) } } // import {combineReducers} from 'redux'
使用 combineReducersgit
const {combineReducers} = Redux; const todoApp = combineReducers({todos, visibilityFilter});
預備知識:context, 使用react的context傳遞store, 做爲全局變量github
import PropTypes from 'prop-types'; class Button extends React.Component { render() { return ( <button style={{background: this.context.color}}> {this.props.children} </button> ); } } /* 接收context */ Button.contextTypes = { color: PropTypes.string }; class Message extends React.Component { render() { return ( <div> {this.props.text} <Button>Delete</Button> </div> ); } } class MessageList extends React.Component { /* 傳遞context */ getChildContext() { return {color: "purple"}; } render() { const children = this.props.messages.map((message) => <Message text={message.text} /> ); return <div>{children}</div>; } } // 傳遞context MessageList.childContextTypes = { color: PropTypes.string };
經過添加childContextTypes、getChildContext到MessageList, context 會自動傳遞到子組件,但只有定義了contextTypes的子組件,context纔會做爲屬性傳遞redux
使用context傳遞store, 封裝Provideride
class Provider extends React.Component { /* called by react */ getChildContext () { return { store: this.props.store } } render () { return this.props.children } } Provider.childContextTypes = { store: PropTypes.object } ReactDOM.render( <Provider store={createStore(todoApp)}> <TodoApp /> </Provider>, document.getElementById('root') ) // import {Provider} from 'react-redux'
class VisibleTodoList extends React.Component { componentDidMount () { const {store} = this.context this.unsubscribe = store.subscribe(() => { this.forceUpdate() }) } componentWillUnmount () { this.unsubscribe() } render () { const {store} = this.context const state = store.getState() return ( <TodoList todos={getVisibleTodos(state.todos, state.visibilityFilter)} onTodoClick={id => store.dispatch({type: 'TOGGLE_TODO', id})} /> ) } } VisibleTodoList.contextTypes = { store: PropTypes.object }
使用react-redux的connect函數將presentation component和redux鏈接起來函數
1 省去了context的傳遞(presentation component的contextTypes的定義)this
2 省去了componentDidMount中調用store.subscribe(),componentWillUnmount中調用unsubscribe()spa
3 傳遞了dispatch
const mapStateToProps = (state, ownProps) => { return { todos: getVisibleTodos( state.todos, state.visibilityFilter) } } const mapDispatchToProps = (dispatch, ownProps) => { return { onTodoClick: id => dispatch({ type: 'TOGGLE_TODO', id }) } } import {connect} from 'react-redux'; const visibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
附上connect.js的代碼解釋,很詳細
https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e
將action映射爲封裝的組件的props,能夠簡寫爲對象形式,但要保證參數的順序要一直,好比下面例子中的id
const mapDispatchToTodoListProps = (dispatch) => ({ onTodoClick (id) { dispatch(toggleTodo(id)) } }) VisibleTodoList = withRouter(connect( mapStateToTodoListProps, actions )(VisibleTodoList)) // mapDispatchToTodoListProps的簡寫版 VisibleTodoList = withRouter(connect( mapStateToTodoListProps, {onTodoClick: toggleTodo} )(VisibleTodoList))
const addLoggingToDispatch = (store) => { const rawDispatch = store.dispatch if (!console.group) { return rawDispatch } return (action) => { console.group(action.type) console.log('%c prev state', 'color: gray', store.getState()) console.log('%c action', 'color: blue', action) const returnValue = rawDispatch(action) console.log('%c next state', 'color: green', store.getState()) console.group(action.type) return returnValue } } const configureStore = () => { const persistedState = loadState() const store = createStore(todoApp, persistedState) store.dispatch = addLoggingToDispatch(store) return store }