Redux、Flux 和 React-Redux 三者之间的区别

Redux、Flux 和 React-Redux 三者之间的区别

在之前的章节 Flux 与 Redux 当中,我们介绍了 FluxRedux 的一些基本概念和它们之间的一些区别,今天我们在来看看另外一个和它们比较类似的 React-Redux,然后在简单汇总一下,比较一下它们三者之间的区别,不过在看 React-Redux 的内容之前,我们先来简单的回顾一下上一章的内容

Flux

传统的 Redux 简化了 Flux 的流程,一般 Flux 的流程是

  • view 触发 action 中的方法
  • action 发送 dispatch
  • store 接收新的数据进行合并,触发 view 中绑定在 store 上的方法
  • 通过修改局部 state,改变局部 view

Redux

Redux 就是 Flux 思想在 React 当中的实现,它的流程是

  • view 直接触发 dispatch
  • action 发送到 reducer 中后,根节点上会更新 props,改变全局 view
  • Reduxviewstore 的绑定从手动编码中提取出来,形成了一个统一的规范最后放在了自己的体系中
  • 而在基本的 Redux 流程中,action 只是充当了一个类似于 topic 之类的角色(类似 type 属性),reducer 会根据这个 topic 确定需要如何返回新的数据
  • 数据的结构处理也从 store 中移到了 reducer

Redux 数据流如下图所示

总之记住一句话

Redux 的基本原理实际上就是围绕着 store 进行的

这个 store 不是 Flux 中的 store,而是 Redux 提供的 createStore 方法创建的 store

  • createStore 方法接收 reducer 函数和初始化的数据(currentState),并将这两个参数并保存在 store
  • createStore 时传入的 reducer 方法会在 storedispatch 被调用的时候再被调用,接收 store 中的 stateaction,根据业务逻辑修改 store 中的 state

Store

store 中包含 subscribe()dispatch()getState()replaceReducer() 这四个方法

  • 其中,subscribedispatch 顾名思义就是订阅和发布的功能
  • subscribe 接收一个回调(listener),当 dispatch 触发时,执行 reducer 函数去修改当前数据(currentState),并执行 subscribe 传入的回调函数(listener
  • getState 是获取当前 storestate(currentState)
  • 至于 replaceReducer方法,就是动态替换 reducer 函数(一般使用较少)

Middleware

下面我们再来简单的了解一下 Redux 中的 middlewareRedux 中的 middleware 简单来说只是针对于 dispatch 方法做了 middleware 处理,也就是说,只接受一个 action 对象,例如官方示例中的

1
2
3
4
5
6
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore)

store = createStoreWithMiddleware(rootReducer, initialState)

ReduxmiddlewarereduceRight 的方法,将 applyMiddleware 方法中的参数串起来,原始的 dispatch 方法会最后执行,比如下图所示

而如果需要自定义 middleware 只需要注意这个 middleware 只接收一个 action,执行后也需要返回一个 action,如果需要执行下一步,调用 next(action) 即可

关于中间件的概念,我们会在 中间件 这一章当中详细的来进行介绍

React-Redux

在简单回顾完了 FluxRedux 的一些基本概念之后,下面我们就来看看 React-Redux,其实简单来说 React-Redux 是对 Redux 流程的一种封装,使其可以适配与 React 的代码结构,React-Redux 首先提供了一个 Provider 组件(用来包裹),可以将从 createStore 返回的 store 放入 Context 中,使子集可以获取到 store 并进行操作

1
2
3
<Provider store={store}>
{() => <App />}
</Provider>

大致逻辑如下

  • 首先 React-Redux 提供了 connect 方法,将原始根节点包装在 Connect 下,在 Connect 中的 state 存储不可变对象,并将 state 对象中的 propsstore 中的 dispatch 函数传递给原始根节点
  • ConnectcomponentDidMount 中,给 store 添加 listener 方法(handleChange),每当 store 中的 dispatch 被调用时执行 handleChange
  • handleChange 会去修改 state 中的 porps,使原始根节点重新 render,并且 Connect 已经在 shouldComponentUpdate 实现了 PureRender 功能

handleChange 更新 state 中的 props 逻辑主要由三个函数构成,这三个函数都由 connect 方法传入(前两个参数用的较多)

1
2
3
4
5
connect(
mapStateToProps,
mapDispatchToProps,
mergeToProps
)(App)
  • 第一个函数接收 storestateprops,使页面可以根据当前的 storestateprops返回新的 stateProps
  • 第二个函数接收 store 中的 dispatchprops,使页面可以复写 dispatch 方法,返回新的 dispatchProps
  • 第三个函数接收前两个函数生成的 statePropsdispatchProps,在加上原始的 props 合并成新的 props 并传给原始根节点的 props

React-Redux 中的流程如下图

首先 view 触发 dispatch 然后进入 reducer,修改 store 中的 state ,再将新的 stateprops 传入 handleChange 中,生成更符合页面的 props,最后传给原始根节点重新 render,下面我们就深入的来了解一下 React-Redux 当中的相关概念,React-Redux 将所有组件分成了以下两大类

  • UI 组件(presentational component
  • 容器组件(container component

下面我们就分别来看这两类组件

UI 组件

UI 组件有以下几个特征

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用 this.state 这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 ReduxAPI

因为不含有状态,UI 组件又称为纯组件,即它像纯函数一样,纯粹由参数决定它的值

容器组件

容器组件的特征恰恰相反

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 ReduxAPI

总之,只要记住一句话,UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑

React-Redux 当中比较常用的 API 就两个,一个是提供的一个组件 <Provider>,另一个就是 connect() 方法

connect()

React-Redux 提供 connect 方法,用于从 UI 组件生成容器组件,connect 的意思就是将这两种组件连起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { connect } from 'react-redux'

const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}

const mapDispatchToProps = (dispatch) => {
return {
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
dispatch(action)
},
handleSubmit() {
const action = {
type: 'add_todo_item'
}
dispatch(action)
},
handleDelete(index) {
const action = {
type: 'delete_todo_item',
index
}
dispatch(action)
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoList)

上面代码中,connect 方法接受两个参数,mapStateToPropsmapDispatchToProps,它们定义了 UI 组件的业务逻辑,前者负责输入逻辑,即将 state 映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 action

connect() 方法有两个比较重要的参数,mapStateToPropsmapDispatchToProps(都是函数)

mapStateToProps

官方解释如下

  • 如果你传入了这个参数,那么这个组件将会注册 Reduxstore 的更新信息(简单的可以理解为虚拟 DOM 那样,即变化了会自动更新)
  • 即意味着无论什么时候 store 更新了,mapStateToProps 函数将会被调用
  • mapStateToProps 的返回值必须是一个 plain object(简单的对象),这个对象将和组件的 props 融合,也就是说返回的对象中的 key 将自动成为组件的 props 中的成员(就比如上例中的 App 组件中的参数)
  • 如果不想订阅 store 的更新,可以不用传入该参数,此时使用 null 来占位即可(不能不传)

抛开官方那些比较繁琐的解释,本质上 mapStateToProps() 就是一个函数,它的作用就是建立一个从(外部的)state 对象到(UI 组件的)props 对象的映射关系,作为函数 mapStateToProps 执行后应该返回一个对象,里面的每一个键值对就是一个映射

1
2
3
4
5
6
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}

这个函数内部返回的键值对可以被 connect() 传入的参数组件所拿到(上例中的 App),即 App 组件当中可以拿到传递过去的参数(state)(当作 props 来使用),而 </Provider> 中传递的 store 之所以可以被全局使用,也是依靠的这个函数(间接的通过参数传递过去了),然后最重要的一点就是,它可以得到全局唯一的 store 中的 state

mapDispatchToProps

mapDispatchToPropsconnect 函数的第二个参数,用来建立 UI 组件的参数到 store.dispatch 方法的映射,也就是说,它定义了哪些用户的操作应该当作 action,传给 store,官方解释如下

  • 如果传入了第二个参数,并且是一个函数,那么这个函数将获得 dispatch 方法(可以发出 action,也可以导致 statestore) 改变)
  • connect() 方法第一个参数来获得 state(但是不能修改),第二个参数可以用来修改 state(但是最终都传递回了 App 组件)

它接收参数 dispatchdispatchstore 中用来分发命令的 API,这里简化了),返回一个总的 actions 清单对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mapDispatchToProps = (dispatch) => {
return {
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
dispatch(action)
},
handleSubmit() {
const action = {
type: 'add_todo_item'
}
dispatch(action)
},
}
}

<Provider>

connect 方法生成容器组件以后,需要让容器组件拿到 state 对象,才能生成 UI 组件的参数,React-Redux 提供 Provider 组件,可以让容器组件拿到 state

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react'
import ReactDOM from 'react-dom'
import TodoList from './TodoList'
import { Provider } from 'react-redux'
import store from './store'

const App = (
<Provider store={store}>
<TodoList />
</Provider>
)

ReactDOM.render(App, document.getElementById('root'));

在上面的示例当中,Provider 在根组件外面包了一层,这样一来 App 的所有子组件就默认都可以拿到 state

# React

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×