動手實現 React-redux(二):結合 context 和 store

既然要把 store 和 context 結合起來,咱們就先構建 store。在 src/index.js 加入以前建立的 createStore 函數,而且構建一個 themeReducer 來生成一個 storecss

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import Header from './Header'
import Content from './Content'
import './index.css'

function createStore (reducer) {
  let state = null
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach((listener) => listener())
  }
  dispatch({}) // 初始化 state
  return { getState, dispatch, subscribe }
}

const themeReducer = (state, action) => {
  if (!state) return {
    themeColor: 'red'
  }
  switch (action.type) {
    case 'CHANGE_COLOR':
      return { ...state, themeColor: action.themeColor }
    default:
      return state
  }
}

const store = createStore(themeReducer)
...

themeReducer 定義了一個表示主題色的狀態 themeColor,而且規定了一種操做 CHNAGE_COLOR,只能經過這種操做修改顏色。如今咱們把 store 放到 Index 的 context 裏面,這樣每一個子組件均可以獲取到 store 了,修改 src/index.js 裏面的 Indexhtml

class Index extends Component {
  static childContextTypes = {
    store: PropTypes.object
  }

  getChildContext () {
    return { store }
  }

  render () {
    return (
      <div>
        <Header />
        <Content />
      </div>
    )
  }
}

若是有些同窗已經忘記了 context 的用法,能夠參考以前的章節: React.js 的 context 。react

而後修改 src/Header.js,讓它從 Index 的 context 裏面獲取 store,而且獲取裏面的 themeColor 狀態來設置本身的顏色:redux

class Header extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  constructor () {
    super()
    this.state = { themeColor: '' }
  }

  componentWillMount () {
    this._updateThemeColor()
  }

  _updateThemeColor () {
    const { store } = this.context
    const state = store.getState()
    this.setState({ themeColor: state.themeColor })
  }

  render () {
    return (
      <h1 style={{ color: this.state.themeColor }}>React.js 小書</h1>
    )
  }
}

其實也很簡單,咱們在 constructor 裏面初始化了組件本身的 themeColor 狀態。而後在生命週期中 componentWillMount 調用 _updateThemeColor_updateThemeColor會從 context 裏面把 store 取出來,而後經過 store.getState() 獲取狀態對象,而且用裏面的 themeColor 字段設置組件的 state.themeColordom

而後在 render 函數裏面獲取了 state.themeColor 來設置標題的樣式,頁面上就會顯示:函數

如法炮製 Content.js優化

class Content extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  constructor () {
    super()
    this.state = { themeColor: '' }
  }

  componentWillMount () {
    this._updateThemeColor()
  }

  _updateThemeColor () {
    const { store } = this.context
    const state = store.getState()
    this.setState({ themeColor: state.themeColor })
  }

  render () {
    return (
      <div>
        <p style={{ color: this.state.themeColor }}>React.js 小書內容</p>
        <ThemeSwitch />
      </div>
    )
  }
}

還有 src/ThemeSwitch.jsthis

class ThemeSwitch extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  constructor () {
    super()
    this.state = { themeColor: '' }
  }

  componentWillMount () {
    this._updateThemeColor()
  }

  _updateThemeColor () {
    const { store } = this.context
    const state = store.getState()
    this.setState({ themeColor: state.themeColor })
  }

  render () {
    return (
      <div>
        <button style={{ color: this.state.themeColor }}>Red</button>
        <button style={{ color: this.state.themeColor }}>Blue</button>
      </div>
    )
  }
}

這時候,主題已經徹底生效了,整個頁面都是紅色的:spa

固然如今點按鈕仍是沒什麼效果,咱們接下來給按鈕添加事件。其實也很簡單,監聽 onClick 事件而後 store.dispatch 一個 action 就行了,修改 src/ThemeSwitch.jscode

class ThemeSwitch extends Component {
  static contextTypes = {
    store: PropTypes.object
  }

  constructor () {
    super()
    this.state = { themeColor: '' }
  }

  componentWillMount () {
    this._updateThemeColor()
  }

  _updateThemeColor () {
    const { store } = this.context
    const state = store.getState()
    this.setState({ themeColor: state.themeColor })
  }

  // dispatch action 去改變顏色
  handleSwitchColor (color) {
    const { store } = this.context
    store.dispatch({
      type: 'CHANGE_COLOR',
      themeColor: color
    })
  }

  render () {
    return (
      <div>
        <button
          style={{ color: this.state.themeColor }}
          onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
        <button
          style={{ color: this.state.themeColor }}
          onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
      </div>
    )
  }
}

咱們給兩個按鈕都加上了 onClick 事件監聽,並綁定到了 handleSwitchColor 方法上,兩個按鈕分別給這個方法傳入不一樣的顏色 red 和 bluehandleSwitchColor 會根據傳入的顏色 store.dispatch 一個 action 去修改顏色。

固然你如今點擊按鈕仍是沒有反應的。由於點擊按鈕的時候,只是更新 store 裏面的 state,而並無在 store.state 更新之後去從新渲染數據,咱們其實就是忘了 store.subscribe 了。

給 Header.jsContent.jsThemeSwitch.js 的 componentWillMount 生命週期都加上監聽數據變化從新渲染的代碼:

...
  componentWillMount () {
    const { store } = this.context
    this._updateThemeColor()
    store.subscribe(() => this._updateThemeColor())
  }
...

經過 store.subscribe,在數據變化的時候從新調用 _updateThemeColor,而 _updateThemeColor 會去 store 裏面取最新的 themeColor 而後經過 setState 從新渲染組件,這時候組件就更新了。如今能夠自由切換主題色了:

咱們順利地把 store 和 context 結合起來,這是 Redux 和 React.js 的第一次勝利會師,固然還有不少須要優化的地方。

相關文章
相關標籤/搜索