既然要把 store 和 context 结合起来,我们就先构建 store。在 src/index.js 加入之前创建的 createStore 函数,并且构建一个 themeReducer 来生成一个 store

    1. import React, { Component } from 'react'
    2. import PropTypes from 'prop-types'
    3. import ReactDOM from 'react-dom'
    4. import Header from './Header'
    5. import Content from './Content'
    6. import './index.css'
    7. function createStore (reducer) {
    8. let state = null
    9. const listeners = []
    10. const subscribe = (listener) => listeners.push(listener)
    11. const getState = () => state
    12. const dispatch = (action) => {
    13. state = reducer(state, action)
    14. listeners.forEach((listener) => listener())
    15. }
    16. dispatch({}) // 初始化 state
    17. return { getState, dispatch, subscribe }
    18. }
    19. const themeReducer = (state, action) => {
    20. if (!state) return {
    21. themeColor: 'red'
    22. }
    23. switch (action.type) {
    24. case 'CHANGE_COLOR':
    25. return { ...state, themeColor: action.themeColor }
    26. default:
    27. return state
    28. }
    29. }
    30. const store = createStore(themeReducer)
    31. ...

    themeReducer 定义了一个表示主题色的状态 themeColor,并且规定了一种操作 CHNAGE_COLOR,只能通过这种操作修改颜色。现在我们把 store 放到 Index 的 context 里面,这样每个子组件都可以获取到 store 了,修改 src/index.js 里面的 Index

    1. class Index extends Component {
    2. static childContextTypes = {
    3. store: PropTypes.object
    4. }
    5. getChildContext () {
    6. return { store }
    7. }
    8. render () {
    9. return (
    10. <div>
    11. <Header />
    12. <Content />
    13. </div>
    14. )
    15. }
    16. }

    如果有些同学已经忘记了 context 的用法,可以参考之前的章节: React.js 的 context

    然后修改 src/Header.js,让它从 Index 的 context 里面获取 store,并且获取里面的 themeColor 状态来设置自己的颜色:

    1. class Header extends Component {
    2. static contextTypes = {
    3. store: PropTypes.object
    4. }
    5. constructor () {
    6. super()
    7. this.state = { themeColor: '' }
    8. }
    9. componentWillMount () {
    10. this._updateThemeColor()
    11. }
    12. _updateThemeColor () {
    13. const { store } = this.context
    14. const state = store.getState()
    15. this.setState({ themeColor: state.themeColor })
    16. }
    17. render () {
    18. return (
    19. <h1 style={{ color: this.state.themeColor }}>React.js 小书</h1>
    20. )
    21. }
    22. }

    其实也很简单,我们在 constructor 里面初始化了组件自己的 themeColor 状态。然后在生命周期中 componentWillMount 调用 _updateThemeColor_updateThemeColor 会从 context 里面把 store 取出来,然后通过 store.getState() 获取状态对象,并且用里面的 themeColor 字段设置组件的 state.themeColor

    然后在 render 函数里面获取了 state.themeColor 来设置标题的样式,页面上就会显示:

    实例图片

    如法炮制 Content.js

    1. class Content extends Component {
    2. static contextTypes = {
    3. store: PropTypes.object
    4. }
    5. constructor () {
    6. super()
    7. this.state = { themeColor: '' }
    8. }
    9. componentWillMount () {
    10. this._updateThemeColor()
    11. }
    12. _updateThemeColor () {
    13. const { store } = this.context
    14. const state = store.getState()
    15. this.setState({ themeColor: state.themeColor })
    16. }
    17. render () {
    18. return (
    19. <div>
    20. <p style={{ color: this.state.themeColor }}>React.js 小书内容</p>
    21. <ThemeSwitch />
    22. </div>
    23. )
    24. }
    25. }

    还有 src/ThemeSwitch.js

    1. class ThemeSwitch extends Component {
    2. static contextTypes = {
    3. store: PropTypes.object
    4. }
    5. constructor () {
    6. super()
    7. this.state = { themeColor: '' }
    8. }
    9. componentWillMount () {
    10. this._updateThemeColor()
    11. }
    12. _updateThemeColor () {
    13. const { store } = this.context
    14. const state = store.getState()
    15. this.setState({ themeColor: state.themeColor })
    16. }
    17. render () {
    18. return (
    19. <div>
    20. <button style={{ color: this.state.themeColor }}>Red</button>
    21. <button style={{ color: this.state.themeColor }}>Blue</button>
    22. </div>
    23. )
    24. }
    25. }

    这时候,主题已经完全生效了,整个页面都是红色的:

    实例图片

    当然现在点按钮还是没什么效果,我们接下来给按钮添加事件。其实也很简单,监听 onClick 事件然后 store.dispatch 一个 action 就好了,修改 src/ThemeSwitch.js

    1. class ThemeSwitch extends Component {
    2. static contextTypes = {
    3. store: PropTypes.object
    4. }
    5. constructor () {
    6. super()
    7. this.state = { themeColor: '' }
    8. }
    9. componentWillMount () {
    10. this._updateThemeColor()
    11. }
    12. _updateThemeColor () {
    13. const { store } = this.context
    14. const state = store.getState()
    15. this.setState({ themeColor: state.themeColor })
    16. }
    17. // dispatch action 去改变颜色
    18. handleSwitchColor (color) {
    19. const { store } = this.context
    20. store.dispatch({
    21. type: 'CHANGE_COLOR',
    22. themeColor: color
    23. })
    24. }
    25. render () {
    26. return (
    27. <div>
    28. <button
    29. style={{ color: this.state.themeColor }}
    30. onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
    31. <button
    32. style={{ color: this.state.themeColor }}
    33. onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
    34. </div>
    35. )
    36. }
    37. }

    我们给两个按钮都加上了 onClick 事件监听,并绑定到了 handleSwitchColor 方法上,两个按钮分别给这个方法传入不同的颜色 redbluehandleSwitchColor 会根据传入的颜色 store.dispatch 一个 action 去修改颜色。

    当然你现在点击按钮还是没有反应的。因为点击按钮的时候,只是更新 store 里面的 state,而并没有在 store.state 更新以后去重新渲染数据,我们其实就是忘了 store.subscribe 了。

    Header.jsContent.jsThemeSwitch.jscomponentWillMount 生命周期都加上监听数据变化重新渲染的代码:

    1. ...
    2. componentWillMount () {
    3. const { store } = this.context
    4. this._updateThemeColor()
    5. store.subscribe(() => this._updateThemeColor())
    6. }
    7. ...

    通过 store.subscribe,在数据变化的时候重新调用 _updateThemeColor,而 _updateThemeColor 会去 store 里面取最新的 themeColor 然后通过 setState 重新渲染组件,这时候组件就更新了。现在可以自由切换主题色了:

    实例图片

    我们顺利地把 store 和 context 结合起来,这是 Redux 和 React.js 的第一次胜利会师,当然还有很多需要优化的地方。


    因为第三方评论工具有问题,对本章节有任何疑问的朋友可以移步到 React.js 小书的论坛 发帖,我会回答大家的疑问。