Redux从入门到进阶,看这一篇就够了!

Redux,带你从入门到进阶

  • 🌂序言
  • ☂️一、基础知识
    • 1、Redux概念简述
    • 2、Redux的工作流程
  • 🎃二、使用Antd实现TodoList页面布局
    • 1、在项目中使用Antd
    • 2、使用Antd实现TodoList的基本布局
    • 3、创建redux中的store
      • (1)创建store
      • (2)在项目中使用store
  • 🧵三、Action和Reducer的编写 - 增添功能
    • 1、主体页面内容改造
    • 2、改变action中的数据
    • 3、store数据改造
  • 🧶四、使用Redux实现TodoList的删除功能
    • 1、对组件进行事件绑定
    • 2、在reducer中进行数据通信
  • 👓五、逻辑归纳
    • 1、ActionTypes的拆分
    • 2、使用actionCreator统一创建action
  • 👔六、Redux的一些总结
    • 1、Redux设计和使用的三项原则
    • 2、Redux的核心API
  • 👝七、进阶组件的拆分
    • 1、UI组件和容器组件的拆分
    • 2、无状态组件
  • 🎩八、Redux发起异步请求
    • 1、Redux中发送异步请求数据
    • 2、Redux-thunk中间件
      • (1)解决什么问题
      • (2)如何使用
      • (3)为什么要使用 redux-thunk ?
      • (4)什么是 Redux-thunk 中间件?
  • 💼九、Redux的其他中间件
    • 1、Redux-logger
    • 2、Redux-saga
      • (1)Redux-saga是什么
      • (2)Redux-saga如何使用
  • 🛵十、React-Redux
    • 1、React-Redux是什么
    • 2、React-Redux的使用
      • (1)安装React-Redux
      • (2)项目目录
      • (3)核心内容
  • 🚦十一、结束语
  • 🐣彩蛋 One More Thing
    • (:往期推荐
    • (:番外篇

🌂序言

大家都知道, react 是单向数据流,所以它传递数据也较为简单,父子之间的关系也较为明确。但是呢,如果我们要做更多复杂数据的传递,单单使用 react 是完全不够的。因此,我们需要用到 redux 来做更为复杂的数据传递。

那在下面的这篇文章中,将从入门到进阶,讲解 redux 的工作流程。

叮!开始 redux 之旅吧~👏

☂️一、基础知识

1、Redux概念简述

对于 react 来说,它是一个非视图层的轻量级框架,如果要用它来传递数据的话,则要先父传子,然后再慢慢地一层一层往上传递。

但如果用 redux 的话,假设我们想要某个组件的数据,那这个组件的数据则会通过 redux 来存放到 store 中进行管理。之后呢,通过 store ,再来将数据一步步地往下面的组件进行传递。

值得注意的是,我们可以视 Redux 为 Reducer 和 Flux 的结合。

2、Redux的工作流程

Redux ,实际上就是一个数据层的框架,它把所有的数据都放在了 store 之中。我们先来看一张图:

Redux的工作流程

大家可以看到中间的 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><ListbordereddataSource={data}renderItem={item => <List.Item>{item}</List.Item>}/></div>)}
}export default TodoList;

此时浏览器的显示效果为:

antd布局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><ListbordereddataSource={this.state.list}renderItem={item => <List.Item>{item}</List.Item>}/></div>)}
}export default TodoList;

此时浏览器的显示效果为:

todoList接收来自store的数据

🧵三、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><Inputvalue={this.state.inputValue}placeholder="todo info"style={{ width: '300px', marginRight: '10px'}}onChange={this.handleInputChange}/><Button type="primary" onClick={this.handleBtnClick}>提交</Button></div><Liststyle={{marginTop: '10px', width: '300px'}}bordereddataSource={this.state.list}renderItem={item => <List.Item>{item}</List.Item>}/></div>)}handleInputChange(e) {// 在react中,action是一个对象的形式// type旨在告诉react说,你帮我去改变input的值,这个值是下面的value,也就是e.target.valueconst 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__() 给传递进去并调用这个方法。

最后,我们来看下浏览器的显示效果:

action和reducer传递数据

🧶四、使用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 ({/* 此处省略上述已有代码 */}<Liststyle={{marginTop: '10px', width: '300px'}}bordereddataSource={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 。先看下图:

Redux的核心API

现在来回顾下这几个核心 API 的作用。具体如下:

  • createStore —— 可以帮助我们创建一个 store ;
  • store.dispatch —— dispatch 方法帮助我们派发 action ,同时,这个 action 会传递给 store ;
  • store.getState —— getState 方法帮助我们获取到所有的数据;
  • store.subscribe —— subscribe 帮助我们订阅 store 的改变,只要 store 发生改变,store.subscribe 接收的回调函数就会被执行。

👝七、进阶组件的拆分

1、UI组件和容器组件的拆分

在上面的代码中,我们已经基本完成了 TodoList 的功能。但是呢,大家有没有发现,在 TodoList.js 文件中,页面的渲染和页面的逻辑编写是放在一起的。

往往在实际开发中,我们都会直接把 UI 组件和容器组件给拆分开来。其中, UI 组件专门用于负责页面的渲染,而容器组件用于负责页面的逻辑。

下面我们来对其进行拆分。首先,我们现在 src 文件夹下新增一个文件,命名为 TodoListUI.js 。具体代码如下:

import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input, Button, List } from 'antd';class TodoListUI extends Component {render() {return (<div style={{ marginTop: '10px', marginLeft: '10px' }}><div><Inputvalue={this.props.inputValue}placeholder="todo info"style={{ width: '300px', marginRight: '10px' }}onChange={this.props.handleInputChange}/><Button type="primary" onClick={this.props.handleBtnClick}>提交</Button></div><Liststyle={{ marginTop: '10px', width: '300px' }}bordereddataSource={this.props.list}renderItem={(item, index) => <List.Item onClick={() => { this.props.handleItemDelete(index) }}>{item}</List.Item>}/></div>)}
}export default TodoListUI;

继续,我们来改造 TodoList.js 文件的内容。具体代码如下:

import React, { Component } from 'react';import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';
import TodoListUI from './TodoListUI';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)this.handleItemDelete = this.handleItemDelete.bind(this)store.subscribe(this.handleStoreChange)}render() {return (<TodoListUIinputValue={this.state.inputValue}list={this.state.list}handleInputChange={this.handleInputChange}handleBtnClick={this.handleBtnClick}handleItemDelete={this.handleItemDelete}/>)}handleInputChange(e) {const action = getInputChangeAction(e.target.value);store.dispatch(action)}handleStoreChange() {this.setState(store.getState())}handleBtnClick() {const action = getAddItemAction();store.dispatch(action)}handleItemDelete(index) {const action = getDeleteItemAction(index);store.dispatch(action);}
}export default TodoList;

大家可以看到,我们把页面的内容给单独抽离出来放到 TodoListUI.js 文件当中,让它只做渲染这一件事情。这样,我们就成功的把 UI 组件和逻辑组件进行拆分。

2、无状态组件

有了 UI 组件之后,我们再来看另外一种组件,无状态组件。所谓无状态组件,就是整个页面什么逻辑都没有,只有一个 render 函数时,我们可以把它称之为是一个无状态组件。

那无状态组件怎么定义呢??

我们可以定义一个函数,这个函数接收一个参数,props 。 TodoListUI.js 文件的具体代码如下:

import React from 'react';
import { Input, Button, List } from 'antd';const TodoListUI = (props) => {return (<div style={{ marginTop: '10px', marginLeft: '10px' }}><div><Inputvalue={props.inputValue}placeholder="todo info"style={{ width: '300px', marginRight: '10px' }}onChange={props.handleInputChange}/><Button type="primary" onClick={props.handleBtnClick}>提交</Button></div><Liststyle={{ marginTop: '10px', width: '300px' }}bordereddataSource={props.list}renderItem={(item, index) => <List.Item onClick={() => { props.handleItemDelete(index) }}>{item}</List.Item>}/></div>)
}export default TodoListUI;

当一个普通函数只有 render 函数的时候,我们完全可以通过一个无状态的组件来替换掉这个普通的组件。那为什么要做这样子的替换呢?

如果我们改造为只有一个函数的时候,那么程序就只需要去运行这个函数,也只需要做这一件事情。换言之,如果我们用 class 的话,那么它的类背后是一个对象,而这个对象又有很多的生命周期函数等等,这就显得没有那么纯粹了。因此,我们定义无状态组件这样的方式,来让组件更加地纯正。

🎩八、Redux发起异步请求

1、Redux中发送异步请求数据

往往在实际的项目中,我们总是需要去和后端请求接口数据并发送 AJAX 请求。那想要在 react 中请求到后端接口数据,该怎么处理呢?

首先我们在 TodoList.js 下面,来请求数据。具体代码如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, initListAction } from './store/actionCreators';class TodoList extends Component {componentDidMount() {axios.get('./list.json').then((res) => {const data = res.data;const action = initListAction(data);store.dispatch(action);})}
}

接着,修改 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';
export const INIT_LIST_ACTION = 'init_list_action';

继续,我们在 actionCreators.js 中对封装 action 。具体代码如下:

import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } 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
});export const initListAction = (data) => ({type: INIT_LIST_ACTION,data: data
})

最后,修改 reducer.js 代码。具体代码如下:

import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION
} from './actionTypes';const defaultStore = {inputValue: '123',list: [1, 2, 3]
};// 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 === INIT_LIST_ACTION) {const newState = JSON.parse(JSON.stringify(state));newState.list = action.data;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;

由此,我们就实现了通过 axios 的方式来发布 AJAX 请求,请让其获取到数据。

2、Redux-thunk中间件

(1)解决什么问题

在上面的例子中,我们成功地对接口的数据发起了请求。上面这种情况是属于比较简单的例子,但是往往在实际场景中我们遇到的,都是比较复杂的例子。

因此,我们希望的是,当遇到异步请求或者是有着非常复杂逻辑的时候,把它移出到其他文件下进行管理。

那这个时候就需要用到 Redux-thunk 中间件来进行问题解决。接下来我们来看下 Redux-thunk 中间件如何使用?

(2)如何使用

第一步: 安装 redux-thunk 。具体命令如下:

npm i redux-thunk -D

第二步: 引入 redux-thunk 。往往我们在实际调试中,都会受用 redux-devtools 去对项目的 store 进行调试。但如果我们既要引入 redux-devtools ,又要引入 redux-thunk 中间件,该怎么处理呢?在 store|index.js 文件中进行处理。具体代码如下:

// compose函数来自于redux中
import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import thunk from 'redux-thunk';const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;const enhancer = composeEnhancers(applyMiddleware(thunk)
);const store = createStore(reducer,enhancer
);export default store;

通过这种形式的编码,使得我们的 store 既支持 windows 下的 devtools ,也就是可以去调试 store ,又可以成功的引入 redux-thunk 。

第三步: 将异步逻辑进行抽离。先来修改 TodoList.js 的代码。具体如下:

import { getTodoList, getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators';class TodoList extends Component {componentDidMount() {// 这里的action是一个函数const action = getTodoList();// 只有用了thunk,action才能是用函数的形式去进行传递store.dispatch(action);}
}

接着,修改 actionCreators.js 的代码。具体代码如下:

// getTodoList 是一个函数
// 以这种形式生成的函数,可以直接接收dispatch方法
export const getTodoList = () => {return (dispatch) => {axios.get('./list.json').then((res) => {const data = res.data;// 这里的 action 是一个对象const action = initListAction(data);dispatch(action);})}
}

下面,我们来解释下上面这两段代码,具体如下:

配置好 redux-thunk 的环境之后,它使得我们可以在 action 里面,写异步的代码了!为什么这么说呢?

  • 以前我们在创建 action 时,只能是一个JS对象,而现在,当使用了 redux-thunk 之后,即使 getTodoList() 返回的不是一个对象而是一个函数,也可以通过 store.dispatch() 的方式,把函数发送给到 store 了。
  • 那为什么能够把函数给发送出去呢?就是因为用了 redux-thunk 。

继续,我们要谈论具体的实现步骤👇:

  • 首先让 TodoList.js 中的 store ,去执行 action 函数。而这个 action 函数,来自于 actionCreators.js 中的 getTodoList() 。
  • 对于 getTodoList() 来说,它要做的事情是去请求json的数据和获取json的数据。
  • 而获取好了数据之后,接下来,要改变 store 里面的数据,那么要先去创建一个 action ,这个 action 用来提供给 store.dispatch() 进行调用。但是呢, store.dispatch() 要怎么去获取呢?我们所返回的那个函数中,就会自动地接收到 store.dispatch() 方法。所以,只要通过 dispatch(action) ,将 action 给派发出去就可以了。
  • 也就是说, redux-thunk 使得我们去创建 action 或者支持 action 时,是一个函数的形式。

(3)为什么要使用 redux-thunk ?

看完上面的解释之后,相信大家也就知道 redux-thunk 的奇妙之处了。那为什么要使用 redux-thunk 呢?👇

如果把异步函数放在组件的生命周期中来使用的话,那么这个组件的逻辑就会变得越来越复杂,组件的内容也会变得越来越多。因此,我们通常就会把这种复杂的异步逻辑给拆分出去进行单独管理。那么现在,我们就借助 redux-thunk 中间件,把异步逻辑给拆分到 actionCreators.js 去进行单独管理。由此,使得代码更加规范和统一。

(4)什么是 Redux-thunk 中间件?

在有了上面内容的铺垫之后,接下来,我们返回到中间件的源头,来谈谈 Redux-thunk 中间件的原理。

所谓中间件,肯定就是说是谁和谁的中间。我们先来看一张图:

Redux Data Flow

Redux 中间件的这个中间,指的是 action 和 store 之间。

之前我们说过,在 redux 中, action 只能是一个对象,就因为它是对象,因此直接把它派发给 store 。现在,当我们使用了 redux-thunk 之后, action 就可以是函数了。那为什么可以是函数呢?

看上面的图中不难发现, action 通过 dispatch 的方法,将数据递交给了 store 。且 action 和 store 之间,是一个 dispatch 方法,那我们说的中间件 middleware ,实际上就是对 dispatch 方法的封装和升级。

对于最原始的 dispatch 方法来说,它会接收到一个 JS 对象并将其传递给 store 。

但如果我们传递的是一个 函数 的话,那么这个 dispatch 就升级了。 dispatch 不会直接把函数传递给 store ,它会通过 redux-thunk 中间件的方式,先执行对应的函数,等执行到需要调用 store 的时候,再去调用 store 。

💼九、Redux的其他中间件

1、Redux-logger

redux 的中间件非常的多,比如 redux-logger 可以记录 action 每一次派发的日志。那它怎么记录呢?

它在每一次调用 action 的时候,会通过 dispatch 方法把 action 传递给 store ,之后呢,我们可以对 dispatch 做一个升级,让 dispatch 不仅把 action 传递给 store ,而且在每一次传递之前,我们还通过 console.log 的方式将其打印出来,这样的话,我们就写了一个 redux-logger 的中间件, 它可以在我们派发 action 的时候,把 action 打印在我们的控制台里面。

2、Redux-saga

(1)Redux-saga是什么

在现如今的项目中,用的比较火的中间件不仅有 redux-thunk , redux-logger ,还有 reudx-saga 的使用范围也非常的广。

reudx-saga 也是解决 react 中异步问题的一个中间件,不同于 redux-thunk 的是, redux-thunk 采用的是把异步操作放到 action 里面去操作。而 redux-saga 采用的设计思想是,单独地把异步逻辑拆分出来,放到另一个文件中去进行管理。那 redux-saga 这个中间件该如何使用呢?

(2)Redux-saga如何使用

我们把上面的 TodoList 组件进行升级改造。首先是 store|index.js 文件。具体代码如下:

import { createStore, applyMiddleware, compose } from "redux";
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import todoSagas from './sagas';const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(sagaMiddleware(sagaMiddleware));const store = createStore(reducer, enhancer);
sagaMiddleware.run(todoSagas);export default store;

在这个文件当中,主要是要把基础配置做好。那这里主要有几个要注意的点是:

  • 引入 createSagaMiddleware ;
  • 之后是使用 const sagaMiddleware = createSagaMiddleware() 将其进行引入;
  • 使用 apllyMiddleware 去使用这个中间件;
  • 使用完中间件之后,我们又创建了 saga.js 。

接下来我们在 store 文件夹下创建 saga.js 。具体代码如下:

import { takeEvery, put } from 'redux-saga/effects';
import { initListAction } from './actionCreators';
import { GET_INIT_LIST } from './actionTypes';
import axios from 'axios';function getInitList() {try {const res = yield axios.get('./list.json');const action = initListAction(res.data);yield put(action);} catch (e) {console.log('list.json网络请求失败');}
}function* mySaga() {// 通过takeEvery去捕获到每一次派发下来的actionyield takeEvery(GET_INIT_LIST, getInitList);
}export default mySaga;

对于 saga.js 来说,有几个要注意的点是:

  • 在 saga.js 里面,一定要导出一个 generator 函数,在这个函数里面,我们写了一些逻辑。逻辑是,当我们接收到的 action 类型是 GET_INIT_LIST 时,那么我们就会去执行 getInitList 这个方法。
  • getInitList() 方法是一个函数,它将会去帮我们取数据,取完数据之后,再将这个数据创建出来一个新的 action ,并将这个 action 通过 yield put(action) 的方式,派发给 store 。

下面我们来看 actionTypes.js 中的内容。具体代码如下:

// CHANGE_INPUT_VALUE、ADD_TODO_ITEM、DELETE_TODO_ITEM、INIT_LIST_ACTION
export const GET_INIT_LIST = 'get_init_list';

接着,我们来到 TodoList.js 。具体代码如下:

import { getInputChangeAction, getAddItemAction, getDeleteItemAction, getInitList } from './store/actionCreators';class TodoList extends Component {// 此处省略n多内容componentDidMount() {const action = getInitList();store.dispatch(action);}
}export default TodoList;

最后是 store|actionCreators.js 。具体代码如下:

import { GET_INIT_LIST, CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM, INIT_LIST_ACTION } from "./actionTypes";// 此处省略n多内容export const getInitList = () => ({type: GET_INIT_LIST
});

在 TodoList.js 中,我们创建了一个 action ,并将这个 action 派发给 store 。

🛵十、React-Redux

1、React-Redux是什么

在学习了 react 之后,紧接着,我们学习了 redux 。那如果把它们俩结合起来, react-redux 是什么呢?

实际上,它是一个第三方模块,它使得我们在 react 中更加方便地使用 redux 。

2、React-Redux的使用

(1)安装React-Redux

同样地,我们以 TodoList 组件为例,来看下 react-redux 的使用。首先新创建一个 react 项目,同时安装 react-redux 。具体命令如下:

npm install react-redux

(2)项目目录

下面先来看项目目录。具体如下图:

在这里插入图片描述

(3)核心内容

第一步,将 TodoList 组件挂载到页面上。src|index.js 文件下的内容如下:

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
import { Provider } from 'react-redux';
import store from './store';const App = (// 表示Provider里面所有的组件,都有能力获取到store<Provider store={store}><TodoList /></Provider>
)ReactDOM.render(App, document.getElementById('root'));

Provider 是 react 提供的第一个核心 API ,它旨在表明, Provider 里面所有的组件,都有能力获取到 store 。


第二步,编写 src|TodoList.js 的内容。具体代码如下:

import React from 'react';
import { connect } from 'react-redux';const TodoList = (props) => {const { inputValue, list, changeInputValue, handleClick, handleDelete } = props;return (<div><div><input value={inputValue} onChange={ changeInputValue }/><button onClick={ handleClick }>提交</button></div><ul>{list.map((item, index) => {return <li onClick={handleDelete} key={index}>{ item }</li>})}</ul></div>)
}const mapStateToProps = (state) => {return {inputValue: state.inputValue,list: state.list}
}// store, dispatch, props
const mapDispatchToProps = (dispatch) => {return {changeInputValue(e) {const action = {type: 'change_input_value',value: e.target.value};// console.log(action.value)dispatch(action);},handleClick() {const action = {type: 'add_item'}dispatch(action);},handleDelete() {}}
}// 让我们的TodoList和store做连接
// TodoList是一个UI组件,connect把这个UI组件和前边的业务逻辑相结合,可以把前面括号的内容称为是容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

在上面的代码中,我们要注意的是 react-redux 中的 connect 。

connect 表示的是连接,那么是谁和谁做连接呢? TodoList 和 store 做连接。它们俩做连接需要一个映射关系,这个映射关系就在 mapStateToProps 里面。

在 mapStateToProps 中, state 指的是 store 里面的数据,那 store 里面的数据,就把它映射到 props 里面,之后我们就可以通过 this.props.xxx 的方式,去获取到 store 里面的数据。


第三步,创建 reducer 。在 src|store|reducer.js 下进行编写,具体代码如下:

const defaultState = {inputValue: '',list: []
}export default (state = defaultState, action) => {if (action.type === 'change_input_value') {const newState = JSON.parse(JSON.stringify(state));newState.inputValue = action.value;return newState;}if (action.type === 'add_item') {const newState = JSON.parse(JSON.stringify(state));newState.list.push(newState.inputValue);newState.inputValue = '';return newState;}return state;
}

将 store 中的数据给放到 reducer 当中去进行记录。


第四步,将 reducer 传给 store 。在 src|store|index.js 下进行编写,具体代码如下:

import { createStore } from 'redux';
import reducer from './reducer';const store = createStore(reducer);export default store;

我们将 reducer 中存放的内容进行深拷贝,并把它传回给 store 。这样,就形成了一个数据传递的闭环。


最后,我们来看一下浏览器显示的效果:

React-Redux

相比于使用中间件来说, React-Redux 的使用更加地直观和简洁。在实际项目中,不管是 redux 中间件,还是 react-redux ,都值得拿来做状态管理。

那么要注意的是,redux 中间件和 react-redux 之间,各自在使用过程中不同的点,区分好即可。至于在项目中使用哪一种类型,就依据当下的项目场景去决定就好啦!

🚦十一、结束语

在上面的文章中,我们讲解了 Redux 设计和使用的三项原则,同时,也讲解了 Redux 中的一些核心 API 。除此之外呢,我们还学习了 redux 的中间件, redux-thunk 和 redux-saga 。同时,还学习了另外一个做状态管理的内容, react-redux 。

到这里,关于 redux 的内容就介绍完毕啦!不知道大家是否对 redux 又有了新的了解呢?

🐣彩蛋 One More Thing

(:往期推荐

👉初探react,用react实现一个todoList功能

👉react只停留在表层?五大知识点带你梳理进阶知识

(:番外篇

  • 关注公众号星期一研究室,第一时间关注优质文章,更有 「offer来了」面试专栏 待你解锁~
  • 如果您觉得这篇文章有帮助到您的的话不妨点赞支持一下哟~~😉
  • 以上就是本文的全部内容!我们下期见!👋👋👋

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/307844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core 3.x控制IHostedService启动顺序浅探

想写好中间件&#xff0c;这是基础。一、前言今天这个内容&#xff0c;基于于ASP.NET Core 3.x。从3.x开始&#xff0c;ASP.NET Core使用了通用主机模式。它将WebHostBuilder放到了通用的IHost之上&#xff0c;这样可以确保Kestrel可以运行在IHostedService中。我们今天就来研究…

com.mysql.cj.exceptions.InvalidConnectionAttributeException

一:java连接数据库报错 com.mysql.cj.exceptions.InvalidConnectionAttributeException 二:报错原因 MySQL jdbc 6.0 版本以上必须配置“serverTimezone”参数 UTC代表的是全球标准时间 若我们使用的时间是北京时区也就是东八区&#xff0c;领先UTC八个小时。url的时区使用…

Istio Pilot 源码分析(一)

张海东&#xff0c; ‍多点生活&#xff08;成都&#xff09;云原生开发工程师。Istio 作为目前 Servic Mesh 方案中的翘楚&#xff0c;吸引着越来越多的企业及开发者。越来越多的团队想将其应用于微服务的治理&#xff0c;但在实际落地时却因为不了解 Istio 黑盒中的运行机制而…

结营啦!有缘相聚于青训,未来高处见呀~~

&#x1f4f8;叮&#xff01; 记 字节跳动第一届青训营顺利结营啦&#xff01; 从8月份的青训营&#xff0c;到9月份的实训营&#xff0c;搁置了许久的结营心得终于拾起来辽&#xff01; &#x1f3ac;开营进行时 从答疑会开始&#xff0c;负责人仔细的阐述了本次训练营的…

MVC三层架构(详解)

1:初始MVC (1):三层架构 三层架构是指&#xff1a;视图层 View、服务层 Service&#xff0c;与持久层 Dao。它们分别完成不同的功能。 View 层&#xff1a;用于接收用户提交请求的代码在这里编写。 Service 层&#xff1a;系统的业务逻辑主要在这里完成。 Dao 层&#xff1a;…

「offer来了」保姆级巩固你的js知识体系(4.0w字)

「面试专栏」前端面试之JavaScript篇&#x1f9d0;序言&#x1f973;思维导图环节&#x1f60f;一、JS规范1、说几条JavaScript的基本规范。2、对原生JavaScript的了解。3、说下对JS的了解吧。4、JS原生拖拽节点5、谈谈你对ES6的理解6、知道ES6的class嘛&#xff1f;7、说说你对…

写作是人生最大的杠杆

职场&认知洞察 丨 作者 / 易洋 这是findyi公众号的第71篇原创文章不知不觉&#xff0c;公众号写作已经持续了9个月了。去年11月底&#xff0c;心血来潮写了第一篇文章&#xff0c;更多是为了复盘过去的一些工作经历。在前几天&#xff0c;读者数突破了3万&#xff0c;虽然…

拥塞控制(详解)

一&#xff1a;TCP的拥塞控制 1:是什么 (1):是什么(拥塞现象) 网络的 吞吐量 与 通信子网 负荷(即通信子网中正在传输的分组数)有着密切的关系。当 通信子网 负荷比较小时,网络的 吞吐量 (分组数/秒)随网络负荷(每个 节点 中分组的平均数)的增加而线性增加。当网络负荷增加到…

解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

解决 WPF 绑定集合后数据变动界面却不更新的问题独立观察员 2020 年 9 月 9 日在 .NET Core 3.1 的 WPF 程序中打算用 ListBox 绑定显示一个集合&#xff08;满足需求即可&#xff0c;无所谓什么类型的集合&#xff09;&#xff0c;以下是 Xaml 代码&#xff08;瞟一眼就行&…

Kubernetes Liveness and Readiness Probes

在设计关键任务、高可用应用程序时&#xff0c;弹性是要考虑的最重要因素之一。当应用程序可以快速从故障中恢复时&#xff0c;它便具有弹性。云原生应用程序通常设计为使用微服务架构&#xff0c;其中每个组件都位于容器中。为了确保Kubernetes托管的应用程序高可用&#xff0…

「offer来了」2种递进学习思维,24道计网题目,保姆级巩固你的计网知识体系

「面试专栏」前端面试之计算机网络篇⚾序言&#x1f3d0;一、基础知识环节1、专栏学习2、书籍学习⚽二、思维导图环节&#x1f3b3;三、OSI七层模型1、OSI模型是什么&#xff1f;2、OSI七层模型遵循原则&#x1f3cf;四、TCP与UDP1、TCP与UDP的区别2、TCP/UDP的优缺点&#xff…

leetcode236. 二叉树的最近公共祖先

一:题目 二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/ class Solution { public:/**思路:1.这里我们需要的是从底向上开…

进击吧! Blazor !第二期 页面制作

Blazor 是一个 Web UI 框架&#xff0c;可通过 WebAssembly 在任意浏览器中运行 .Net 。Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScrip…

「软件项目管理」一文详解软件项目管理概述

一文详解软件项目管理概述&#x1f6b5;前言&#x1f93d;一、项目与软件项目1、项目的定义2、项目的特征3、项目与日常运作举例&#xff08;1&#xff09;判断哪些活动是项目&#xff08;2&#xff09;举例结果&#xff08;3&#xff09;项目与日常运作区别总结4、软件项目的特…

初识ABP vNext(9):ABP模块化开发-文件管理

点击上方蓝字"小黑在哪里"关注我吧创建模块模块开发应用服务运行模块单元测试模块使用前言在之前的章节中介绍过ABP扩展实体&#xff0c;当时在用户表扩展了用户头像字段&#xff0c;用户头像就涉及到文件上传和文件存储。文件上传是很多系统都会涉及到的一个基础功能…

「offer来了」浏览器原理被问懵?5大知识板块巩固你的http知识体系(3.6w字)

「面试专栏」前端面试之浏览器原理篇&#x1f3d4;️序言&#x1f304;一、http和https协议&#xff08;一&#xff09;http和https之间的关系&#x1f9ed;1、http和https是什么&#xff1f;2、http和https的区别&#xff08;二&#xff09;http协议&#x1f9ed;1、http1.0、…

使用Azure DevOps Pipeline实现.Net Core程序的CD

上一次我们讲了使用Azure DevOps Pipeline实现.Net Core程序的CI。这次我们来演示下如何使用Azure DevOps实现.Net Core程序的CD。实现本次目标我们除了Azure DevOps外还需要&#xff1a;一台安装了Docker的主机一个 Docker Hub 账号上一次我们的CI实现了&#xff1a;发布>编…

TCP四次挥手(详解)

一:TCP四次挥手 1:图示 二:TCP四次挥手的过程 所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放&#xff0c;另一方被动释放。挥手之前主动释放连接的客户端结束ESTABLISHED阶段。随后开始“四次挥手”&#xff1a; a:首先客户端想要释放连接&#xff0c…

「软件项目管理」项目初始——项目确立与生存期模型

「软件项目管理」项目初始阶段——项目确立与生存期模型&#x1f6f0;️序言Preface&#x1f680;一、项目评估1、评估内容2、净利润与投资回报率3、举例阐述&#x1fa90;二、项目立项1、立项流程2、Make or Buy决策3、Make or Buy决策实例&#x1f6f8;三、项目招投标1、项目…

双城生活,一种相对无奈且幸福的选择

这是头哥侃码的第215篇原创我小时候经常被人问到一个问题&#xff1a;“你喜欢夏天还是冬天&#xff1f;”“夏天啊&#xff01;因为夏天可以有两个月的暑假&#xff0c;而且还可以玩水&#xff0c;还有清凉的盐水棒冰、短裤和凉拖&#xff0c;还可以在空调间里打游戏&#xff…