Redux入门(1)

Redux概念简述

  • Redux 是全球范围内比较推荐的 和React进行搭配的 数据层框架
  • 无论程序多么复杂,都不需要我们手动传值了,数据传递的过程都是:组件数据改变-> Store改变 -> 其他组件取值
  • Redux=Reducer+FluxFlux是FaceBook公司最早配合React推出的数据层框架,但是它问题很多,所以作者Dan引入Reducer的概念创造了Redux

复习:React视图层框架

  • React的文档首页就说明了“A JavaScript library for building user interfaces”,也就是“React是一个用于创建用户接口的JavaScript库”。
  • React是轻量型的视图层框架,只能处理简单的数据关系(父子组件中的传值)多几层就只能一层一层传,具体可看笔记围绕 React 衍生出的思考或者下面的例子。

例子

假设 绿色组件 想给很多灰色的组件 传值
示意图
如果我们改变了 绿色子组件 的数据,只使用React就需要先调用 绿色子组件的父组件的方法 改变 父组件中的相关值(第一次子组件向父组件传值),然后使用 同样的方法 层层传递,直到最顶端。
如果我们想要方便的进行传值就需要搭配 数据层框架 结合使用。

使用Redux继续上面的例子

当我们使用Redux时,

  • 所有组件的数据都不放在自身,而是放在公共存储区域Store
  • 当绿色组件放在Store中的数据发生改变时,其他灰色组件就会感知到Store中的数据发生变化,自动到Store中获取改变的绿色组件的数据。
  • 这样一来绿色组件的数据就很轻松的传给了其他灰色的组件。(实际上不需要有传递这一步)

使用Antd实现TodoList页面布局

  • Antd:React的UI框架(React的UI组件库)
  • Antd(Ant Design of React)的**官网**
  • antd可以非常方便快捷的完成一个后端的页面布局,但是由于我们接下来做的是用户端,所以可能并不会用到特别多antd。
  • 接下来的例子会使用React和Redux重新编写TodoList的页面布局

安装Antd(React的UI组件库)

在官网中提到,使用 npm 或 yarn 安装:

1
yarn add antd

安装完成后重启服务器:

1
npm start

使用Antd

引入样式

1
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'

需要什么组件就引入什么组件,比如我想放入一个<input>框,就可以到官网搜索得到:
input
选择一个喜欢的,查看他的代码,使用即可:
显示代码

完整例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from 'react'
import 'antd/dist/antd.css';
import { Input } from 'antd';

class TodoList extends Component {

render() {
return (
<div>
<div>
<Input placeholder="Todo info" style={{ width: "300px" }} />
</div>
</div>
)
}
}

export default TodoList;

显示效果

选一个按钮button
button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react'
import 'antd/dist/antd.css';
import { Input, Button } from 'antd';

class TodoList extends Component {

render() {
return (
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
<div>
<Input placeholder="Todo info" style={{ width: "300px", marginRight: "10px" }} />
<Button type="primary">提交</Button>
</div>
</div >
)
}
}

export default TodoList;

效果

加入列表list

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
import React, { Component } from 'react'
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';

// 列表数据放在外部(全局)
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];

class TodoList extends Component {

render() {
return (
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
<div>
<Input placeholder="Todo info" style={{ width: "300px", marginRight: "10px" }} />
<Button type="primary">提交</Button>
</div>
<List
style={{ marginTop: "10px", width: "300px" }}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div >
)
}
}

export default TodoList;

效果


Redux的工作流程

Redux的工作流程

注意

获取store中数据时

  • Redux中store是最重要的,首先就要编写数据存储的仓库(store
  • 但是光创建 store 是没用的,这个“图书管理员”什么都不知道,还需要同时创建“管理员的记录本”**Reducers然后给到这个“图书管理员”store**。
  • reducer返回(输出)的是一个函数,函数中的 参数state 就是整个store中存储的数据(参数action在后面”改变store中数据”中讲)。
  • 现在store里已经有数据了,reducer也创建好了,那么组件就可以连接store去里面取数据然后显示出来了。

改变store中数据时

  • 想要改变store中的数据就需要在 组件 中创建一个action。(在react中,action是一个对象的形式,type属性 描述要做的事情,value属性 为需要传的值。)
    注意:value属性名是自定义的,根据你需要传的值的名字来定
  • store提供了一个dispatch方法,**可使用store.dispatch(action)action传给store**。
  • store自动将 接收到的数据 和action一起传给reducerreducer返回的函数中的state就是 上次传过来的数据,action就是组件想要修改state的那句话
  • reducer会**根据store传来的 上一次的数据(state) 和action整合起来后进行处理,store返回一个新数据newState**。
  • reducer处理过程reducer 可以接受state,但是决不能修改state。所以我们需要对store传进来的state进行深拷贝newState,然后再进行action的处理,最后返回newStatestore
  • 此时页面和store数据并未同步,需要使用在 组件的构造函数 中使用store.subscribe(this.handleStoreChange);让**组件订阅store**,那么只要store发生改变,handleStoreChange函数 就会被执行一次。
  • 在handleStoreChange函数中,使用getState()获取store中的数据 替换 组件中的state数据,组件的state发生改变后,页面自然重新渲染。

流程总结

获取store数据

  1. 创建Redux中的store(src-store-index.js)
  2. 创建Reducer(src-store-reducer.js)并将state中 默认数据 设置好
  3. store中引入Reducer并在创建store时将Reducer 作为第一个参数传给store,以便获取 默认state数据(defaultState
  4. 组件TodoList.js去连接store,通过store.getState()获取数据然后显示在页面上

改变store数据

  1. 点击输入框时,在组件中**创建 对象action**,type属性 描述要做的事情,value属性 为需要传的值。(TodoList.js-handleInputChange()
  2. 使用store.dispatch(action)**将action传给store**。(TodoList.js-handleInputChange()
  3. store自动将 接收到的数据(state) 和action一起传给reducer
  4. reducer根据store传来的 上一次的数据(state) 和action整合起来后进行处理,**给store返回一个新数据newState**(reducer.js中输出的函数中)
  5. reducer处理过程:对store传进来的state进行深拷贝newState,然后再进行action希望的处理,最后返回newStatestore
  6. 在 组件的构造函数 中**使用store.subscribe(this.handleStoreChange);组件订阅store**,那么只要store发生改变,handleStoreChange函数 就会被执行一次。
  7. 在handleStoreChange函数中,使用getState()获取store中的数据 替换 组件中的state数据。那么组件的数据一更新页面自然重新渲染。

使用redux获取store数据(state)

首先需要在项目中添加redux,执行:

1
yarn add redux

创建Redux中的store

创建store
src下创建 文件夹store,在此文件夹下创建index.js用于创建store:

1
2
3
4
5
6
7
8
// 引入`redux`库的`createStore`模块
import { createStore } from "redux";

// 使用`createStore()`创建store
const store = createStore();

// 记得输出
export default store;

创建reducer

在文件夹store下创建**reducers.js用于创建reducer**(state就是整个store中存储的数据)reducer返回(输出)的是一个函数】:

1
2
3
4
5
6
const defaultState = {};

// 默认state为空对象:默认store上什么数据都没有
export default (state = defaultState, action) => {
return state;
}

state就是整个store中存储的数据,defaultState是我们定义的默认数据,也就是一开始放在store中存储的数据
我们知道实际上TodoList需要存储的是两个数据:inputValuelist,所以我们可以将它们放在**默认数据defaultState**中:

1
2
3
4
const defaultState = {
inputValue: "请输入一些待办事项",
list: ["吃饭","睡觉"]
};

stroe中引入reducer

stroe(index.js)中引入reducer:
在**index.js中引入reducers(图书管理员拿到记录本),并且在创建store时将reducers作为第一个参数传给store**:

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

// 创建store,将reducers作为第一个参数传给store
const store = createStore(reducer);

export default store;

现在store里已经有数据了,reducer也创建好了,那么组件就可以连接store去里面取数据然后显示出来了。

组件TodoList连接store获取数据

注意

  • 组件引入时,**./store相当于./store/index.js**,因为会默认到index.js中去寻找组件。
  • 组件store提供了**getState()用于获取store中存储的数据**。

TodoList.js中删除我们原本列表写死的数据。引入store。使用getState()获取store中存储的数据并保存在组件TodoListstate中。将state中的数据用于input框的value值(输入框默认显示)与list列表的datasource(列表的数据来源):

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 React, { Component } from 'react'
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';
// `./store`相当于`./store/index.js`
import store from "./store";

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

render() {
return (
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
<div>
<Input value={this.state.inputValue} placeholder="Todo info" style={{ width: "300px", marginRight: "10px" }} />
<Button type="primary">提交</Button>
</div>
<List
style={{ marginTop: "10px", width: "300px" }}
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div >
)
}
}

export default TodoList;

效果


安装chrome插件Redux DevTools

  • Redux DevTools可以帮助我们进行redux的调试
  • “扩展程序” - 打开 Cent Browser 网上应用商店 - 搜索Redux DevTools - 添加 :
    添加成功
    显示“找不到store,需要跟着指南做配置”。

配置过程

首先需要我们store文件夹下的index.js中创建store时传入第二个参数
配置

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

// 创建store,将reducers作为第一个参数传给store
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default store;

该参数的意思是“如果window下安装了__REDUX_DEVTOOLS_EXTENSION__这个扩展程序就在页面上使用__REDUX_DEVTOOLS_EXTENSION__这个工具”。

此时点开控制面板就可以看到state里面的数据:
控制面板


使用redux改变store中数据(action)

input框内数据改变时改变store中的值

首先我们希望**input框内数据改变时,state中的inputValue也跟着变。**

TodoList.jsrender函数中,给**input框 绑定onChange事件函数handleInputChange()**:

1
2
3
4
5
6
<Input
value={this.state.inputValue}
placeholder="Todo info"
style={{ width: "300px", marginRight: "10px" }}
onChange={this.handleInputChange}
/>

TodoList.js的构造函数中,绑定this指向
注意:这里的store是顶部import store from "./store";已经引用的store

1
2
3
4
5
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
}

在事件函数handleInputChange()中通过打印测试是否能在 input框 中发生改变时获取到 input框 中的 value值:

1
2
3
handleInputChange(e) {
console.log(e.target.value);
}

成功打印
那么接下来就要告诉store,我想让store中的stateinputValue改成e.target.value

创建action传给store

那么我们就需要创建一个action,在react中,**action是一个对象的形式,type属性 描述要做的事情,value属性 为需要传的(现在的)值**。store提供了一个dispatch方法,可使用该方法将action传给store
注意:这里的store是顶部import store from "./store";已经引用的store

1
2
3
4
5
6
7
8
9
10
handleInputChange(e) {
// `action`是一个对象的形式
const action = {
// `type`描述要做的事情,`value`为需要传的(现在的)值
type: "change_input_value",
value: e.target.value
}
// 用`store`的`dispatch`方法将`action`传给`store`
store.dispatch(action)
}

此时数据已经传递给了store,可是这个“管理员”不知道怎么处理数据,就去查阅“小手册”,所以**store会自动将 接收到的数据 和action一起传给“小手册”reducer。(reducer返回的函数中的state就是 上次传过来的数据,action就是组件想要修改state的那句话
接下来,
reducer会根据store传来的 (上一次的)数据 和action整合起来后,进行处理,给store返回一个新数据newState**。

reducer处理并返回newState

reducer处理过程reducer 可以接受state,但是决不能修改state。所以我们需要对store传进来的state进行深拷贝newState,然后再进行action的处理,最后返回newStatestore

1
2
3
4
5
6
7
8
9
10
11
12
13
// state为store中上一次数据,action由组件发出
// reducer 可以接受state,但是决不能修改state
export default (state = defaultState, action) => {
if (action.type === "change_input_value") {
// 不能修改state,故先深拷贝为newState
const newState = JSON.parse(JSON.stringify(state));
// action处理
newState.inputValue=action.value;
// 把newState返回给store
return newState;
}
return state;
}

效果
此时输入“aaa”已经可以在改变store中的state,但页面显示效果还没变。

组件与store数据同步(订阅store)

在组件TodoList.js中,使用store.subscribe()订阅store。只要store发生改变,subscribe()内作为参数的函数就会被执行一次:
注意:这里的store是顶部import store from "./store";已经引用的store

1
2
3
4
5
6
7
8
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
// 只要`store`发生改变,handleStoreChange函数就会被执行一次
store.subscribe(this.handleStoreChange);
}

当感知到store发生改变时,函数handleStoreChange()就会被执行,函数中将使用getState()获取store中的数据后,使用setatate()来改变 组件 中的state(组件 中的state改变,页面自然重新渲染):

1
2
3
4
handleStoreChange() {
// 使用`getState()`获取`store`中的数据 替换 组件中的`state`
this.setState(store.getState());
}

效果
此时页面效果就和store中的数据同步了。


,