学习react-readx状态管理

学习react-readx状态管理

八月 17, 2021

本文针对React与Redux,如有错误,欢迎指正!

禁止全文转载,可以部分转载,转载请注明出处。

Redux是什么?

Redux 是 JavaScript 状态容器,提供可预测化的状态管理

可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览

Redux除了和React一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)。

Why redux?

为什么React需要Redux,或者说为什么Redux适合与React合作呢?

第一个原因:

我们知道,React的数据是单向流动的,而根据React官方的React 哲学中所述,

React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand, so follow these steps to figure it out:

For each piece of state in your application:

  • Identify every component that renders something based on that state.
  • Find a common owner component (a single component above all the components that need the state in the hierarchy).
  • Either the common owner or another component higher up in the hierarchy should own the state.
  • If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.

这意味着什么?

Either the common owner or another component higher up in the hierarchy should own the state.

  • 该共同所有者组件或者比它层级更高的组件应该拥有该 state。

意味着我们产生数据的地方(例如input标签)可能离储存数据的地方(上文中的更高级组件)之间可能隔了数层!

然而,想要控制一个输入标签至少需要两个传给子组件的参数(一个获取,一个传递),又隔了数层,麻烦不说,同时极大的增加了出错概率!当然,你也可以以高阶组件的方式将输入组件通过参数向下传递,可是这又破坏了代码结构,降低了代码可读性,提高了代码耦合性。

遇到这种情况,请试试Redux吧!


第二个原因:

当你的页面(特别是SPA:单页应用程序)有很多状态需要管理时,你需要记住每一个状态的改变由谁产生,影响了谁,这并不是一件简单的事!有了Redux,你可以做到简单快捷的理顺这一切,并在调试时清楚的看到它们!

当然,Redux并不是只能和React配合使用,只要你想,可以在任何需要状态管理的地方使用它!

Redux的核心概念

正如Redux官网上说的那样,Redux本身很简单,以我所见,它主要分为:

  • 事件:Action
  • 处理者:Reducer
  • 仓库:Store

请允许我以我的理解试着解释它们:

仓库Store

这个简单,就是储存数据的地方,我们通过

1
2
import {createStore} from 'redux';
const store = createStore();

创建一个Store。

当然这个函数有几个参数,后面再作介绍,你只需要知道:

store是集中储存数据的地方,它保证了数据只能由action经由reducer传递过来,自动传递到需要的地方。

出于方便考虑,store的初始值由reducer给出,这个后面再讲。

注意:对于一个程序,你最好预先定义好一个store对象,并在后面严格遵守!

事件Action

Action或许应该翻译成行动,但在这里,我认为事件更准确。

事件就是某一些改变了数据的事情,它是唯一能改变store的方法,任何一个绑定了redux的组件都可以产生action,但你可以通过定义并限制action的使用类型来限制组件能改变的数据。

例如:对应一个商店系统,你定义了许多action,其中action A可以改变store中商品的存货,action B可以改变store中商品的单价。假设有一个点击从后台获取存货数量的组件,那么只需要限制它只能使用action A即可。

action本质上是一个描述数据改变的对象,这个对象的“形状”是任意的,但对于每一个程序,你最好定义一个统一标准,例如:

1
2
3
4
5
6
{
"type": "ChangeScores",
"data": {
"scores": 233
}
}

处理者Reducer

reducer的意思是还原剂,搞不懂。。。

处理者reducer很好理解,就是根据action的类型,将action中的数据取出来,合并到store中,它是一个函数:

1
2
3
4
5
6
7
8
const myReducer = (action, store) => {
// 原store不可修改,需要复制一份
store = {...store}; // 也可以使用 `Object.assign({}, store)`
/*
* 数据合并操作
*/
return store;
}

一个store有且只能有一个reducer,因此这个reducer需要处理所有种类的action,你可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const doChangeScores = (data, store) => {
store.scores = data.scores;
return store;
}

const myReducer = (action, store = {/* store的初始值 */}) => {
store = {...store};
switch (action.type) {
case "ChangeScores":
return doChangeScores(action.data, store);
case "ChangeXXXXX":
// ............
default:
return store;
}
if (action.type === "ChangeScores")
return store;
}

入=如上例所示,可以在reducer中方便的给出store的初始值。

至于为什么不能之间改变store,而是复制一份,因为Redux内部需要比较store是否改变了,借此判断受影响组件是否更新。

他们的关系

下面用一段生动的话来讲解他们的关系:


现有三个人分析“巨硬”公司的市场行情,

数据收集师action发现“巨硬”公司股票又涨了,他告诉会计reducer:“喂,老兄,‘巨硬’公司股票又涨了aaa。”

会计reducer听到后,查询数据库中上月的“巨硬”公司股票,并做出了计算,但是reducer不会修改数据库,只能把数据库管理员store叫来,告诉他:“‘巨硬’公司这个月股票bbb,增长了ccc”。

数据库管理员store回答:“了解!”并在数据库里加上:“八月‘巨硬’公司月股票bbb,增长了c%”。然后通知领导把数据拿走了,用来指定下一步计划。。。


简单来说:

action通知reducer数据改了,reducer将数据合并,交给storestore修改自身数据,通知数据的使用者。

connect函数

connect函数非常重要!它描述了组件如何连接store

1
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(Component)

主要需要了解前两个参数

  1. mapStateToProps(state, [ownProps]): stateProps

    这是一个函数,提供从stateprops的映射,也就是说,定义该组件需要从store中获取什么到props中。

    这将导致:store刷新时该组件重新渲染。

  2. mapDispatchToProps(dispatch, [ownProps]): dispatchProps

    这是一个函数,提供action发送到reducer的手段,它返回一个对象,该对象会合并到组件的props中,对象里全是函数,这些函数根据mapDispatchToProps接收的dispath参数调用reducer,具体请看下面的例子。

  3. mergeProps(stateProps, dispatchProps, ownProps): props

    一个函数,将上两个函数的执行结果传入,并传入组件自身的props,返回值作为组件的实际props

    例如,它的默认值是(**重点理解!**

    1
    (stateProps, dispatchProps, ownProps) => {Object.assign({}, stateProps, dispatchProps, ownProps)};
  4. options

    一个对象,指定connector的行为。

一个例子

安装依赖

1
yarn add redux react-readx

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createStore } from "redux";

function reducer (state = { source: 0 }, action) {
switch (action.type) {
case "Scores/Change":
return { source: state.scores + action.count };
default:
return state;
}
}

export default createStore(reducer);

// 产生其中一种 `action` 的工厂函数
export const ChangeScores = (count) => {
return {
type: "Scores/Change",
count: count
}
}

ChangeScores.jsx

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
import React, { Component } from 'react';
import MyButton from './MyButton';
import Input from './Input';

class ChangeScores extends Component {
constructor(props) {
super(props);
this.state = { value: 0 };
this.handleChangeValue = this.handleChangeValue.bind(this);
}

handleChangeValue(value) {
this.setState({
value: value
})
}

render() {
return (
<div>
<Input handleChangeValue={ this.handleChangeValue }/>
<MyButton value={this.state.value}>Change Scores</MyButton>
</div>
);
}
}

export default ChangeScores;

MyButton.jsx

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
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { ChangeScores } from '../redux/store';

class MyButton extends Component {
constructor(props) {
super(props);
this.onChangeScores = this.onChangeScores.bind(this);
}

onChangeSource() {
this.props.onChangeScores(this.props.value);
}

render() {
return (
<button onClick={this.onChangeScores}>{ this.props.children }</button>
);
}
}

function mapDispatchToProps(dispath) {
return {
onChangeScores: (value) => dispath(ChangeScores(value))
}
}

export default connect(null, mapDispatchToProps)(MyButton);

这里的ChangeScores()是一个工厂函数,产生改变scoresaction

Input.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react';

class Input extends Component {
constructor(props) {
super(props);
this.onChangeValue = this.onChangeValue.bind(this);
}

onChangeValue(e) {
const value = parseInt(e.target.value);
if (Number.isInteger(value)) {
this.props.handleChangeValue(value);
}
}

render() {
return (
<label>value<input onChange={this.onChangeValue} type="text"></input></label>
)
}
}

export default Input;

ShowScores.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from 'react';
import { connect } from 'react-redux';

class ShowScores extends Component {
render() {
return (
<h2>Source: { this.props.source }</h2>
);
}
}

function mapStateToProps(state) {
return {
source: state.source
}
}

export default connect(mapStateToProps)(ShowScores);

App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from 'react';
import ChangeScores from '../components/ChangeScores';
import ShowScores from '../components/ShowScores';

class App extends Component {
render() {
return (
<div>
<ShowScores/>
<ChangeScores/>
</div>
);
}
}

export default App;

例子