React+Redux基本使用流程

Redux简介

Redux是React生态中重要的组成部分。React提出将展示组件与容器组件分离的思想,降低了React与Redux之间的耦合度。

React+Redux

  • Store:整个应用的数据存储中心,集中大部分页面需要的状态数据;
  • ActionCreators:view 层与data层的介质;
  • Reducer:接收action并更新Store。

所以流程是用户通过界面组件触发ActionCreator,携带Store中的旧State与Action流向Reducer,Reducer返回新的state,并更新界面。

案例实现

依赖库

1
2
3
4
yarn add prop-types
yarn add react-redux
yarn add redux
yarn add redux-thunk

页面组件

这是一个纯React代码 ,结构清晰

component/person.js

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React, {Component} from 'react';
import PropTypes from 'prop-types'

class Person extends Component {
//声明属性
static propTypes = {
name: PropTypes.string.isRequired,
nameCreator: PropTypes.func.isRequired,
age: PropTypes.number.isRequired,
ageCreator: PropTypes.func.isRequired,
nameAsync: PropTypes.func.isRequired
}
//点击事件
handlerNameFunc = () => {
const inputName = this.refs.inputValueName.value;
this.props.nameCreator(inputName);
}
handlerAgeFunc = () => {
const inputAge = this.refs.inputValueAge.value;
this.props.ageCreator(inputAge);
}
handlerAsyncFunc = () => {
const inputName = this.refs.inputValueName.value;
this.props.nameAsync(inputName);
}

//渲染界面
render() {
const {name, age} = this.props;
return (
<div>
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<label> {name} </label><br/>
<input ref="inputValueName"/>
<button onClick={this.handlerNameFunc}>Name Confirm</button>
<button onClick={this.handlerAsyncFunc}>Async Confirm</button>
<br/>

<label> {age} </label><br/>
<input ref="inputValueAge"/>
<button onClick={this.handlerAgeFunc}>Age Confirm</button>
<br/>
<br/>
</div>
);
}
}

export default Person;

ActionCreator

redux/action-type.js

ActionCreator会传达用户的操作信息以及一些数据。定义一些常量供我们使用,这里就两种操作,一是添加名字,二是添加年龄,实际都一样。为了后面实现reducer的合并强行写了俩。这些常量一般都定义在actionTpye文件中。

1
2
export const NAME = 'NAME'
export const AGE = 'AGE'

redux/actions.js

接着就是ActionCreator ,定义了一些操作类型,告诉store自己是干什么的,需要什么样的数据。

1
2
3
4
5
6
7
8
9
10
11
12
import {NAME, AGE} from "./action-type";

//包含所有的action creator
export const nameCreator = (name) => ({type: NAME, data: name})
export const ageCreator = (age) => ({type: AGE, data: age})
export const nameAsync = (name) => {
return dispatch => {
setTimeout(() => {
dispatch(nameCreator(name))
}, 2000);
}
}

redux/reducers.js

Reducer会接收到action的信息。然后进行状态(数据)的处理,相当于react中的setState()的功能。如果有多个reducer ,可以使用combineReducers方法将其合并,并暴露出去。

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
//包含n个reducer函数的模块
import {NAME, AGE} from './action-type'
import {combineReducers} from 'redux'

function name(state = 'initRedux', action) { //形参默认值
switch (action.type) {
case NAME:
return action.data
default:
return state
}
}

function age(state = 0, action) {
switch (action.type) {
case AGE:
return action.data
default:
return state
}
}

export const finalReducer = combineReducers({
name, age
})

其中state=’initRedux’ 、state=0 相当于我们在React组件内部初始化state

redux/store.js

一切操作还是基于Store 的。类似于中央集权。所以还要把Store建立出来

1
2
3
4
5
6
7
8
import {createStore, applyMiddleware} from 'redux'
import {finalReducer} from './reducers'
import thunk from 'redux-thunk'

//生成store对象
const store = createStore(finalReducer, applyMiddleware(thunk)); //内部会第一次调用reducer函数,得到初始state

export default store

因为reducer会更新Store中的状态(数据),所以需要引入reducer,并创建store

至此,流程图结束。不过2、3、4都是redux中负责接管React状态的功能。1是React负责展示的组件。两者并没啥关系。既然有了展示组件,接下来就要有容器组件,也就是能够将React与redux相关联的一个组件。

容器组件

containers/redux-app.js

1
2
3
4
5
6
7
8
9
10
11
import {connect} from 'react-redux'
import {nameCreator, ageCreator, nameAsync} from '../redux/actions'
import Person from '../component/person'

export default connect(
state => ({
name: state.name,
age: state.age
}),
{nameCreator, ageCreator, nameAsync}
)(Person)

这里用到react-redux中的connect,可以将React与redux关联起来。Person就是第一步写的组件名,state中关联了React中的属性。

添加store

src/index.js

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

ReactDOM.render((
//使用Provider 组件将APP主组件包裹住,这样内部组件都有Store种提供的属性
<Provider store={store}>
<ReduxApp/>
</Provider>
), document.getElementById('root'));

文件结构

  • redux:集中管理状态(数据)
  • component:专注于React 展示组件部分
  • containers:集中处理React与redux交互的部分

此外,redux还可以处理一些异步请求,可以做到data和view的管理分离,增强了工程结构的可读性与可维护性。

深入理解Redux

Action与Reducer的详细理解

先回顾一下不使用Redux的情况下,React的Class该怎么写

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

class Test extends Component {
constructor(props){
super(props);
state = {
nickname: ''
}
}
handlerFunc = () => {
const inputName = this.refs.inputValue.value;
this.setState({
nickname: inputName
})
}
render() {
const {nickname} = this.state;
return (
<div>
<label> {nickname} </label><br/>
<input ref="inputValue" /><br/>
<button onClick={this.handlerFunc}>confirm</button><br/>
</div>
);
}
}

主要流程:constructor里初始化状态,然后渲染组件,onClick里绑定点击事件,事件函数中setState()一下,此时得到新的状态,并使虚拟DOM重新渲染,最终Label显示我们输入的值。

然后看看使用Redux后,组件是怎么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, {Component} from 'react';
import PropTypes from 'prop-types'
class Test extends Component {
static propTypes = {
nickname:PropTypes.string.isRequired,
addName:PropTypes.func.isRequired
}
handlerFunc = () => {
const inputName = this.refs.inputValue.value;
this.props.addName(inputName);
}
render() {
const {nickname} = this.props;
return (
<div>
<label> {nickname} </label><br/>
<input ref="inputValue" /><br/>
<button onClick={this.handlerFunc}>confirm</button>
</div>
);
}
}

观察两种写法,发现:
(1)初始化状态没有了
(2)setState()也移除了,然后通过属性结构赋值

总之,状态管理方面的东西都被移除。因为采用Redux框架,本身就是要让其接管状态的,所以,redux框架下的React代码就很好理解了。就是react组件里面是没有State相关的东西(当然,特殊情况下还是需要用的组件自己的状态管理的)。redux所要做的就是,传递动作(按钮点击)或者请求(请求数据),并根据自身属性向HTML中填入数据。这里用到了prop-types这个库。来进行属性的校验。

之后可以这样理解,setState()方法在Redux那边被拆分成两步,现在Action中,把获取到的值,拼装成一个Object,代码如下:

1
2
3
4
import { NAME } from "./action-type";

//包含所有的action creator
export const addName = (name) => ({type:NAME, data:name})

这里面的addName 就是React代码里按钮触发的方法中要执行的函数,name 就是我们或得到的输入框里的值。

接着redux帮我们做的就是用Store中提供的dispatch方法,把这个动作传到reducer那去,然后来看看Reducer做了什么:

1
2
3
4
5
6
7
8
9
10
11
12
import {NAME} from './action-type'
const initialState = {
name: 'Joe.Ye'
}
const name = (state=initialState, action) => { //形参默认值
switch(action.type){
case NAME:
return action.data
default:
return state
}
}

Reducer可以类别理解为:既充当了setState()的角色,也负责了初始化state的任务

所以,reducer在case到NAME这个动作之后,就把从action传来的值,做了一个相当于setState()的操作:

1
2
3
this.setState({
name: action.data
})

要是一开始没有触发任何动作,则Store会帮我们在程序运行之初执行一遍Reducer,使用初始值initialState

展示组件与容器组件

redux写的代码单拎出来与react代码是没啥联系的。写完上面的代码后,一运行就会报错,一大堆undefined。这个时候就需要一个connect来连接Redux和React。现在有这么一个库react-redux,使用里面的connect即可。如下:

1
2
3
4
5
6
7
8
9
10
11
import React from 'react'
import {connect} from 'react-redux'
import {addName} from '../redux/actions'
import Test from '../component/Test'

export default connect(
state => ({
nickname: state.name
}),
{addName}
)(Test)

(1)容器组件

这样就写好了一个组件,官方称之为容器组件,就是引入之前写好的React组件Test,再引入之前action和reducer里的动作、状态,进行一个封装,这样就把Test里事件函数与属性匹配到了一起。

其中nickname对应Test里面的nickname属性,state.name就是reducer里定义的那个name。到时候Test组件里更新nickname的值,就是state.name给赋上去的。属性状态匹配完之后要匹配动作,Test中点击事件中执行的addName,就匹配action里的addName。这样,点击按钮之后,数据就会通过action传到Reducer那去做类似于setState()这样的操作。

(2)展示组件

就是Test那个组件,里面一般不包含任何Redux相关的东西,纯净的React组件,称之为展示组件。可以理解为容器组件(封装组件)的一部分

异步

一个web应用,大多数都需要做请求操作的。而在React + Redux这样的一个结构中,异步请求操作最合适的位置就是放到Redux中的action中。首先,展示组件,即Test组件,这个是view层。view层显然不适合掺杂异步请求的。然后reducer其实是实现的类似于setState()的作用。所以在Redux中的Action中做异步请求是比较合适的。action其实也是个传接数据的载体。

既然选择在Redux中进行异步请求,就需要引入一个库react-thunk,来帮助我们进行异步请求。首先进行配置:

1
2
3
4
5
6
7
8
import {createStore, applyMiddleware} from 'redux'
import {finalReducer} from './reducers'
import thunk from 'redux-thunk'

//生成store对象
const store = createStore(finalReducer, applyMiddleware(thunk)); //内部会第一次调用reducer函数,得到初始state

export default store

正常来说,一般我们是触发一个动作进行请求,得到数据后,再去setState。在Action中也是一样,如下:

1
2
3
4
5
6
7
export const nameAsync = (name) => {
return dispatch => {
setTimeout(() => {
dispatch(nameCreator(name))
}, 2000);
}
}

这里用setTimeout模拟请求过程。两秒后,数据请求到了,再把数据给Reducer进行状态替换。这里我们需要手动调用dispatch这个函数,向Reducer去分发动作。

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :