五、Redux入门
5-1 Redux概念简述
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 (如果你需要一个 WordPress 框架,请查看 Redux Framework。)
可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。
Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)。
5-2 Redux的工作流程
把它们理解成:借书的用户(React Components)、要借什么书(Action)、图书管理员(Store)、记录本(Reducers);以此来更清楚地理解工作流程。
5-3 使用antd实现TodoList页面布局
步骤:
- 安装antd
yarn add antd
- 关键代码
//导入包
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
//第一个div代码段
<label htmlFor="insertArea">输入内容</label>
<Input
placeholder="todo info"
style={{width: '300px',marginRight: '10px'}}
value={this.state.inputValue}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>提交</Button>
- 最终效果:
输入框、按钮、表均为antdesign样式。
5-4 创建Redux中的store
步骤:
- 安装 store
(in Terminal)yarn add redux
- 在src根目录下创建store文件夹,在其中新建index.js,reducer.js,内容如下
//index.js
import { createStore } from "redux";
import reducer from './reducer';
const store = createStore(reducer);
export default store;
//reducer.js
const defaultState = {
inputValue: '123', //测试数据
list: [1,2,3] //测试数据
}
export default (state = defaultState, action) => { //两个“必须”的值
return state;
}
- 在TodoList.js内引用store,并且连接数据
//TodoList.js
...
import store from './store'; //在TodoList.js内引用store
class TodoList extends Component{
constructor(props) {
...
this.state = store.getState(); //导入reducer中的数据到state
console.log(this.state); //仅作监视用
}
return (
<Fragment>
...
<List
...
dataSource={this.state.list} //导入state中的数据到表
...
/>
</Fragment>
)
...
}
- 最终效果:
①reducer中的list里的数据可以被打印在表中。
②reducer中inputvalue的值被打印在输入框里。
5-5 Action和Reducer的编写
- 安装插件Redux Devtools,插件界面
实现:
- 代码
//TodoList.js
import React, { Component,Fragment } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
import store from './store';
class TodoList extends Component{
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
store.subscribe(this.handleStoreChange); //监听store,当其发生变化时,处理函数
}
render() {
return (
<Fragment>
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<label htmlFor="insertArea">输入内容</label>
<Input
placeholder="todo info"
style={{width: '300px',marginRight: '10px'}}
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>提交</Button>
</div>
<List
style={{width: '440px', marginTop: '10px', marginLeft: '10px'}}
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</Fragment>
)
}
handleInputChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleBtnClick(){
const action = {
type: 'change_todo_item'
}
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState());//同步最新数据
}
}
export default TodoList;
//store/index.js
import { createStore } from "redux";
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
//store/reducer.js
const defaultState = {
inputValue: '',
list: []
}
//reducer可以接受state,但是绝不能修改state
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state)); //深拷贝state到newstate
newState.inputValue = action.value;
return newState; //返回给Store
}
if (action.type === 'change_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
console.log(newState);
return newState;
}
return state;
}
- 测试结果:
目前为止,只实现了TodoList的添加功能,删除功能及代码优化会在后面几节中实现。
5.6 使用Redux完成TodoList
//TodoList.js
//在原来<List/>中修改
renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>}//给每个List.item绑定事件
//写这个事件的方法
handleItemDelete(index) {
const action = {
type: 'delete_todo_item',
index
}
store.dispatch(action);
}
//reducer.js
//新增一个删除功能
...
if (action.type === 'delete_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
5.7 ActionTypes的拆分
常量和变量的错误能直接被提示出来,而字符串的错误是不会被提示出来的。如果庞大的代码因为字符串的错误而不能实现某功能,这时候错误是很难被发现的。为了便于找到代码的错误,这里建议把字符串action.type转换成常量的形式存在文件中。
步骤:
- 在store文件夹下新建actionTypes.js,在其中进行“字符串action.type转换成常量”的操作。
//把字符串转换成常量的形式
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const CHANGE_TODO_ITEM ='change_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
- 在TodoList.js和reducer.js中引用这些常量,并把原来字符串的地方替换成对应的常量。
- 保存,运行网页,能正常运行。
5.8 使用actionCreator统一创建action
为了进一步提高代码的可维护性,和方便自动化测试,我们使用actionCreator统一创建action
步骤:
- 在store文件夹下新建actionCreators.js。
- 编写actionCreators.js。
//引用actionTypes里的常量,封装成函数的形式,导出给TodoList.js
import { CHANGE_INPUT_VALUE, CHANGE_TODO_ITEM, DELETE_TODO_ITEM } from "./actionTypes";
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = () => ({
type: CHANGE_TODO_ITEM,
})
export const getDeleteItemAction = (value) => ({
type: DELETE_TODO_ITEM,
value
})
- 在TodoList.js中引用这些函数,并把原来const action等号之后调用这些函数。
下面是例子:
handleInputChange(e) {
const action = getInputChangeAction(e.target.value);
store.dispatch(action);
}
handleBtnClick(){
const action = getAddItemAction();
store.dispatch(action);
}
handleStoreChange() {
this.setState(store.getState());//同步最新数据
}
handleItemDelete(index) {
const action = getDeleteItemAction(index);
store.dispatch(action);
}
- 保存,运行网页,能正常运行。
5.9 Redux总结
- store必须是唯一的,它是公共存储空间。
- 只有store能改变自己的内容。
- reducer必须是纯函数。纯函数是指,给定固定的输入(在任何时间下),就一定有固定的输出,而且不会有任何副作用。不是纯函数的例子:输出和日期相关、ajax请求等等。副作用是指,直接对参数state的修改,而不是对newstate的修改。
- Redux的核心api:
名称 | 作用 |
---|---|
creatStore | 帮助创建store |
store | 帮助派发action传递给store |
store.getState | 帮助获取store中的内容 |
store.subscribe | 订阅store的改变,只要store发生改变,执行回调函数 |