一、Redux概念简述
Facebook:react在2013年开源的时候,还开源了flux(已经过时),flux是官方推出最原始的辅助react的数据层框架,但实际使用时有缺点:公共数据存储区域store由很多的store所组成,这样会导致数据存储的时候会有数据依赖的问题。便在flux的基础上引入了reducer的概念-->redux。
redux = reducer+flux redux基础设计理念: 数据放在Store,公用存储空间。组件改变Store 的数据,其他组件就能感知到Store里数据的变化,取到新的数据,从而间接的实现了组件之间实现数据传递的功能
三大设计原则:
- 单一数据源(store中存储数据)
- store是只读的(想要修改store只能使用action, 其他方法不可以修改)
- 使用纯函数修改(即reducer)
二、Redux 的工作流程
用一个实际的例子来解释redux的工作流程
coponent是借阅人 store是用来存储数据的. 相当于图书管理员 action就是借阅人对图书管理员说的话 reducers就是借阅记录
当借阅人(component)要借书时, 对管理员(store)说"我要借xx书"(action), 管理员就在借阅记录(reducers)上找出来。然后,管理员将书(state)给借阅人(component)。
三、使用 Antd 实现 TodoList 页面布局
使用 npm 或 yarn 安装
npm install antd --save
yarn add antd
具体代码
import React, { Component } from 'React';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';
const data = [
'AAAAA',
'BBBBB',
'CCCCC',
'DDDDD',
'EEEEE',
];
class TodlList extends Component {
render() {
return() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input placeholder='todo info' style={{width: '300px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={data}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
}
四、创建 redux 中的 store
- npm安装Redux
npm install --save redux
- 在src目录下新建目录store,新并建文件reducer.js。内容如下:
// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
inputValue:'123',
list: [1, 2]
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
export default (state = defaultState, action) => {
return state;
}
- 再在目录src/store下,新建文件index.js,创建stroe。内容如下:
// 引入createStore方法
import { createStore } from 'redux';
// 引入reducer
import reducer from './reducer';
// 创建store,并把reducer中导出的方法传进来
const store = createStore(reducer);
export default store;
- 在组件中引用store并使用
import React, { Component } from 'React';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';
// 引用store
import store from './store';
class TodlList extends Component {
constructor(props) {
super(props);
// 获取store,并赋值给state
this.state = store.getState();
}
render() {
return() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<div>
<Input value={this.state.inputValue} placeholder='todo info' style={{width: '300px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
}
五、Action 和 Reducer 的编写
- 为了方便调试redux,可以先在chrome浏览器中安装插件 Redux DevTools
- 为了能正常使用,还要在给createStore方法添加一个参数。(按插件提示添加即可)
redux的工作流程说明
- 要想更新state中的数据,首先派发(dispatch)一个action,action通过dispatch方法把类型(type)和新数据传给store。
- store把之前的数据(previousState)和传过来的action转发给reducers函数。
- reducers接收previousState和action后进行数据处理,重新生成一个newState(原state只读不改),把newState作为返回值返回给store。
- store接收newState,将新数据替换原来的数据。
- react组件中观测(store.subscribe)到数据发生改变,会从store里面重新取数据(state)设置(setState)state,更新组件的内容,页面发生变化。
六、使用redux编写todoList代码
src/store/reducer.js中的内容
// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
inputValue:'123',
list: [1, 2]
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
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_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState;
}
if (action.type === 'delete_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
return state;
}
src/store/index.js中的内容
// 引入createStore方法
import { createStore } from 'redux';
// 引入reducer
import reducer from './reducer';
// 创建store,并把reducer中导出的方法传进来
const store = createStore(reducer);
export default store;
在组件中的内容
import React, {Component} from 'react';
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import { Input, Button, List } from 'antd';
// 引用store
import store from './store';
class TodoList extends Component {
constructor(props) {
super(props);
// 获取store,并赋值给state
this.state = store.getState();
// 统一在constructor中绑定this,提交性能
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleClick = this.handleClick.bind(this);
// 在组件中订阅store,只要store改变就触发这个函数
this.unsubscribe = store.subscribe(this.handleStoreChange);
}
// 当store状态改变时,更新state
handleStoreChange() {
// 用从store中获取的state,来设置state
this.setState(store.getState());
}
render() {
return (
<div style={{margin: '10px'}}>
<div className="input">
<Input
style={{width: '300px', marginRight: '10px'}}
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<Button type="primary" onClick={this.handleClick}>提交</Button>
</div>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={(item, index) => (<List.Item onClick={this.handleDelete.bind(this, index)}>{item}</List.Item>)}
/>
</div>
)
}
// 组件注销前把store的订阅取消
componentWillUnmount() {
this.unsubscribe();
}
// 输入内容时(input框内容改变时)
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
// 当input框中数据改变时,交给store去处理
store.dispatch(action);
}
// 添加一项
handleClick () {
const action = {
type: 'add_todo_item',
}
store.dispatch(action);
}
// 点击删除当前项
handleDelete (index) {
const action = {
type: 'delete_todo_item',
index
}
store.dispatch(action);
}
}
export default TodoList;
七、ActionTypes 的拆分
使用常量代替action中的type字符串 因为如果action的type中字符串,如果拼错了,不会报错,很难找到问题。但是使用常量如果写错了控制台会报错,方便排错。
- 在store目录中新文件actionTypes.js,定义一些常量。内容如下
const types = {
CHANGE_INPUT_VALUE: 'change_input_value',
ADD_TODO_ITEM: 'add_todo_item',
DELETE_TODO_ITEM: 'delete_todo_item'
}
export default types;
- 在组件中引入这些常量
import types from './store/actionTypes';
...
...
// 输入内容时(input框内容改变时)
handleInputChange(e) {
const action = {
type: types.CHANGE_INPUT_VALUE,
value: e.target.value
}
// 当input框中数据改变时,交给store去处理
store.dispatch(action);
}
// 添加一项
handleClick () {
const action = {
type: types.ADD_TODO_ITEM,
}
store.dispatch(action);
}
// 点击删除当前项
handleDelete (index) {
const action = {
type: types.DELETE_TODO_ITEM,
value: index
}
store.dispatch(action);
}
- src/store/reducer.js中的内容改成
import types from './actionTypes';
// 设置一个默认值,如果state没有值则使用这个默认值
const defaultState = {
inputValue: '',
list: ['学习英语', '学习React']
}
// 导出一个函数(第一个参数是之前的state,第二个值数是一个action对象,包括类型和传值)
export default (state = defaultState, action) => {
const { type, value } = action;
const newState = JSON.parse(JSON.stringify(state));
switch(type) {
case types.CHANGE_INPUT_VALUE:
newState.inputValue = value;
break;
case types.ADD_TODO_ITEM:
newState.list.push(newState.inputValue);
newState.inputValue = '';
break;
case types.DELETE_TODO_ITEM:
newState.list.splice(value, 1);
break;
default:
return state
}
return newState;
}
八、使用 actionCreator 统一创建 action
在store文件夹下创建一个actionCreators.js,把action都集中写在这个文件中统一管理。这样子看上去多了一个步骤,但是会方便后期维护和自动化测试。
具体代码如下
- 在store目录中新文件actionCreators.js,把action都集中写在这个文件中。内容如下
import types from './actionTypes';
// 返回的是action对象
export const inputChangeAction = (value) => ({
type: types.CHANGE_INPUT_VALUE,
value: value
})
export const addItemAction = (value) => ({
type: types.ADD_TODO_ITEM
})
export const deleteItemAction = (index) => ({
type: types.DELETE_TODO_ITEM,
value: index
})
- 在组件中引入这些action
import { inputChangeAction, addItemAction, deleteItemAction } from './store/actionCreators';
...
...
// 输入内容时(input框内容改变时)
handleInputChange(e) {
const action = inputChangeAction(e.target.value);
store.dispatch(action);
}
// 添加一项
handleClick () {
const action = addItemAction();
store.dispatch(action);
}
// 点击删除当前项
handleDelete (index) {
const action = deleteItemAction(index);
store.dispatch(action);
}
九、Redux 知识点总结及补充
Redux三个基本原则:
- store必须是唯一;
- 只有store能够改变自己的内容(reducer只是返回newState让store更新state,而不是改变state);
- reducer必须是纯函数。
纯函数:纯函数指的是,给定固定的输入,就一定会返回固定的输出(所以不能有ajax请求、new Date()和定时器)。而且不会有任何副作用(如不能修改store的内容,要先复制一份再去操作)。
Redux的API
createStore:创建store仓库 store.dispatch:派发action,并传递给store store.getState:获取store中所有的内容 store.subscribe:订阅store的改变