深刻淺出redux知識

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 參數是個對象,對象裏面有一個不爲 undefinedtype 屬性,就是根據這個屬性去區分各類動做類型。redux

在組件中這樣使用app

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>
    );
  }
}
複製代碼

咱們都知道組件中的 stateprops 改變都會致使視圖更新,每當容器裏面的狀態改變須要修改 state,此時就須要用到 store 中的 subscribe 訂閱這個修改狀態的方法,該方法的返回值是取消訂閱,要修改容器中的狀態要用store 中的 dispatch 表示派發動做類型,store 中的 getState 表示獲取容器中的狀態。dom

bindActionCreators

爲了防止本身手動調用 store.dispatch ,通常會使用redux的這個 bindActionCreators 方法來自動綁定 dispatch 方法,用法以下。ide

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

這個庫是鏈接庫,用來和react和redux進行關聯的,上面使用redux的時候發現一個痛點就是要訂閱設置狀態的方法還要取消訂閱,而react-redux卻能夠經過props自動完成這個功能。函數

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映射爲屬性,第二次須要傳入組件做爲參數。ui

mapStateToProps

該參數是個函數返回對象的形式,參數是store中的 state,能夠用來篩選咱們須要的屬性,防止組件屬性太多,難以維護this

好比咱們狀態是這樣的{ a: 1, b: 2 } 想改爲這樣的{ a: 1 },使用以下spa

const mapStateToProps = state => {
  return { a: state.a }
}
複製代碼

mapDispatchToProps

這個方法將action中的方法映射爲屬性,參數是個函數返回對象的形式,參數是store中的 dispatch,能夠用來篩選action

let actions = {
  increment() {
    return { type: INCREMENT }
  },
  decrement() {
    return { type: DECREMENT }
  }
}
複製代碼

如今action中有兩個方法,咱們只須要一個的話就能夠這麼作了。

const mapDispatchToProps = dispatch => {
  return {
    increment: (...args) => dispatch(actions.increment(...args))
  }
}
複製代碼

原理

createStore原理

如今你已經掌握了react和react-redux兩個庫的使用,而且知道他們的做用分別是幹什麼的,那麼咱們就看看原理,先學習redux原理,先寫一個createStore方法。

import createStore from './createStore'

export {
  createStore
}
複製代碼

回顧一下createStore是怎麼使用的,使用的時候須要傳入一個處理器reducer函數,根據動做類型修改狀態而後返回狀態,只有在調用dispatch方法修改狀態的時候纔會執行reducer 才能獲得新狀態。

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
複製代碼

該方法的原理就是判斷 obj.__proto__ === 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原理

bindActionCreators 在上面已經介紹了他的做用,就是爲每一個方法自動綁定dispatch方法。

function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const boundActionCreators = {}
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
複製代碼
相關文章
相關標籤/搜索