目录
一、前言
二、Ant Design
三、Redux 初学
3.1 Redux简介
3.2 Redux工作流程
3.3 Chrome安装Redux插件
3.4 使用Redux
3.4.1 安装redux
3.4.2 创建目录
四、Redux 进阶
4.1 使用Redux 显示数据
4.2 使用Redux 操作数据
4.2.1 Input输入数据
4.2.2 Button 提交数据
4.3 使用Redux 删除数据
五、进行优化
5.1 actionTypes的拆分
5.2 使用actionCreate统一创建action
六、UI组件和容器组件
七、无状态组件
八、Redux 中发送异步请求获取数据
九、Redux中间件
9.1 redux-thunk 实现ajax数据请求
9.2 thunk-saga 实现ajax数据请求
十、React-redux的使用
十一、总结
这篇文章是我在学习慕课视频的过程中做的课程总结,边看视频边总结。内容比较多,可参考目录
可参考官网:https://ant.design/index-cn
antd
是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。
用这个框架来构建我们TodoList的界面
cnpm install antd --save
import React, { Component } from 'react'; //引入React及其Component
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
//定义数据
const data=[
"this is first TodoList",
"this is second TodoList",
"this is third TodoList",
"this is fourth TodoList",
"this is fifth TodoList"
]
class App extends Component {
render() {
return (
({item} )}
/>
);
}
}
export default App;
react可以做一些小型项目,是一个视图层的框架,用于构建用户界面的JavaScript库,当我们在React中,如果兄弟组件需要传值,例如左图中深色圆圈发送数据到顶部的圆圈,需要一层一层的传上去,导致数据通讯很麻烦。
而Redux很好的弥补了这种通讯方式,在Redux中我们用Store 来保存数据,实现数据的共享,你可以把它看成一个容器。整个应用只能有一个 Store。当某个组件的值改变时,store就会接收到改变值,并传给需要的组件。接下来我们就看Redux的工作流程。
我们用这个图来给大家讲解,将整个工作流程比作一个图书馆借书的流程
流程:react对Store说 我要借xxx书,react说的这句话就是Action Creators,图书管理员Store并不知道书的具体情况,那他就要查阅资料,也就是从Reducer图书馆的系统里中查找资料,并且将书的具体信息再反馈给Store,最后Store再将数据传递给React,这就是redux的工作流程。
要通过在Chrome中的网上应用商店添加插件(Redux DevTools2.17.0 )就可以使用控制台来调试redux的代码。
在Chrome添加好插件以后,要在index.js组件的createStroe函数中传入第二个参数就可以正常使用控制台
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
cnpm install --save redux
在 src 目录下创建 store 目录用来存储 Redux 数据,该目录下有 reducer.js 以及 index.js 两个文件
1. index.js组件:Redux 提供createStore
这个函数,用来生成 Store即创建数据仓库,并将仓库导出去给 TodoList.js 使用。
//创建仓库
import { createStore } from "redux";
const store=createStore();
export default store;
在使用Store时 有一些常用的API:
Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。
2. reducer组件:该组件用来定义数据,存储数据
Reducer 是一个函数,它接受 action 和当前 state 作为参数,返回一个新的 State。
// 整个应用的初始状态,可以作为 State 的默认值
const defaultState={
inputValue:"这是reducer初始的默认数据",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
return state;
}
此时就要在index.js中导入reducer,作为参数传给createStore
//创建仓库,并传入全部的数据
import { createStore } from "redux";
import reduce from "./reducer"
const store=createStore(reduce,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
//导入redux
import store from "../store/index";
class App extends Component {
constructor(props){
super(props);
//获取到Store中的所有数据
this.state=store.getState();
}
render() {
return (
{/* 将原来的data换成 state中的数据 */}
({item} )}
/>
);
}
}
export default App;
效果展示:
TodoList.js组件
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
//导入redux
import store from "../store/index";
class App extends Component {
constructor(props){
super(props);
//store.getState() 方法来获取数据,并赋值为 state
this.state=store.getState();
// 定义handleInputChange 改变this指向
this.handleInputChange=this.handleInputChange.bind(this)
// 定义handleStoreChange 来处理 Redux 返回回来的数据
this.handleStoreChange=this.handleStoreChange.bind(this)
//监听数据改变,调用handleStoreChange函数重新渲染界面
store.subscribe(this.handleStoreChange)
}
render() {
return (
({item} )}
/>
);
}
handleInputChange=(e)=>{
console.log(e.target.value)
const action ={
type:"change_input_value",
value:e.target.value
}
//传给store
store.dispatch(action);
}
handleStoreChange(){
this.setState(store.getState())
}
}
export default App;
reducer.js组件
// 整个应用的初始状态,可以作为 State 的默认值
const defaultState={
inputValue:"这是reducer初始的默认数据",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
console.log(state,action);
//接收到数据后,将新的newState返回去
if(action.type==='change_input_value'){
//深拷贝
const newState=JSON.parse(JSON.stringify(state));
newState.inputValue=action.value;
return newState;
}
return state;
}
当我们修改input框中的内容时,控制台输出的内容就会改变
分析总结:
constructor
中通过 store.subscribe
监听 Redux 传回来的数据,使用处理方法 handleStoreChange
。handleStoreChange方法中,我们只需要使用setState()重新设置state即可,即
this.setState(store.getState())index.js组件
import React, { Component } from 'react'; //引入React及其Component
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
import store from "../store/index"
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return (
({item} )}
/>
);
}
handleValueChange(e){
const action ={
type:"change_input_value",
value:e.target.value
}
//通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action={
type:"change_todo_item"
}
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
}
export default TodoList;
reducer.js
const defaultState={
inputValue:"这是reducer的默认值",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
console.log(state,action);
if(action.type==="change_input_value"){
const newState=JSON.parse(JSON.stringify(state));
newState.inputValue=action.value;
return newState;
}
if(action.type==="change_todo_item"){
const newState=JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.value=""
return newState;
}
return state;
}
分析总结:
做完input的功能以后,在写button的功能就比较容易理解
dispatch(action)
,将数据传递给 store
注意:在写input方法时已经设置了监听事件,只要页面发生改变,就会执行handleStateChange()获取最新数据,并且渲染界面
index.js
import React, { Component } from 'react'; //引入React及其Component
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
import store from "../store/index"
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return (
({item} )}
/>
);
}
handleValueChange(e){
const action ={
type:"change_input_value",
value:e.target.value
}
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action={
type:"change_todo_item"
}
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelet(index){
console.log(0)
const action={
type:"delete_todo_item",
index
}
store.dispatch(action);
}
}
export default TodoList;
reducer.js
const defaultState={
inputValue:"这是reducer的默认值",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
console.log(state,action);
if(action.type==="change_input_value"){
const newState=JSON.parse(JSON.stringify(state));
newState.inputValue=action.value;
return newState;
}
//添加列表项
if(action.type==="change_todo_item"){
const newState=JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.value=""
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;
}
分析总结:
this
,并且传递值index
,即两个值,所以我们直接在代码中:this.handleDeleteItem.bind(this, index)
在前面的部分中我们完成了TodoList的基本功能,这章我们要对其进行优化
在TOdoList.js和reducer.js 的组件中我们都写了action的type,只有二者相等时才会满足条件,进行数据的修改,但是如果我们在二者中写错任意一个action的type,就无法达到预期的效果,控制台也不会报错,所以可能会因为一些小失误让我们找好久的bug(omygod)
所以我们需要进行下 type 的处理,我们在 store 目录下新增一个 actionTypes.js的组件
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const CHANGE_TODO_ITEM = 'change_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
然后在TodoList.js中引入
import React, { Component } from 'react'; //引入React及其Component
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
import store from "../store/index"
import { CHANGE_INPUT_VALUE, CHANGE_TODO_ITEM, DELETE_TODO_ITEM } from '../store/actionTypes'; // 引用 actionTypes
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return (
({item} )}
/>
);
}
handleValueChange(e){
const action ={
type:CHANGE_INPUT_VALUE,
value:e.target.value
}
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action={
type:CHANGE_TODO_ITEM
}
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelet(index){
console.log(0)
const action={
type:DELETE_TODO_ITEM,
index
}
store.dispatch(action);
}
}
export default TodoList;
reducer.ja组件中引入
import { CHANGE_INPUT_VALUE, CHANGE_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes'; // 引用 actionTypes
const defaultState={
inputValue:"这是reducer的默认值",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
console.log(state,action);
if(action.type===CHANGE_INPUT_VALUE){
const newState=JSON.parse(JSON.stringify(state));
newState.inputValue=action.value;
return newState;
}
if(action.type===CHANGE_TODO_ITEM){
const newState=JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.value=""
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;
}
在我们的工作流程中 Action Creator并不是在React中的,而是由React的某个动作产生的,也就是说不应该在TodoList.js组件中中直接定义action,我们可以通过actionCreators统一管理页面上所有的action,也就是说用actionCreators创建action ,便于代码的管理维护。
在store文件夹下创建actionCreators.js组件
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from "./actionTypes";
export const getInputChangeAcion=(value)=>({
type:CHANGE_INPUT_VALUE,
value
})
export const getAddItemAcion=(value)=>({
type:ADD_TODO_ITEM,
})
export const getDeleteItemAcion=(index)=>({
type:DELETE_TODO_ITEM,
index
})
TodoList.js组件
import React, { Component } from 'react'; //引入React及其Component
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
import store from "../store/index";
import {getInputChangeAcion,getAddItemAcion,getDeleteItemAcion} from "../store/actionCreators" //引入actionCreators
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return (
({item} )}
/>
);
}
handleValueChange(e){
const action =getInputChangeAcion(e.target.value)
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action=getAddItemAcion()
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelete(index){
const action=getDeleteItemAcion(index)
store.dispatch(action);
}
}
export default TodoList;
注意:我们在actionCreators中导入并使用了 actionTypes,那么在TodoList中就不需要再导入
这样,我们就把整个 action
抽取出来了,在大型项目中,对我们的工作会非常方便。
要在store文件夹下创建TodoListUI.js组件,将它看做UI组件,TodoList.js做为容器组件,这俩个组件需要传值,也就是我们在React中学习的子父组件传值。
现在我们拆分这俩个组件
TodoList.js组件代码
import React, { Component } from 'react'; //引入React及其Component
import TodoListUI from "./TodoListUI";
import store from "../store/index";
import {getInputChangeAcion,getAddItemAcion,getDeleteItemAcion} from "../store/actionCreators" //引入actionCreators
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
this.handleItemDelete=this.handleItemDelete.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return ;
}
handleValueChange(e){
const action =getInputChangeAcion(e.target.value)
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action=getAddItemAcion()
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelete(index){
const action=getDeleteItemAcion(index)
store.dispatch(action);
}
}
export default TodoList;
TodoListUI.js组件的代码
import React,{Component} from "react";
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
class TodoListUI extends Component{
render (){
return (
({item} )}
/>
)
}
}
export default TodoListUI;
分析总结:
注意:在处理handleItemDelete方法时要对index做特殊处理
这样,我们就完成了页面的抽取,当我们页面过多的时候,我们就将内容独立到 UI 组件中。而容器组件,则可以包含无数个 UI 组件。所以:容器组件是聪明组件,它对整体进行了一个把控;而 UI 组件是傻瓜组件,只需要执行容器组件传递过来的事件并渲染页面即可。
当一个组件中,只有 render() 函数,没有任何逻辑操作的时候 ,我们就把它叫做无状态组件。其实无状态组件就是一个函数。
无状态组件只需要执行一个函数,性能比较高,普通组件在执行时会执行函数也会执行生命周期函数,性能比较低
import React,{Component} from "react";
import 'antd/dist/antd.css'; //引入antd的样式
import { Input,Button,List } from 'antd'; //引入antd的列表
//无状态组价
const TodoListUI=(props)=>{
return (
({item} )}
/>
)
}
export default TodoListUI;
分析总结:
Component
,进行无状态组件定义,定义一个箭头函数,参数为props。cnpm install axios --save
Charles是一个HTTP代理/HTTP监视器/反向代理(抓包工具),它允许开发人员查看他们的机器和Internet之间的所有HTTP和SSL/HTTPS通信,包括请求、响应和HTTP头(包含cookie和缓存信息)。
基本原理就是将自己作为代理服务器,浏览器、手机app等客户端进行代理设置,配置成Charles监听的端口,客户端将请求发给Charles,Charles再将请求发送给真正服务器,结果返回时,由Charles转发给浏览器、手机等客户端。
在本案例中创建一个list.json文件,文件内容为
["苹果","香蕉","西瓜"]
我们使用axios异步请求获取list.json中的内容
TodoList.js组件
import React, { Component } from 'react'; //引入React及其Component
import axios from "axios" //引入axios
import TodoListUI from "./TodoListUI";
import store from "../store/index";
import {getInputChangeAcion,getAddItemAcion,getDeleteItemAcion,initListAction} from "../store/actionCreators" //引入actionCreators
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
this.handleItemDelete=this.handleItemDelete.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return ;
}
// 在 componentDidMount() 中进行 axios 接口调用
componentDidMount(){
axios.get("./list.json").then((res)=>{
const data=res.data;
// 将接口数据 dispatch 到 action 中,所以需要先前往 actionCreators.js 中创建 action
const action =initListAction(data);
// 创建好action 并 dispatch 到 reducer.js 中
store.dispatch(action)
})
}
handleValueChange(e){
const action =getInputChangeAcion(e.target.value)
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action=getAddItemAcion()
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelete(index){
const action=getDeleteItemAcion(index)
store.dispatch(action);
}
}
export default TodoList;
actionsCreators.js组件
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION} from "./actionTypes";
export const getInputChangeAcion=(value)=>({
type:CHANGE_INPUT_VALUE,
value
})
export const getAddItemAcion=(value)=>({
type:ADD_TODO_ITEM,
})
export const getDeleteItemAcion=(index)=>({
type:DELETE_TODO_ITEM,
index
})
// 创建要导出的 initListAction,所以需要先在 actionTypes 中引入 INIT_LIST_ACTION
export const initListAction=(data)=>({
type:INIT_LIST_ACTION,
data
})
actionTypes组件
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'
reducer.js组件
//reducer 一定是一个纯函数 指给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
import { CHANGE_INPUT_VALUE, ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes'; // 引用 actionTypes
const defaultState={
inputValue:"这是reducer的默认值",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
console.log(state,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.value=""
return newState;
}
if(action.type===DELETE_TODO_ITEM){
const newState=JSON.parse(JSON.stringify(state));
newState.list.splice(action.index,1);
return newState;
}
// 接受 TodoList 传递过来的数据,并进行处理与返回
if(action.type===INIT_LIST_ACTION){
const newState=JSON.parse(JSON.stringify(state));
newState.list=action.data;
return newState;
}
return state;
}
分析总结:
通过以上代码使用Charles抓包工具和axios,我们完成异步请求的数据,并最终渲染到界面上。
中间件的工作流程图
在上面图中我们可以看出,通过 Dispatch 将 Action 派发到 Store 的时候,我们在 Dispatch 中引用了中间件做处理。它对 Dispatch 做了封装升级,从而使得我们不仅可以在 Dispatch 使用对象,而且可以使用方法函数。
直接使用redux可以传递对象,若使用了 Redux-Thunk 或者 Redux-Saga ,就会传递给 Dispatch 一个函数,中间件会对函数进行处理,我们也可以调用函数
因此,简单来说,Redux 的中间件,就是对 Dispatch 的封装升级。
在上一章中我们使用axios异步请求数据,但随着axios的增多,如果我们将所有的异步请求都放在TodoList的组件中,就会显得页面臃肿,因此使用Redux-Thunk 可以把异步请求及复杂业务逻辑抽取到其他地方处理。
cnpm install redux-thunk --save
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
store文件夹中的index.js组件
// 从 redux 中引入 applyMiddleware,其的作用是应用 redux 中间件,即使用redux中间件就必须引入applyMiddleware
// 引入 compose 函数,因为我们用到了两个中间件:redux-thunk 以及 redux-devtools-extension,需要 compose 辅助
import { createStore, applyMiddleware,compose} from 'redux';
import reducer from "./reducer.js";
// 从 redux-thunk 中引入 thunk
import thunk from 'redux-thunk';
const composeEnhancers =
// 使用 redux-devtools-extension 中间件
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 使用 applyMiddleware 对此进行扩展
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;
分析总结:
redux-devtools-extension
中间件applyMiddleware
对此进行扩展,即 redux-thunk
中间件加上 redux-devtools-extension
中间件createStore
进行 enhancer
调用通过以上步骤,我们就可以同时使用redux的redux-thunk中间件和redux-devtoolds-extension
actionCreators.js组件
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION} from "./actionTypes";
import axios from "axios" //引入axios
export const getInputChangeAcion=(value)=>({
type:CHANGE_INPUT_VALUE,
value
})
export const getAddItemAcion=(value)=>({
type:ADD_TODO_ITEM,
})
export const getDeleteItemAcion=(index)=>({
type:DELETE_TODO_ITEM,
index
})
// 创建要导出的 initListAction,所以需要先在 actionTypes 中引入 INIT_LIST_ACTION
export const initListAction=(data)=>({
type:INIT_LIST_ACTION,
data
})
//将TodoList.js,componentDidMount中的axios.get()的内容全部剪切到actionCreators.js中
export const getTodoList=()=>{
// 当我们使用 getTodoList 的时候,在这里能传递 store 的 dispatch,从而直接在下面代码中使用
return (dispatch)=>{
axios.get("./list.json").then((res)=>{
const data=res.data;
// 直接使用 actionCreators.js 中的 initListAction方法
const action =initListAction(data);
// 创建好action 并 dispatch 到 reducer.js 中
dispatch(action)
})
}
}
TodoList.js组件
import React, { Component } from 'react'; //引入React及其Component
import TodoListUI from "./TodoListUI";
import store from "../store/index";
import {getTodoList,getInputChangeAcion,getAddItemAcion,getDeleteItemAcion} from "../store/actionCreators" //引入actionCreators
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
this.handleItemDelete=this.handleItemDelete.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return ;
}
// 在 componentDidMount() 中进行 axios 接口调用
componentDidMount(){
// 在 componentDidMount 中调用 getTodoList。如果我们没使用 redux-thunk,我们只能使用对象,但是现在我们可以使用函数了。
const action =getTodoList();
// 当我们 dispatch 了 action 的时候,我们就调用了上边的 getTodoList(),从而获取了数据
store.dispatch(action);
}
handleValueChange(e){
const action =getInputChangeAcion(e.target.value)
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action=getAddItemAcion()
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelete(index){
const action=getDeleteItemAcion(index)
store.dispatch(action);
}
}
export default TodoList;
分析总结:
getTodoList()
的时候,传递参数为 store
的 dispatch
,从而在下面代码中直接调用dispatch(action)initListAction
方法,并 dispatch
该 action
getTodoList()
,并去除没再引用的initListAction
dispatch
了 action
的时候,我们就调用了 getTodoList()
,从而获取数据通过以上步骤,我们就使用 redux-thunk
,将 axios
的接口调用抽取到了 actionCreators.js 中了。
原因总结:
我们为什么要将 axios
的接口调用抽取到 actionCreators.js 中呢?可以假设一下,当我们的项目足够复杂,逻辑复杂,代码很多以及数据接口有很多时,如果我们的接口调用都在容器组件中,页面就会很臃肿,而且在项目最后如果我们需要改动某个接口,我们就很难快速的找到接口,这无疑会增加工作量,浪费时间。
但是通过 redux-thunk
的调用,我们就把接口代码从容器组件中抽取出来,从而做到:接口代码逻辑是接口代码逻辑,业务代码逻辑是业务代码逻辑。
而且,通过 redux-thunk
的抽取,可以方便我们的自动化测试。当然,自动化测试是什么东东,我们还不清楚,咱也不知道咱也不敢说(哈哈)
cnpm install --save redux-saga
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// then run the saga
sagaMiddleware.run(mySaga)
// render the application
Store文件夹下的index.js组件
// 引入 applyMiddleware 和 compose 进行多个中间件的处理
import { createStore, applyMiddleware,compose} from 'redux';
import reducer from "./reducer.js";
import createSagaMiddleware from 'redux-saga'; //引入 redux-saga 的 createSagaMiddleware
import TodoSagas from './sagas'; //引入sagas文件
const sagaMiddleware = createSagaMiddleware() // 调用 createSagaMiddleware 方法创建redux-saga中间件
const composeEnhancers =
// 使用 redux-devtools-extension 中间件
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware), // 使用 applyMiddleware 对此进行扩展
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(TodoSagas) //使用 TodoSaga
export default store;
Store文件夹下的sagas.js 组件
function* TodoSagas() {
}
export default TodoSagas;
分析总结:
applyMiddleware
和 compose
进行多个中间件的处理redux-saga
的 createSagaMiddleware方法
调用createSagaMiddleware方法创建saga中间件
sagaMiddlewaregenerator
函数定义 sagas.js 文件generator
函数导出去TodoList.js组件
import React, { Component } from 'react'; //引入React及其Component
import TodoListUI from "./TodoListUI";
import store from "../store/index";
import {getInputChangeAcion,getAddItemAcion,getDeleteItemAcion,getInitList} from "../store/actionCreators" //引入actionCreators
class TodoList extends Component {
constructor(props){
super(props)
this.state=store.getState()
this.handleValueChange=this.handleValueChange.bind(this)
this.handleStateChange=this.handleStateChange.bind(this)
this.handleBtnClick=this.handleBtnClick.bind(this)
this.handleTnputKey=this.handleTnputKey.bind(this)
this.handleItemDelete=this.handleItemDelete.bind(this)
store.subscribe(this.handleStateChange)
}
render() {
return ;
}
// 在 componentDidMount() 中进行 axios 接口调用
componentDidMount(){
const action=getInitList();
store.dispatch(action);
}
handleValueChange(e){
const action =getInputChangeAcion(e.target.value)
store.dispatch(action) ;
}
handleStateChange(){
this.setState(store.getState())
}
handleBtnClick(){
const action=getAddItemAcion()
// 通过 dispatch(action),将数据传递给 store
store.dispatch(action) ;
}
handleTnputKey(e){
if(e.keyCode===13){
this.handleBtnClick()
}
}
handleItemDelete(index){
const action=getDeleteItemAcion(index)
store.dispatch(action);
}
}
export default TodoList;
sagas.js组件
import {put, takeEvery } from 'redux-saga/effects';
import {initListAction} from "./actionCreators";
import {GET_INIT_LIST} from "./actionTypes";
import axios from "axios"; //引入axios
function* getInitList(){
try{
const res=yield axios.get('/list.json');
const action =initListAction(res.data.todolist);
yield put(action)
}catch(e){
console.log("网络请求失败")
}
}
function* TodoSagas() {
yield takeEvery(GET_INIT_LIST, getInitList);
}
export default TodoSagas;
actionCreators.js组件
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION,GET_INIT_LIST} from "./actionTypes";
export const getInputChangeAcion=(value)=>({
type:CHANGE_INPUT_VALUE,
value
})
export const getAddItemAcion=(value)=>({
type:ADD_TODO_ITEM,
})
export const getDeleteItemAcion=(index)=>({
type:DELETE_TODO_ITEM,
index
})
// 创建要导出的 initListAction,所以需要先在 actionTypes 中引入 INIT_LIST_ACTION
export const initListAction=(data)=>({
type:INIT_LIST_ACTION,
data
})
export const getInitList=()=>({
type:GET_INIT_LIST
})
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';
export const GET_INIT_LIST='get_init_list';
通过以上操作,我们就把调用接口的异步请求函数,抽取到了 sagas.js 文件中,从而对接口进行统一管理。
分析总结:
initListAction
以及 axios
,并引入 actionCreators.js 中的 getInitList()GET_INIT_LIST
并导出给 actionCreators.js 使用GET_INIT_LIST
getInitList
,并使用 dispatch
将 action
派发出去。这时候不仅 reducer.js 可以接收到这个 action
,我们的 sagas.js 也可以接收到这个 action
。redux-saga/effets
中的 takeEvery
GET_INIT_LIST
类型generator
函数takeEvery
,表示只要我们接收到 GET_INIT_LIST
的类型,我们就执行 getInitList
方法getInitList
方法axios
引入迁移到 sagas.js 中initListAction
getInitList
()store
,所以不能使用 store.dispatch()
,但是 redux-saga
给我们提供了 put
方法来代替 store.dispatch()
方法,所以我们引用 put
方法。等 action
处理完之后,再执行 put
方法:yield put(action)
在 src/store/sagas.js
中,我们还通过 try...catch...
方法,对接口进行处理,当接口不存在或者请求异常的时候,就会抛出错误。
在之前的章节中,我们学习了 React,也学习了 Redux,接触了解Redux 的中间件:Redux-Thunk 和 Redux-Saga。
其实在学习这章节以前我一直以为redux就是react-redux,redux是react-redux的简写,是不是有些小伙伴也有这样的想法啊,其实这俩个是不一样滴,本章节讲解下 React-Redux
它是第三方模块,将所有组件分成两大类:UI 组件和容器组件
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
接下来我们看具体的代码:
index.js组件
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './components/TodoList';
import * as serviceWorker from './serviceWorker';
import {Provider} from "react-redux";
import store from "./store/index.js"
//React-Redux 提供Provider组件(核心 API),Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
const App =(
)
ReactDOM.render(App, document.getElementById('root'));
serviceWorker.unregister();
TodoList.js组件
不用引入store,通过connect连接组件TodoList与store
import React,{Component} from "react";
import { connect } from "react-redux"; //引入核心APIconnect
class TodoList extends Component{
render (){
return (
- {this.props.list}
)
}
}
//建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。 作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射
const mapStateToProps=(state)=>{
return {
inputValue:state.inputValue,
list:state.list
}
}
const mapDispatchToProps=(dispatch)=>{
return{
handleChangeValue(e){
const action ={
type:"change_input_value",
value:e.target.value
}
dispatch(action)
}
}
}
export default connect (mapStateToProps,mapDispatchToProps)(TodoList);
//connect方法接受两个参数:mapStateToProps和mapDispatchToProps。
// 它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),
// 后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
reducer.js组件
const defaultState={
inputValue:"HELLO WORLD",
list:[1,2,3]
}
export default (state=defaultState,action)=>{
if(action.type==='change_input_value'){
const newStore=JSON.parse(JSON.stringify(state));
newStore.inputValue=action.value;
return newStore;
}
return state;
}
分析总结:
store.dispatch
方法映射到props
上,所以我们就可以通过 this.props
来定义方法通过以上步骤,我们就简单的使用了react-redux。对于项目的应有的增删改我们就不一一介绍了,大家可以参考之前的内容,进行项目优化。
经过一段时间的学习,我们掌握了很多的知识点,包括
这些基础知识点很多,需要我们多多练习,熟练掌握。下一个阶段我们就要进行实战啦,期待!