Redux进阶(3)React-Redux的使用

React-Redux 的使用

  • redux是数据框架
  • React-Redux 是第三方模块,他可以让我们在react中更方便的使用redux

安装React-Redux

首先我们需要将之前的项目删除到只剩下src文件夹下的index.html。
然后安装React-Redux :

1
yarn add react-redux

(如果还未安装redux则需要先使用yarn add redux安装redux)


新建组件TodoList.js

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

class TodoList extends Component{
render(){
return(
<div>
<div>
<input />
<button>提交</button>
</div>
<ul>
<li>Dell</li>
</ul>
</div>
)
}
}

export default TodoList ;

实现效果


新建store(使用redux)

在react中,我们最好使用store来管理数据,那么我们就需要使用redux的createStore()创建一个store

新建store文件夹并在其下新建index.js:

1
2
3
4
5
import { createStore } from "redux";

const store = createStore();

export default store;

新建reducer.js

  • store需要借助 reducer 来处理action以改变state。所以我们需要新建reducer.js并在index.js中引入reducer。
  • reducer.js是纯函数,返回一个新的state给store,函数的参数1是store的state参数2是action。 一般我们会定义一个state初始值。

新建reducer.js

1
2
3
4
5
6
7
8
const defaultState = {
inputValue: "",
list: []
}

export default (state = defaultState, action) => {
return state;
}

在index.js中引入reducer

createStore()创建一个store时我们就要引入reducer作为参数。

index.js:

1
2
3
4
5
6
import { createStore } from "redux";
import reducer from "./reducer";

const store = createStore(reducer);

export default store;

在组件中使用store的数据

原本的数据使用方法

  1. 在组件中引入store并创建constructor构造函数,在构造函数内使用store的getState()获取到store的state并赋值给组件的state。
  2. 在需要使用数据的元素上通过调用组件的state来使用store的state中的值。
  3. (在reducer中给store的state一个inputValue的初始值为“你好呀”)
    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
    import React, { Component } from 'react';
    import store from "./store";

    class TodoList extends Component {
    constructor(props) {
    super(props);
    this.state = store.getState();
    }

    render() {
    return (
    <div>
    <div>
    <input value={this.state.inputValue} />
    <button>提交</button>
    </div>
    <ul>
    <li>Dell</li>
    </ul>
    </div>
    )
    }
    }

    export default TodoList;

使用react-redux之后的数据使用方法

  1. 在src的index.js中引入react-redux的 Provider组件和store,通过Provider组件使所有被其包裹的组件有能力与store连接。
  2. 在组件中引入react-redux的connect方法,在组件输出时通过connect方法连接store
    1. connect()的参数1(mapStateToProps())定义store中的数据state与组件的props之间的映射关系
      • 在组件中通过this.props.数据名引用store中的数据
    2. connect()的参数2(回调函数)接收dispatch作为参数,在参数2(mapDispatchToProps())内定义action并将其传给store

总结:

  1. Provider组件让TodoList有能力连接上store
  2. connect()让TodoList连接store
  3. 如何连接看 mapStateToProps(),修改state看 mapDispatchToProps()

Provider组件

react-redux提供一个 Provider组件 ,该组件可以连接store,并且使得被包含在该组件下的所有组件都有能力连接store

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'));

原本我们在src目录下的index.html中只将TodoList组件渲染在了页面上。
现在我们可以引入react-redux的 Provider组件,将其放在App组件(JSX语法)中,将我们所有需要用到store数据的组件都放在Provider组件中。
最后页面只渲染App组件即可.【**注意:这里渲染用的是App,不是<App />**】

connect方法

  • Provider组件 只是使得被包含的组件有能力连接store,但怎么连接就要依靠connect方法
  • connect方法让被 Provider组件 包裹的组件连接上store,从而可以直接使用store中的数据。
    • connect方法的参数1mapStateToProps(回调函数)规定组件的props和store之间怎么做连接的规则。
      • 注意:使用了connect方法后,使用this.props.xxx调用store中的数据。
    • connect方法的参数2mapDispatchToProps(回调函数):将store的dispatch()传到组件的props中,使props中的函数可以调用dispatch(),所以可以在该函数中调用dispatch将action传给store。然后在reducer中修改state的值。【修改action的操作放在参数2中
  • 让xx组件连接上store,则原本导出的是xx组件,现在我们**导出的是 connect()生成的组件connect(mapStateToProps,mapDispatchToProps)(xx)**。
总结
  • 调用connect(mapStateToProps,mapDispatchToProps)()返回值是Connect组件(请注意大小写的区别)。
    • connect(mapStateToProps,mapDispatchToProps)(需要连接store的组件名)返回的是组件
  • 通俗点理解,使用connect可以把state和dispatch绑定到react组件,使得组件可以访问到redux的数据
    1. connect让TodoList连接store
    2. 如何连接看mapStateToProps(),修改state看mapDispatchToProps()
使输入框内实时显示输入的内容

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

class TodoList extends Component {
render() {
return (
<div>
<div>
<input value={this.props.inputValue} onChange={this.props.changeInputValue} />
<button>提交</button>
</div>
<ul>
<li>Dell</li>
</ul>
</div>
)
}
}

// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中
const mapStateToProps = (state) => {
return {
// state中的inputValue映射到props的inputValue中
inputValue: state.inputValue
}
}

// 将store的dispatch()传到组件的props中
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e){
const action={
type:"change_input_value",
value:e.target.value
}
dispatch(action);
}
}
}

// connect让TodoList连接store
// 如何连接看mapStateToProps(),修改state看mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

reducer.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
const defaultState = {
inputValue: "你好呀",
list: []
}

export default (state = defaultState, action) => {
if (action.type === "change_input_value") {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
}
return state;
}

使用React-redux完成TodoList功能

好处:
使用connect()连接组件和store后,每次store更新就会带动组件的state更新,页面渲染不需要通过store.subscribe()订阅store了。

点击“确认”增加list中item

TodoList.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
52
53
import React, { Component } from 'react';
import { connect } from "react-redux";

class TodoList extends Component {
render() {
return (
<div>
<div>
<input value={this.props.inputValue} onChange={this.props.changeInputValue} />
<button onClick={this.props.handleClick}>提交</button>
</div>
<ul>
{
this.props.list.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}

// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中
const mapStateToProps = (state) => {
return {
// state中的inputValue映射到props的inputValue中
inputValue: state.inputValue,
list: state.list
}
}

// 将store的dispatch()传到组件的props中
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type: "change_input_value",
value: e.target.value
}
dispatch(action);
},
handleClick() {
const action = {
type: "add_item"
}
dispatch(action);
}
}
}

// connect让TodoList连接store,如何连接看mapStateToProps(),修改state看mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

reducer.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const defaultState = {
inputValue: "",
list: []
}

export default (state = defaultState, action) => {
if (action.type === "change_input_value") {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
}
if (action.type === "add_item") {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue="";
return newState;
}
return state;
}

点击“提交”后页面list中item增加:
效果


优化代码

  • TodoList是一个UI组件,所以我们可以将它变成一个无状态组件,依次提高性能。
  • 在TodoList.js中我们输出的是经过connect()合成以后的 容器组件(加上参数1、2后 他是有逻辑的)。
  • 所以使用了react-redux以后,我们将逻辑和页面写在了一个TodoList.js中,不像之前那样分为TodoList.js和TodoListUI.js。

优化步骤:

  1. TodoList 没有逻辑,他是一个UI组件,所以我们可以将TodoList组件优化为无状态组件
    • 无状态组件:使用常量const定义一个箭头函数,该函数接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容
  2. 使用了connect()的参数1、2后,store中state的数据都映射在props上,涉及dispatch传递action的函数也都在props上。所以我们可以使用ES6的解构赋值将所有this.props.xxx形式的变量/函数都简化为const {xxx,xxx} = props,接着调用时直接使用 变量/函数名 即可
    • 在解构赋值中,由于无状态组件接收一个参数props,所以此时**this.props变成props**。

TodoList.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
52
import React from 'react';
import { connect } from "react-redux";

const TodoList = (props) => {
const { inputValue, list, changeInputValue, handleClick } = props;
return (
<div>
<div>
<input value={inputValue} onChange={changeInputValue} />
<button onClick={handleClick}>提交</button>
</div>
<ul>
{
list.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}

// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中
const mapStateToProps = (state) => {
return {
// state中的inputValue映射到props的inputValue中
inputValue: state.inputValue,
list: state.list
}
}

// 将store的dispatch()传到组件的props中
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = {
type: "change_input_value",
value: e.target.value
}
dispatch(action);
},
handleClick() {
const action = {
type: "add_item"
}
dispatch(action);
}
}
}

// connect让TodoList连接store,如何连接看mapStateToProps(),修改state看mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

connect()与componentDidMount()

原本,connect 将store的state和dispatch包装在无状态组件TodoList上,使其成为聪明的容器组件

那么我们想要使用componentDidMount()时

  1. 先将 无状态组件TodoListUI 包装在 容器组件TodoList
  2. 在该 容器组件使用componentDidMount()
  3. 最后使用connect包装该 容器组件,形成新的容器组件

注意无状态组件调用store相关的变量/方法

  • 我们原本使用connect直接包裹 无状态组件 ,无状态组件中可以通过props拿到store中的变量/方法
  • 现在使用 容器组件TodoList 包裹 无状态组件TodoListUI ,那么无状态组件就没办法自己拿到store中的变量/方法。需要容器组件(父组件)在调用子组件(无状态组件TodoListUI)时通过属性的方式容器组件拿到的store中的变量/方法 传给子组件(无状态组件TodoListUI),子组件才能调用store中的变量/方法。

点击删除功能和mock.js模拟数据

接下来补全了actionTypes.js、actionCreator.js、mock-TodoListMock.js,并使用了redux-thunk中间件。

store文件夹-index.js 加入redux-thunk中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createStore,applyMiddleware,compose } from "redux";
import reducer from "./reducer";
import thunk from 'redux-thunk';

// redux-devtools-extension中间件
const composeEnhancers =
//如果REDUX_DEVTOOLS_EXTENSION_COMPOSE存在就调用它,否则就调用compose函数
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;//compose要从redux中引入

// thunk中间件
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);

const store = createStore(reducer,enhancer);

export default store;

store文件夹-actionTypes.js 规定action的type值为常量,防止书写错误不报错

1
2
3
4
export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_ITEM = "add_item";
export const DELETE_ITEM = "delete_item";
export const INIT_LIST = "init_list";

store文件夹-actionCreator.js 统一创建action

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
52
53
54
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM,INIT_LIST } from "./actionTypes"
import axios from "axios";
import "../mock/TodoListMock";


export const getChangeInputValue = (e) => {
return (
{
type: CHANGE_INPUT_VALUE,
value: e.target.value
}
)
}

export const getAddItem = () => {
return (
{
type: ADD_ITEM,
}
)
}

export const getDeleteItem = (index) => {
return (
{
type: DELETE_ITEM,
index
}
)
}

export const initListAction = (data) => {
return (
{
type: INIT_LIST,
data
}
)
}

export const getTodoList = ()=>{
return(dispatch)=>{
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
axios.get("/list.json").then((res) => {
const data = res.data.list;
// 通过initListAction()将data转为action对象
const action = initListAction(data);
// 将action对象传给store
dispatch(action);
}).catch(() => {
alert("失败");
})
}
}

store文件夹-reducer.js 处理action返回newState:

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
import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST } from "./actionTypes"

const defaultState = {
inputValue: "",
list: []
}

export default (state = defaultState, action) => {
if (action.type === CHANGE_INPUT_VALUE) {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState;
}
if (action.type === ADD_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = "";
return newState;
}
if (action.type === DELETE_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
if (action.type === INIT_LIST) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState;
}
return state;
}

mock文件夹-TodoListMock.js 创建随机模拟数据

1
2
3
4
5
6
import Mock from "mockjs"//引入Mock

export default Mock.mock("/list.json",{
// 随机生成长度为3的数组,数组元素是3-10个字范围内的中文单词
"list|3":["@cword(3,10)"]
})

TodoLisy.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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import React, { Component } from 'react';
import { connect } from "react-redux";
import { getChangeInputValue, getAddItem, getDeleteItem, getTodoList } from "./store/actionCreator";


// UI组件=>无状态组件
const TodoListUI = (props) => {
const { inputValue, list, changeInputValue, handleClick, handleClickDelete } = props;
// console.log(inputValue, list);//undefined undefined
return (
<div>
<div>
<input value={inputValue} onChange={changeInputValue} />
<button onClick={handleClick}>提交</button>
</div>
<ul>
{
list.map((item, index) => {
return <li key={index} onClick={() => (handleClickDelete(index))}>{item}</li>
})
}
</ul>
</div>
)
}

class TodoList extends Component {
render() {
const { inputValue, list, changeInputValue, handleClick, handleClickDelete } = this.props;
return (
<TodoListUI
inputValue={inputValue}
list={list}
changeInputValue={changeInputValue}
handleClick={handleClick}
handleClickDelete={handleClickDelete}
/>
)
}
// 生命周期函数
componentDidMount() {
this.props.initList();
}
}


// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中
const mapStateToProps = (state) => {
return {
// state中的inputValue映射到props的inputValue中
inputValue: state.inputValue,
list: state.list
}
}

// 将store的dispatch()传到组件的props中
const mapDispatchToProps = (dispatch) => {
return {
changeInputValue(e) {
const action = getChangeInputValue(e);
dispatch(action);
},
handleClick() {
const action = getAddItem();
dispatch(action);
},
handleClickDelete(index) {
const action = getDeleteItem(index);
dispatch(action);
},
initList() {
// 将异步AJAX请求放到action中
const action = getTodoList();
// 因为thunk中间件,action可以是一个函数
dispatch(action);
}
}
}

// connect让TodoList连接store,如何连接看mapStateToProps(),修改state看mapDispatchToProps()
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

完成效果

,