快速理解react-redux

40次阅读

共计 6330 个字符,预计需要花费 16 分钟才能阅读完成。

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 数据 连接起来即可。

参考
react 小书
完整代码
make-react-redux
本文如果有错,欢迎指出

正文完
 0