动手实现 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 的第一次胜利会师,固然还有不少须要优化的地方。

相关文章
相关标签/搜索