使用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
default
export.
原因
我们之前在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
数据打包传送给reducer
reducer
处理action
并返回newState
值给store
:reducer
根据action
中不同的type
属性值做出不同的处理并返回新的newState
值给store
store
获取到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()
可以帮助我们创建store
store.dispatch()
帮助我们将action
传给store
store.getState()
帮助我们获取store
中的所有数据store.subscribe()
帮助我们订阅store
的改变,只要store
发生改变,store.subscribe()
接收的回调函数就会执行