redux必知基礎概念

redux無疑已是react應用的標配了,也確實好用,這裏就redux的基礎點說明一下,包括createStore, combineReducers,provider, connectjavascript

 

createStore()  

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

 

combineReducers()

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});

 

 

Provider

預備知識: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
};

經過添加childContextTypesgetChildContext到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
}

 

Connect

使用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

 

mapDispatchToProps

將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))

 

MiddleWare

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
}
相關文章
相關標籤/搜索