使用redux完成TodoList列表功能
实现效果:继续上一篇笔记的例子,我们希望在点击 “提交”按钮 后,input框中的 数据value 存到store中的list 中,并清空input框。
1.首先**给button绑定一个onClick事件函数handleButtonClick**(记得绑定this作用域,JS表达式在JSX中使用{}):
1 | <Button type="primary" onClick={this.handleButtonClick}>提交</Button> |
2.在事件函数handleButtonClick中添加action,写好type属性。(注意:这里不需要value属性去传值,action传给store以后store会把之前的state数据和action一起传给reducer)
1 | handleButtonClick(){ |
(我们到时候在reducer中使用reducer的state中的inputValue的数据即可.)
3.store收到action后会自动把之前的state数据和action一起传给reducer
4.在reducer中,一样先深拷贝state,然后通过newState.list.push(newState.inputValue);将input框中的值加入到list数组中。(记得返回newState给store),并清空输入框:
1 | if(action.type === "add_todo_item"){ |
5.store接收到newState后会替换掉原本的state,而我们之前在组件上使用store.subscribe(this.handleStoreChange);**监听了store**,在该函数中我们使用this.setState(store.getState());获取了store中的state来改变 组件的state,组件的state发生改变后,页面数据自然重新渲染:
使用Redux完成TodoList删除功能
1.在reducer中清空input框的内容和list列表的数据:
1 | const defaultState = { |


那么我们希望点击“111”后他会消失
2.在组件TodoList中,我们的列表子项是通过<List.Item>进行渲染的,所以我们可以给<List.Item>绑定一个 事件函数handleItemDelete ,**增加一个参数index传入<List.Item>,并通过bind()的第二个参数将index传入函数handleItemDelete**:
1 | renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>} |
3.在事件函数handleItemDelete中书写action,将type和index一起传给store:
1 | handleItemDelete(index){ |
4.store接收到type和index后就自动带上原本的state一起打包发给reducer。
5.在reducer中通过数组的splice方法删除index子项:
1 | if(action.type === "delete_todo_item"){ |
实现效果:
actionTypes拆分type属性值
注意:
- 在
actionType.js中输出的是各个常量名,在别的文件也是使用对象的方式引用多个常量名再进行使用。(常量名的引用没有引号,区分于字符串) - 常量的输出不能使用
export default,只能使用export.仅允许表达式、函数或类作为“默认”导出:Parsing error: Only expressions, functions or classes are allowed as the
defaultexport.
原因
我们之前在action中都是使用字符串作为属性值(type: "add_todo_item"),在reducer.js中(if(action.type === "add_todo_item"){})也都是调用的字符串。
假设一旦字符串中有一个字母输错了,页面不会报错,但功能无法实现,所以我们需要把这个 字符串 保存在另一个js文件的常量(const)中,在需要使用他们的时候再进行文件和常量的引用。这样一来,一旦 常量书写错误,页面就会进行相应的报错。
拆分过程
我们应该把所有action中的 type属性值 拆分出来写在store文件夹下的actionType.js中,然后再在组件TodoList.js和reducer.js中分别引用actionType.js中对应 type属性值的各个 常量(const)。
例子
1.在store文件夹下新建actionType.js用于存放所有action中的 type属性值,记得把这些常量输出:
1 | // 记得输出才能在别的文件引用 |
2.在**TodoList.js中引入**:
1 | import { CHANGE_INPUT_VALUE, ADD_DOTO_ITEM, DELETE_TODO_ITEM } from "./store/actionTypes" |
3.将TodoList.js中的所有action中的**type属性值从字符串修改为常量名** (注意:常量名的引用不需要引号):type: CHANGE_INPUT_VALUE,、type: ADD_DOTO_ITEM,、type: DELETE_TODO_ITEM,
4.在**reducer.js中引入**(注意路径的改变):
1 | import { CHANGE_INPUT_VALUE, ADD_DOTO_ITEM, DELETE_TODO_ITEM } from "./actionTypes" |
5.将reducer.js中的所有action中的**type属性值从字符串修改为常量名** (注意:常量名的引用不需要引号):if (action.type === CHANGE_INPUT_VALUE) {}、if(action.type === ADD_DOTO_ITEM){}、if(action.type === DELETE_TODO_ITEM){}
测试
将reducer.js中的DELETE_TODO_ITEM写错:
1 | if(action.type === DELET_TODO_ITEM){} |

会告诉你“DELET_TODO_ITEM没有被定义”也就是说DELET_TODO_ITEM没被声明,它不是一个变量,那我们就很容易发现错误了。
actionCreator统一创建action
好处
- 方便管理,提高代码的可维护性。
- 前端有自动化测试工具,把
action统一写在actionCreator.js文件中将方便测试。
拆分过程
在store文件夹下创建actionCreator.js文件并引入action的type属性值,将action的创建都都写在actionCreator.js的不同常量定义的方法中,在组件中直接引用方法即可。(需要由组件传递到action中的方法通过函数进行传递)
例子
修改第一个action
1.在store文件夹下创建actionCreator.js文件,引入type常量并写入一个新函数(记得输出):
1 | import { CHANGE_INPUT_VALUE } from "./actionTypes"; |
2.在**TodoList.js中引入actionCreator.js**中的getInputChangeAction方法:
1 | import { getInputChangeAction } from "./store/actionCreator"; |
3.修改TodoList.js中action,调用getInputChangeAction方法时将e.target.value传过去,getInputChangeAction方法收到后就将它作为value返回:
修改剩下的action
1.将剩余会使用的type值都引入到actionCreator.js中,创建剩余的action:
1 | import { CHANGE_INPUT_VALUE,ADD_DOTO_ITEM, DELETE_TODO_ITEM } from "./actionTypes"; |
1 | export const getButtonClickAction = () => ({ |
2.在TodoList.js中 引入 并 使用 剩余两个获取actin的方法:
1 | import { getInputChangeAction,getButtonClickAction,getItemDeleteAction } from "./store/actionCreator"; |
const action =getButtonClickAction();和const action = getItemDeleteAction(index);
3.删除TodoList.js中无用的引用:import { CHANGE_INPUT_VALUE, ADD_DOTO_ITEM, DELETE_TODO_ITEM } from "./store/actionTypes";
重新归纳redux的工作流程
使用actionCreator以后,工作流程变更为:
在组件中调用actionCreator创建action方法,然后store使用dispatch()将action传给store
获取store中数据
- 创建
store:在store文件夹下的index.js中,通过引用自react的createStore()创建store。(记得输出store) - 创建
reducers:在store文件夹下的reducers.js中,需要输出一个参数为state和action的函数,reducer中的state就是store的state。所以我们可以通过设置state的默认值defaultState并在函数中返回,将默认state的值返回给store - **
store连接reducers**:将reducers.js中的reducers作为createStore()的第一个参数传给store。store从reducers获取到初始state值,然后store将其替换掉自身的state. - 组件
TodoList连接store获取数据:在组件的构造函数中通过store.subscribe();监听一个函数,在被监听的函数中把store的state赋值给 组件的state,这样第一次渲染页面时,组件的state的就会获取到store中的state值。
改变store中数据
- 通过
actionCreator创建 获取action对象的方法:通过actionCreator.js创建组件需要的action对象 存储在不同的 方法 中,这些方法会返回action对象 (记得输出) - 在
actionTypes.js中将type属性值转为常量:在actionTypes.js中将action对象的type属性值统一由字符串赋值给常量。 - 在组件中调用
actionCreator的方法获取action对象并传给store:在组件中引用actionCreator.js并调用actionCreator.js中的方法获取action,通过store.dispatch()将action传送给store store自动将收到的action和自己的state数据打包传送给reducerreducer处理action并返回newState值给store:reducer根据action中不同的type属性值做出不同的处理并返回新的newState值给storestore获取到newState后自动替换state- 组件中
state由于监听函数,更新后页面重新渲染:组件中之前通过store.subscribe();监听的函数会在store的state改变时将store的state赋值给 组件的state,组件自然重新渲染。
Redux设计和使用的三大基本原则
store是唯一的
只有store能改变自己的内容
reducer从store拿到之前的state数据,深拷贝为一个新的newState,修改newState后返回给store,**store拿到newState后自己把state替换掉,所以最后其实是store自己改变了自己的内容**。reducer中的state就是store的state,这也就是为什么我们需要深拷贝一个newState出来用而**不能直接修改reducer中的state**。
reducer必须是纯函数
- 纯函数指的是:给定固定的输入,就一定会有固定的输出,且不能对传入的参数进行修改。
- 纯函数不能涉及 异步 或者是关于 时间 的操作,一旦函数中涉及 AJAX请求、
new Date()、setTimeout(),他就 不是纯函数。
例子
纯函数:
也就是说state和action确定时,返回的newState就是确定的。
非纯函数:
state和action确定时,返回的newState是不固定的,他会收到当前时间的影响。
Redux中的核心api
redux的createStore()可以帮助我们创建storestore.dispatch()帮助我们将action传给storestore.getState()帮助我们获取store中的所有数据store.subscribe()帮助我们订阅store的改变,只要store发生改变,store.subscribe()接收的回调函数就会执行