🌂序言
大家都知道, react
是单向数据流,所以它传递数据也较为简单,父子之间的关系也较为明确。但是呢,如果我们要做更多复杂数据的传递,单单使用 react
是完全不够的。因此,我们需要用到 redux
来做更为复杂的数据传递。
那在下面的这篇文章中,将从入门到进阶,讲解 redux
的工作流程。
叮!开始 redux
之旅吧~👏
☂️一、基础知识
1、Redux概念简述
对于 react
来说,它是一个非视图层的轻量级框架,如果要用它来传递数据的话,则要先父传子,然后再慢慢地一层一层往上传递。
但如果用 redux
的话,假设我们想要某个组件的数据,那这个组件的数据则会通过 redux
来存放到 store
中进行管理。之后呢,通过 store
,再来将数据一步步地往下面的组件进行传递。
值得注意的是,我们可以视 Redux
为 Reducer
和 Flux
的结合。
2、Redux的工作流程
Redux
,实际上就是一个数据层的框架,它把所有的数据都放在了 store
之中。我们先来看一张图:
大家可以看到中间的 store
,它里面就存放着所有的数据。继续看 store
向下的箭头,然后呢,每个组件都要向 store
里面去拿数据。
我们用一个例子来梳理整张图,具体如下:
- ①整张图上有一个
store
,它存放着所有的数据,也就是存储数据的公共区域; - ②每个组件,都要从
store
里面拿数据; - ③假设现在有一个场景,模拟我们要在图书馆里面借书。那么我们可以把
react Component
理解为借书人,之后呢,借书人要去找图书馆管理员才能借到这本书。而借书这个过程中数据的传递,就可以把它视为是Action Creators
,可以理解为 “你想要借什么书” 这句话。 - ④
Action Creatures
去到store
。这个时候我们把store
当做是图书馆管理员,但是,图书馆管理员是没有办法记住所有图书的数据情况的。一般来说,它都需要一个记录本,你想要借什么样的书,那么她就先查一下;又或者你想要还什么书,她也要查一下,需要放回什么位置上。 - ⑤这个时候就需要跟
reducers
去通信,我们可以把reducers
视为是一个记录本,图书馆管理员用这个记录本来记录需要的数据。管理员store
通过reducer
知道了应该给借书人Components
什么样的数据。
🎃二、使用Antd实现TodoList页面布局
1、在项目中使用Antd
打开 antdesign
的官网👉antd官网传送门,我们先来在项目中引入它。具体步骤如下:
第一步,安装 antd
。命令如下:
npm install antd --save 复制代码
第二步,引入样式。代码如下:
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less' 复制代码
2、使用Antd实现TodoList的基本布局
首先,我们在项目的 src
文件夹下创建一个新的文件,命名为 TodoList.js
。具体代码如下:
import React, { Component } from 'react'; import 'antd/dist/antd.css'; import { Input, Button, List } from 'antd'; const data = [ 'Racing car sprays burning fuel into crowd.', 'Japanese princess to wed commoner.', 'Australian walks 100km after outback crash.', 'Man charged over missing wedding girl.', 'Los Angeles battles huge wildfires.', ]; class TodoList extends Component { render() { return ( <div style={{marginTop: '10px', marginLeft: '10px'}}> <div> <Input placeholder="todo info" style={{ width: '300px' }} /> <Button type="primary">提交</Button> </div> <List bordered dataSource={data} renderItem={item => <List.Item>{item}</List.Item>} /> </div> ) } } export default TodoList; 复制代码
此时浏览器的显示效果为:
3、创建redux中的store
(1)创建store
接下来我们来创建项目中的 store
。首先在项目的 src
文件夹下创建一个新的文件夹,命名为 store
。接着,我们在 store
文件夹下面,创建一个新的文件,命名为 index.js
。具体代码如下:
import { createStore } from "redux"; import reducer from './reducer'; const store = createStore(reducer); export default store; 复制代码
然后呢,继续在 store
文件夹下面创建一个新的文件,命名为 reducer.js
。具体代码如下:
const defaultStore = { inputValue: '', list: [] }; export default (state = defaultStore, action) => { return state; } 复制代码
由此,我们就创建完成了项目中的 store
。
(2)在项目中使用store
创建完 store
之后,我们先初步在项目中使用这个 store
,以便于看看效果。先来添加 store
中的数据,首先改造在 store
中的 reducer.js
文件,具体代码如下:
const defaultStore = { inputValue: '123', list: [1, 2] }; export default (state = defaultStore, action) => { return state; } 复制代码
之后改造 TodoList.js
。具体代码如下:
import React, { Component } 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() } render() { return ( <div style={{marginTop: '10px', marginLeft: '10px'}}> <div> <Input placeholder={this.state.inputValue} style={{ width: '300px' }} /> <Button type="primary">提交</Button> </div> <List bordered dataSource={this.state.list} renderItem={item => <List.Item>{item}</List.Item>} /> </div> ) } } export default TodoList; 复制代码
此时浏览器的显示效果为:
🧵三、Action和Reducer的编写 - 增添功能
1、主体页面内容改造
接下来,我们使用 action
和 reducer
,来对这个组件的数据进行前后传递。首先,先来改造 TodoList.js
文件。具体代码如下:
import React, { Component } 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) } render() { return ( <div style={{marginTop: '10px', marginLeft: '10px'}}> <div> <Input value={this.state.inputValue} placeholder="todo info" style={{ width: '300px', marginRight: '10px'}} onChange={this.handleInputChange} /> <Button type="primary" onClick={this.handleBtnClick}>提交</Button> </div> <List style={{marginTop: '10px', width: '300px'}} bordered dataSource={this.state.list} renderItem={item => <List.Item>{item}</List.Item>} /> </div> ) } handleInputChange(e) { // 在react中,action是一个对象的形式 // type旨在告诉react说,你帮我去改变input的值,这个值是下面的value,也就是e.target.value const action = { type: 'change_input_value', value: e.target.value } store.dispatch(action) // console.log(e.target.value) } handleStoreChange() { // 当感知到store的数据发生变化时,那么就去调用store.getState方法,从store里面再重新取一次数据, // 然后去调用setState,替换掉当前store里面的数据 this.setState(store.getState()) } handleBtnClick() { const action = { type: 'add_todo_item' } store.dispatch(action) } } export default TodoList; 复制代码
接下来我们来分析以上代码。首先,每一个动作分别会先去绑定对应的事件,之后呢,在事件里面,去创造 action
。而对于创造的 action
来说,它旨在告诉 react
,让 react
去帮忙 action
去改变某个值,而这个值就是它绑定的 value
。
最后, action
要做的事情结束了,那么它的数据就需要去存储到 store
里面。于是通过 store.dispatch(action)
来进行处理,将 action
的数据传递到 store
里面。
2、改变action中的数据
对于 action
一开始的值来说,它是固定的。但有时候我们是想要去修改action中的值,这个时候就需要用到 reducer
。现在,我们来改造下 reducer.js
文件,让 input
框可以自由的输入值,同时,点击提交按钮之后,进行列表的增添操作。具体代码如下:
const defaultStore = { inputValue: '123', list: [1, 2] }; // reducer 可以接收state,但是绝不能修改state const reducer = (state = defaultStore, 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 = ''; console.log(newState); return newState; } return state; } export default reducer; 复制代码
3、store数据改造
下面,我们来看下 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; 复制代码
除了 reducer
之外,我们还要将 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
给传递进去并调用这个方法。
最后,我们来看下浏览器的显示效果:
🧶四、使用Redux实现TodoList的删除功能
1、对组件进行事件绑定
上面我们实现了增添功能,那么现在,我们继续来实现删除功能,实现每点击每一项时,能够删除点击项的数据。先来在 TodoList.js
文件中绑定对应的事件,具体代码如下:
import React, { Component } from 'react'; import 'antd/dist/antd.css'; import { Input, Button, List } from 'antd'; import store from './store'; class TodoList extends Component { constructor(props) { // 此处省略上述已有代码 } render() { return ( {/* 此处省略上述已有代码 */} <List style={{marginTop: '10px', width: '300px'}} bordered dataSource={this.state.list} renderItem={(item, index) => <List.Item onClick={this.handleItemDelete.bind(this, index)}>{item}</List.Item>} /> </div> ) } // 此处省略上述已有代码 handleItemDelete(index) { const action = { type: 'delete_todo_item', index } store.dispatch(action); } } export default TodoList; 复制代码
2、在reducer中进行数据通信
接着,我们在 reducer.js
文件中,对数据进行通信。具体代码如下:
const defaultStore = { inputValue: '123', list: [1, 2] }; // reducer 可以接收state,但是绝不能修改state const reducer = (state = defaultStore, action) => { // 此处省略上述已有代码 if (action.type === 'delete_todo_item') { const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index, 1); return newState; } return state; } export default reducer; 复制代码
现在,我们来看下浏览器的显示效果:
👓五、逻辑归纳
1、ActionTypes的拆分
在上面的 TodoList.js
中,大家可以看到,我们会频繁地去操作 action
。同时,假设说其中的 type
如果我们稍微写错了一个字母,那排错的过程总是不好定位的。
因此,我们要来做的一件事情就是 ActionTypes
的拆分。
首先,我们在 store
文件夹下新增一个文件,命名为 actionTypes.js
。具体代码如下:
export const CHANGE_INPUT_VALUE = 'change_input_value'; export const ADD_TODO_ITEM = 'add_todo_item'; export const DELETE_TODO_ITEM = 'delete_todo_item'; 复制代码
其次,改造 TodoList.js
下的内容。具体代码如下:
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './store/actionTypes' class TodoList extends Component { handleInputChange(e) { const action = { type: CHANGE_INPUT_VALUE, value: e.target.value } store.dispatch(action) } handleStoreChange() { this.setState(store.getState()) } handleBtnClick() { const action = { type: ADD_TODO_ITEM } store.dispatch(action) } handleItemDelete(index) { const action = { type: DELETE_TODO_ITEM, index } store.dispatch(action); } } export default TodoList; 复制代码
最后,改造 reducer.js
文件。具体代码如下:
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'; const defaultStore = { inputValue: '123', list: [1, 2] }; const reducer = (state = defaultStore, 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 = ''; console.log(newState); 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; } export default reducer; 复制代码
通过将 change_input_value
、 add_todo_item
和 delete_todo_item
进行整合,将其整合到 actionTypes.js
文件下,这样,如果我们遇到字母写错的情况下,也能够更好的进行排错。
2、使用actionCreator统一创建action
在上面的 TodoList.js
中,大家可以看到,对于几个绑定的事件来说,我们总是要频繁的去创建 action
,重复性地操作是在程序中最忌讳的一个事情。因此呢,我们要使用 actionCreator
,来对 action
进行统一管理,使得逻辑更加地统一完整。
首先,我们在 store
文件夹下新创建一个文件,命名为 actionCreators.js
。具体代码如下:
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM } from "./actionTypes"; export const getInputChangeAction = (value) => ({ type: CHANGE_INPUT_VALUE, value: value }); export const getAddItemAction = (value) => ({ type: ADD_TODO_ITEM }); export const getDeleteItemAction = (index) => ({ type: DELETE_TODO_ITEM, index: index }); 复制代码
继续,我们来改造 TodoList.js
。具体代码如下:
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators' class TodoList extends Component { handleInputChange(e) { const action = getInputChangeAction(e.target.value); store.dispatch(action) } handleBtnClick() { const action = getAddItemAction(); store.dispatch(action) } handleItemDelete(index) { const action = getDeleteItemAction(index); store.dispatch(action); } } export default TodoList; 复制代码
通过将 action
中的操作统一抽离到 actionCreators.js
当中,使得最终的逻辑更加的统一。
👔六、Redux的一些总结
讲到这里,我们对上面的一些知识点进行归纳总结,具体如下
1、Redux设计和使用的三项原则
Redux
的设计和使用遵循以下三大原则:
store
必须是唯一的👉即整个应用之中必须且只能有一个store
;- 只有
store
能够改变自己的内容👉即store
不是reducer
去更新的,而是store
在拿到reducer
的数据之后,自己对自己的数据进行一次更新;因此,我们回到上面的reducer.js
文件,在react
中,是不允许state.inputValue === 某个值
之类的事情发生的哦,也就是说不能对其直接进行赋值。 Reducer
必须是纯函数👉所谓纯函数,即给定固定的输入,就一定有固定的输出,而且不会产生任何的副作用。回到我们上面的reducer.js
文件,大家可以看到,state
是固定的,action
也是固定的,那么最终返回的newState
自然也就是固定的。
2、Redux的核心API
我们再来复习 Redux
的几个核心 API
。先看下图:
现在来回顾下这几个核心 API
的作用。具体如下:
- createStore —— 可以帮助我们创建一个
store
; - store.dispatch ——
dispatch
方法帮助我们派发action
,同时,这个action
会传递给store
; - store.getState ——
getState
方法帮助我们获取到所有的数据; - store.subscribe ——
subscribe
帮助我们订阅store
的改变,只要store
发生改变,store.subscribe
接收的回调函数就会被执行。