既然要把 store 和 context 结合起来,我们就先构建 store。在 src/index.js 加入之前创建的 createStore 函数,并且构建一个 themeReducer 来生成一个 store:
src/index.js
createStore
themeReducer
store
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 里面的 Index:
themeColor
CHNAGE_COLOR
Index
class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store } } render () { return ( <div> <Header /> <Content /> </div> ) } }
如果有些同学已经忘记了 context 的用法,可以参考之前的章节: React.js 的 context 。
然后修改 src/Header.js,让它从 Index 的 context 里面获取 store,并且获取里面的 themeColor 状态来设置自己的颜色:
src/Header.js
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.themeColor。
constructor
componentWillMount
_updateThemeColor
store.getState()
state.themeColor
然后在 render 函数里面获取了 state.themeColor 来设置标题的样式,页面上就会显示:
render
如法炮制 Content.js:
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.js:
src/ThemeSwitch.js
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> ) } }
这时候,主题已经完全生效了,整个页面都是红色的:
当然现在点按钮还是没什么效果,我们接下来给按钮添加事件。其实也很简单,监听 onClick 事件然后 store.dispatch 一个 action 就好了,修改 src/ThemeSwitch.js:
onClick
store.dispatch
action
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 和 blue,handleSwitchColor 会根据传入的颜色 store.dispatch 一个 action 去修改颜色。
handleSwitchColor
red
blue
当然你现在点击按钮还是没有反应的。因为点击按钮的时候,只是更新 store 里面的 state,而并没有在 store.state 更新以后去重新渲染数据,我们其实就是忘了 store.subscribe 了。
state
store.state
store.subscribe
给 Header.js、Content.js、ThemeSwitch.js 的 componentWillMount 生命周期都加上监听数据变化重新渲染的代码:
Header.js
ThemeSwitch.js
... componentWillMount () { const { store } = this.context this._updateThemeColor() store.subscribe(() => this._updateThemeColor()) } ...
通过 store.subscribe,在数据变化的时候重新调用 _updateThemeColor,而 _updateThemeColor 会去 store 里面取最新的 themeColor 然后通过 setState 重新渲染组件,这时候组件就更新了。现在可以自由切换主题色了:
setState
我们顺利地把 store 和 context 结合起来,这是 Redux 和 React.js 的第一次胜利会师,当然还有很多需要优化的地方。 {% endraw %}
因为第三方评论工具有问题,对本章节有任何疑问的朋友可以移步到 React.js 小书的论坛 发帖,我会回答大家的疑问。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8