Redux入门(2)

使用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
2
3
4
5
6
7
handleButtonClick(){
const action={
type:"add_todo_item",
}
store.dispatch(action)
// 传给store以后store会把之前的state数据和action一起传给reducer
}

(我们到时候在reducer中使用reducerstate中的inputValue的数据即可.)

3.store收到action后会自动把之前的state数据action一起传给reducer
4.reducer,一样先深拷贝state,然后通过newState.list.push(newState.inputValue);input框中的值加入到list数组中。(记得返回newStatestore),并清空输入框:

1
2
3
4
5
6
7
8
if(action.type === "add_todo_item"){
const newState=JSON.parse(JSON.stringify(state));
// newState.list= [...newState.list,newState.inputValue];效果一样
newState.list.push(newState.inputValue);
// 清空输入框
newState.inputValue="";
return newState;
}

5.store接收到newState后会替换掉原本的state,而我们之前在组件上使用store.subscribe(this.handleStoreChange);**监听了store**,在该函数中我们使用this.setState(store.getState());获取了store中的state来改变 组件的state,组件的state发生改变后,页面数据自然重新渲染:
新增第三条内容


使用Redux完成TodoList删除功能

1.在reducer中清空input框的内容和list列表的数据:

1
2
3
4
const defaultState = {
inputValue: "",
list: []
};

效果
输入111后
那么我们希望点击“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,将typeindex一起传给store:

1
2
3
4
5
6
7
8
handleItemDelete(index){
const action={
type:"delete_todo_item",
// index相当于index:index
index
}
store.dispatch(action)
}

4.store接收到typeindex后就自动带上原本的state一起打包发给reducer
5.在reducer中通过数组的splice方法删除index子项:

1
2
3
4
5
6
7
if(action.type === "delete_todo_item"){
const newState=JSON.parse(JSON.stringify(state));
// 删除list数组中index项
newState.list.splice(action.index,1);
return newState;
}
return state;

实现效果:
实现效果


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.jsif(action.type === "add_todo_item"){})也都是调用的字符串
假设一旦字符串中有一个字母输错了,页面不会报错,但功能无法实现,所以我们需要把这个 字符串 保存在另一个js文件的常量(const)中,在需要使用他们的时候再进行文件和常量的引用。这样一来,一旦 常量书写错误,页面就会进行相应的报错

拆分过程

我们应该把所有action中的 type属性值 拆分出来写在store文件夹下的actionType.js,然后再在组件TodoList.jsreducer.js分别引用actionType.js中对应 type属性值的各个 常量(const

例子

1.在store文件夹下新建actionType.js用于存放所有action中的 type属性值,记得把这些常量输出

1
2
3
4
// 记得输出才能在别的文件引用
export const CHANGE_INPUT_VALUE = "change_input_value";
export const ADD_DOTO_ITEM = "add_todo_item";
export const DELETE_TODO_ITEM = "delete_todo_item";

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文件并引入actiontype属性值,将action的创建都都写在actionCreator.js的不同常量定义的方法中,在组件中直接引用方法即可。(需要由组件传递到action中的方法通过函数进行传递)

例子

修改第一个action

1.在store文件夹下创建actionCreator.js文件,引入type常量并写入一个新函数(记得输出):

1
2
3
4
5
6
7
8
9
10
import { CHANGE_INPUT_VALUE } from "./actionTypes";

// 使用const创建一个箭头函数
// return后只返回一个对象可将`return{}`省略为`()`
export const getInputChangeAction = (value) => ({
// value接收了TodoList中传过来的e.target.value
type: CHANGE_INPUT_VALUE,
// value相当于value:value
value
})

2.在**TodoList.js中引入actionCreator.js**中的getInputChangeAction方法:

1
import { getInputChangeAction } from "./store/actionCreator";

3.修改TodoList.jsaction,调用getInputChangeAction方法时将e.target.value传过去,getInputChangeAction方法收到后就将它作为value返回:
修改`TodoList.js`中`action`

修改剩下的action

1.将剩余会使用的type值都引入到actionCreator.js中,创建剩余的action:

1
import { CHANGE_INPUT_VALUE,ADD_DOTO_ITEM, DELETE_TODO_ITEM } from "./actionTypes";
1
2
3
4
5
6
7
8
export const getButtonClickAction = () => ({
type: ADD_DOTO_ITEM,
})

export const getItemDeleteAction = (index) => ({
type: DELETE_TODO_ITEM,
index
})

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中数据

  • 创建storestore文件夹下的index.js中,通过引用自reactcreateStore()创建store。(记得输出store
  • 创建reducersstore文件夹下的reducers.js中,需要输出一个参数为stateaction的函数,reducer中的state就是storestate。所以我们可以通过设置state的默认值defaultState并在函数中返回,将默认state的值返回给store
  • **store连接reducers**:将reducers.js中的reducers作为createStore()的第一个参数传给 storestorereducers获取到初始state值,然后store将其替换掉自身的state.
  • 组件TodoList连接store获取数据:在组件的构造函数中通过store.subscribe();监听一个函数,在被监听的函数中把storestate赋值给 组件的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值给storereducer根据action中不同的type属性值做出不同的处理并返回新的newState值给store
  • store获取到newState后自动替换state
  • 组件中state由于监听函数,更新后页面重新渲染:组件中之前通过store.subscribe();监听的函数会在storestate改变时将storestate赋值给 组件的state,组件自然重新渲染。

Redux设计和使用的三大基本原则

store是唯一的

只有store能改变自己的内容

  • reducerstore拿到之前的state数据,深拷贝为一个新的newState,修改newState后返回给store,**store拿到newState后自己把state替换掉,所以最后其实是store自己改变了自己的内容**。
  • reducer中的state就是storestate,这也就是为什么我们需要深拷贝一个newState出来用而**不能直接修改reducer中的state**。

reducer必须是纯函数

  • 纯函数指的是:给定固定的输入,就一定会有固定的输出,且不能对传入的参数进行修改
  • 纯函数不能涉及 异步 或者是关于 时间 的操作,一旦函数中涉及 AJAX请求、new Date()setTimeout() ,他就 不是纯函数

例子

纯函数:
纯函数
也就是说stateaction确定时,返回的newState就是确定的。

非纯函数:
非纯函数
stateaction确定时,返回的newState是不固定的,他会收到当前时间的影响。

Redux中的核心api

  • reduxcreateStore()可以帮助我们创建store
  • store.dispatch()帮助我们将action传给store
  • store.getState()帮助我们获取store中的所有数据
  • store.subscribe()帮助我们订阅store的改变,只要store发生改变,store.subscribe()接收的回调函数就会执行

,