react-redux react和redux的结合
简述
相信很多前端开发者都听说或使用过react-redux,我曾写过一篇关于快速理解redux的文章,虽说是快速理解,但实际上更应该叫做复习redux吧。本文也只是讲述react-redux的思想及原理,对于细节实现则不赘述。
一、初始化工程
- 我们先
create-react-app
新建一个react项目,然后安装第三方库cnpm install --save prop-types
- 我们在src目录下新建3个文件,Header.js、Content.js、ThemeSwitch.js
- 组件结构是这样的
- 运行起来是这样的
二、结合context和store
-
我们先构建store,并且把它放在Index组件的context里面,那样Index以下的所有组件都可以使用store了。
class Index extends Component { static childContextTypes = { store: PropTypes.object } getChildContext () { return { store } } render () { return ( <div> <Header /> <Content /> </div> ) } }
- 我们修改Header,Content,ThemeSwitch,定义一个函_updateThemeColor在componentWillMount中调用
- 在该函数中获取Index组件context里的store,并且将store里的colorState这只为自己文本的颜色
- 我们再修改ThemeSwitch,使按钮绑定事件,点击后执行dispatch修改store里的state
-
此时我们点击按钮后,store里的数据确实改变了,可是页面却没有改变,为何?
- 因为我们忽略了subscribe,使得dispatch后_updateThemeColor函数并未执行
- 我们分别给 Header.js、Content.js、ThemeSwitch.js 的 componentWillMount 生命周期都加上监听数据变化重新渲染的代码
-
代码如下,仅以ThemeSwitch为例,其他文件类似
class ThemeSwitch extends Component { static contextTypes = { store: PropTypes.object } constructor () { super() this.state = { themeColor: '' } } componentWillMount () { const { store } = this.context this._updateThemeColor() store.subscribe(() => 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> ) } }
到这里,我们已经把react-redux的骨架搭建起来了
三、connect 和 mapStateToProps
我们观察刚刚写的组件,他们存在两个问题
-
有大量重复逻辑
- 使用Connect高阶组件解决
-
对context依赖过强,可复用性过低
- 使用mapStateToProps解决
使用高阶组件
我们需要高阶组件帮助我们从 context 取数据,我们也需要写 Dumb(纯) 组件帮助我们提高组件的复用性。所以我们尽量多地写 Dumb 组件,然后用高阶组件把它们包装一层,高阶组件和 context 打交道,把里面数据取出来通过 props 传给 Dumb 组件。
这个高阶组件被其铭文Connect,因为他可以把 Dunb组件 和 context数据 connect 起来
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
render () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState())
// {...stateProps} 意思是把这个对象里面的属性全部通过 `props` 方式传递进去
return <WrappedComponent {...stateProps} />
}
}
return Connect
}
我们将新建一个react-redux文件,将Connect放进去
我们在定义 mapStateToProps函数 使它接收某参数,返回相应的数据。因为不同的组件需要store中不同的数据
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './react-redux'
class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header)
export default Header
此时,Header 删掉了大部分关于 context 的代码,它除了 props 什么也不依赖,它是一个 Pure Component,然后通过 connect 取得数据。我们不需要知道 connect 是怎么和 context 打交道的,只要传一个 mapStateToProps 告诉它应该怎么取数据就可以了。
此时,Connect还未能监听渲染,我们需要在Connect中创建渲染函数并且在componentWillMount中添加渲染函数
export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { allProps: {} }
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState(), this.props) // 额外传入 props,让获取数据更加灵活方便
this.setState({
allProps: { // 整合普通的 props 和从 state 生成的 props
...stateProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
现在已经很不错了,Header.js 和 Content.js 的代码都大大减少了,并且这两个组件 connect 之前都是 Dumb 组件。接下来会继续重构 ThemeSwitch。
mapDispatchToProps
到目前为止,我们每次在更改数据时,都要用过store.dispatch修改。
为了使组件更 Dunb(纯) 我们对Connect和ThemeSwitch增加一个mapDispatchToProps 函数,使ThemeSwitch组件摆脱对store.dispatch的依赖
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './react-redux'
class ThemeSwitch extends Component {
static propTypes = {
themeColor: PropTypes.string,
onSwitchColor: PropTypes.func
}
handleSwitchColor (color) {
if (this.props.onSwitchColor) {
this.props.onSwitchColor(color)
}
}
render () {
return (
<div>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'red')}>Red</button>
<button
style={{ color: this.props.themeColor }}
onClick={this.handleSwitchColor.bind(this, 'blue')}>Blue</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
export default ThemeSwitch
光看 ThemeSwitch 内部,是非常清爽干净的,只依赖外界传进来的 themeColor 和 onSwitchColor。但是 ThemeSwitch 内部并不知道这两个参数其实都是我们去 store 里面取的,它是 Dumb 的。
五、Provider
我们要把所有和context有关的东西都分离出去,现在只有Index组件是被污染的
所以,我们在react-redux中新增Provider类,让它包裹成为Index的高阶函数,包裹index
使组件结构图如下
代码如下
export class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
<div>{this.props.children}</div>
)
}
}
...
// 头部引入 Provider
import { Provider } from './react-redux'
...
// 删除 Index 里面所有关于 context 的代码
class Index extends Component {
render () {
return (
<div>
<Header />
<Content />
</div>
)
}
}
// 把 Provider 作为组件树的根节点
ReactDOM.render(
<Provider store={store}>
<Index />
</Provider>,
document.getElementById('root')
)
此时,我们就把所有关于 context 的代码从组件里面删除了。
六、react-redux总结
redux的思想就是有条理地,有规则地修改共享数据。而react里刚好有 context 这个东西可以被某组件以下的所有组件共享,为了在react中优雅的修改context,react-redux就诞生了。它通过高阶函数(Connect),纯函数(mapStateToProps, mapDispatchToProps) 使我们在编写组件时完全不用接触context相关内容,只通过Connect 将 Dumb组件 和 Context数据 连接起来即可。
参考
完整代码
本文如果有错,欢迎指出
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。