Redux--升级TodoList

目录

一、前言

二、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的使用

十一、总结


一、前言

这篇文章是我在学习慕课视频的过程中做的课程总结,边看视频边总结。内容比较多,可参考目录

  • 《React 16.4 开发简书项目从零基础人门到实战》

二、Ant Design

可参考官网:https://ant.design/index-cn

antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。

用这个框架来构建我们TodoList的界面

  • 安装
cnpm install antd --save
  • 使用antd步骤:
  1. 引入antd的样式
  2. 引入antd的input,button,list组件
  3. 定义数据
  4. 使用input,button,list组件
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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        />
        <Button type="primary">提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={data}
          renderItem={item => (<List.Item>{item}</List.Item>)}
    />
    </div>
    );
  }

}

export default App;
  • 效果展示 

 

三、Redux 初学

3.1 Redux简介 

react可以做一些小型项目,是一个视图层的框架,用于构建用户界面的JavaScript库,当我们在React中,如果兄弟组件需要传值,例如左图中深色圆圈发送数据到顶部的圆圈,需要一层一层的传上去,导致数据通讯很麻烦。

而Redux很好的弥补了这种通讯方式,在Redux中我们用Store 来保存数据,实现数据的共享,你可以把它看成一个容器。整个应用只能有一个 Store。当某个组件的值改变时,store就会接收到改变值,并传给需要的组件。接下来我们就看Redux的工作流程。

 

3.2 Redux工作流程

我们用这个图来给大家讲解,将整个工作流程比作一个图书馆借书的流程

  • react 是借书的用户
  • Store是图书管理员
  • reducer是图书馆的系统
  • Action Creators 是借书的动作

流程:react对Store说 我要借xxx书,react说的这句话就是Action Creators,图书管理员Store并不知道书的具体情况,那他就要查阅资料,也就是从Reducer图书馆的系统里中查找资料,并且将书的具体信息再反馈给Store,最后Store再将数据传递给React,这就是redux的工作流程。

3.3 Chrome安装Redux插件

要通过翻墙在Chrome中的网上应用商店添加插件(Redux DevTools2.17.0 )就可以使用控制台来调试redux的代码。

在Chrome添加好插件以后,要在index.js组件的createStroe函数中传入第二个参数就可以正常使用控制台

window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

3.4 使用Redux 

3.4.1 安装redux

cnpm install --save redux

3.4.2 创建目录

在 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:

  • store.getState()   拿到当前时刻State中的所有数据
  • store.diapatch()   派发action
  • store.subscribe()  监听Store 当数据发生改变时,就会执行

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;

 

四、Redux 进阶

4.1 使用Redux 显示数据

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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue} 
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        />
        <Button type="primary">提交</Button>
    </div>
  {/* 将原来的data换成 state中的数据 */}
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={item => (<List.Item>{item}</List.Item>)}
    />
    </div>
    );
  }

}

export default App;

效果展示:

4.2 使用Redux 操作数据

4.2.1 Input输入数据

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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue} 
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        onChange={this.handleInputChange}
        />
        <Button type="primary">提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={item => (<List.Item>{item}</List.Item>)}
    />
    </div>
    );
  }

  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框中的内容时,控制台输出的内容就会改变

分析总结:

  1. 在input组件中绑顶onChange事件,并为其添加handleInputChange方法
  2. 定义编写handleInputChange方法
  3. 在handleInputChange()中定义action,通过store的dispatch()方法将action传给Redux中的Stroe(index.js),并直接传给Reducer(reducer,js)
  4. 在reducer中打印state和action,state存储的是原来的数据 ,action中存储的改变后的数据
  5. 深拷贝将原来state中数据复制给newState,并将action中的新数据赋值给newState,通过这样的方法newState中存储的就是修改后的数据,最后将newState返回给TodoList.js
  6. 在 TodoList 的 constructor 中通过 store.subscribe 监听 Redux 传回来的数据,使用处理方法 handleStoreChange
  7. handleStoreChange方法中,我们只需要使用setState()重新设置state即可,即 this.setState(store.getState())

4.2.2 Button 提交数据

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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue}
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        onChange={this.handleValueChange}
        //添加回车事件
        onKeyDown={this.handleTnputKey}
        />
        <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={item => (<List.Item>{item}</List.Item>)}
    />
    </div>
    );
  }

  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的功能就比较容易理解

  1. 为button按钮绑定点击事件onClick,添加handleBtnClick方法
  2. 定义编写方法,改变this的指向(可以使用箭头函数)
  3. 通过 dispatch(action),将数据传递给 store
  4. 与input的类似   在 reducer.js 中获取数据,并 return 回去处理结果
  5. 为input 绑定回车事件onKeyDown
  6. 在写回车事件handleInputKey的方法时,只需要再调用一次handleBtnClick()即可

注意:在写input方法时已经设置了监听事件,只要页面发生改变,就会执行handleStateChange()获取最新数据,并且渲染界面

4.3 使用Redux 删除数据

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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue}
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        onChange={this.handleValueChange}
        //添加回车事件
        onKeyDown={this.handleTnputKey}
        />
        <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={(item,index) => (<List.Item onClick={this.handleItemDelet.bind(this,index)}>{item}</List.Item>)}
    />
    </div>
    );
  }

  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;
}

分析总结: 

  1. 列表点击事件绑定 handleItemDelet 方法。此时,由于需要绑定 this,并且传递值index,即两个值,所以我们直接在代码中:this.handleDeleteItem.bind(this, index)
  2. 编写 handleItemDelet 方法
  3. 通过 dispatch(action),将数据传递给 store
  4. 在 reducer.js 中获取数据,并 return 回去处理结果

五、进行优化

在前面的部分中我们完成了TodoList的基本功能,这章我们要对其进行优化

5.1 actionTypes的拆分

在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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue}
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        onChange={this.handleValueChange}
        //添加回车事件
        onKeyDown={this.handleTnputKey}
        />
        <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={(item,index) => (<List.Item onClick={this.handleItemDelet.bind(this,index)}>{item}</List.Item>)}
    />
    </div>
    );
  }

  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;
}

5.2 使用actionCreate统一创建action

在我们的工作流程中 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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
      <div>
        <Input 
        value={this.state.inputValue}
        placeholder="请输入" 
        style={{width:"300px",marginRight:"10px"}}
        onChange={this.handleValueChange}
        //添加回车事件
        onKeyDown={this.handleTnputKey}
        />
        <Button type="primary" onClick={this.handleBtnClick}>提交</Button>
    </div>
    <List    
          style={{width:"300px",marginTop:"10px"}}
          bordered
          dataSource={this.state.list}
          renderItem={(item,index) => (<List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
    />
    </div>
    );
  }

  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 抽取出来了,在大型项目中,对我们的工作会非常方便。 

六、UI组件和容器组件

  • UI组件——傻瓜组件,负责页面的渲染
  • 容器组件——聪明组件,负责页面的逻辑

要在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 <TodoListUI 
    inputValue={this.state.inputValue}
    list={this.state.list}
    handleValueChange={this.handleValueChange}
    handleTnputKey={this.handleTnputKey}
    handleBtnClick={this.handleBtnClick}
    handleItemDelete={this.handleItemDelete}
    />;
  }

  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 (
            <div style={{marginTop:"10px",marginLeft:"10px"}}>
            <div>
              <Input 
              value={this.props.inputValue}
              placeholder="请输入" 
              style={{width:"300px",marginRight:"10px"}}
              onChange={this.props.handleValueChange}
              //添加回车事件
              onKeyDown={this.props.handleTnputKey}
              />
              <Button type="primary" onClick={this.props.handleBtnClick}>提交</Button>
          </div>
          <List    
                style={{width:"300px",marginTop:"10px"}}
                bordered
                dataSource={this.props.list}
                renderItem={(item,index) => (<List.Item onClick={this.props.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
          />
          </div>
        )
    }

}
export default TodoListUI;

分析总结:

  1. 我们将TodoList.js组件,render函数中的内容复制到TodoListUI.js组件render中
  2. 在TodoListUI.js组件中引入antd以及input,button,list的样式
  3. 在TodoList.js中传递数据,在TodoListUI.js接收数据

注意:在处理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 (
    <div style={{marginTop:"10px",marginLeft:"10px"}}>
    <div>
      <Input 
      value={props.inputValue}
      placeholder="请输入" 
      style={{width:"300px",marginRight:"10px"}}
      onChange={props.handleValueChange}
      //添加回车事件
      onKeyDown={props.handleTnputKey}
      />
      <Button type="primary" onClick={props.handleBtnClick}>提交</Button>
  </div>
  <List    
        style={{width:"300px",marginTop:"10px"}}
        bordered
        dataSource={props.list}
        renderItem={(item,index) => (<List.Item onClick={props.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
  />
  </div>
    )
}
export default TodoListUI;

分析总结:

  • 我们不需要 react 中的 Component ,进行无状态组件定义,定义一个箭头函数,参数为props。
  • 通过 props 获取父组件传递过来的数据。
  • 我们不需要render 函数,直接 return 返回JSX就可以。
  • 修改 this.props 为 props。

八、Redux 中发送异步请求获取数据

  • 安装axios
cnpm install axios --save
  •  安装Charles

Charles是一个HTTP代理/HTTP监视器/反向代理(抓包工具),它允许开发人员查看他们的机器和Internet之间的所有HTTP和SSL/HTTPS通信,包括请求、响应和HTTP头(包含cookie和缓存信息)。

基本原理就是将自己作为代理服务器,浏览器、手机app等客户端进行代理设置,配置成Charles监听的端口,客户端将请求发给Charles,Charles再将请求发送给真正服务器,结果返回时,由Charles转发给浏览器、手机等客户端。

在本案例中创建一个list.json文件,文件内容为

["苹果","香蕉","西瓜"]

我们使用axios异步请求获取list.json中的内容

  • 在componentDidMount()生命周期中获取接口数据,并最终渲染到界面上

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 <TodoListUI 
    inputValue={this.state.inputValue}
    list={this.state.list}
    handleValueChange={this.handleValueChange}
    handleTnputKey={this.handleTnputKey}
    handleBtnClick={this.handleBtnClick}
    handleItemDelete={this.handleItemDelete}
    />;
  }

  // 在 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,我们完成异步请求的数据,并最终渲染到界面上。

  • TodoList——在引入axios后,在 componentDidMount() 中进行 axios 接口调用
  • actionTypes——创建INIT_LIST_ACTION
  • actionsCreators——编写initListAction函数创建action
  • TodoList——将创建好的action dispatchreducer
  • reducer——接受 TodoList 传递过来的数据,并进行处理与返回

九、Redux中间件

  • 中间件 即是安排在二者之间的插件。
  •  Redux 中间件  即是处于Action与Store之间的中间件

          中间件的工作流程图

在上面图中我们可以看出,通过 Dispatch 将 Action 派发到 Store 的时候,我们在 Dispatch 中引用了中间件做处理。它对 Dispatch 做了封装升级,从而使得我们不仅可以在 Dispatch 使用对象,而且可以使用方法函数。

直接使用redux可以传递对象,若使用了 Redux-Thunk 或者 Redux-Saga ,就会传递给 Dispatch 一个函数,中间件会对函数进行处理,我们也可以调用函数

因此,简单来说,Redux 的中间件,就是对 Dispatch 的封装升级。

9.1 redux-thunk 实现ajax数据请求

在上一章中我们使用axios异步请求数据,但随着axios的增多,如果我们将所有的异步请求都放在TodoList的组件中,就会显得页面臃肿,因此使用Redux-Thunk 可以把异步请求及复杂业务逻辑抽取到其他地方处理。

  • 安装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)
);
  • 仿照GitHub在本案例中使用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;

分析总结:

  1. 从redux中导入applyMiddleware,applyMiddleware作用是应用 redux 中间件,即使用redux中间件就必须引入applyMiddleware
  2. 从redux-thunk中导入thunk
  3. 从redux中引入compose,因为我们用到了两个中间件:redux-thunk 以及 redux-devtools-extension,需要 compose 辅助
  4. 使用 redux-devtools-extension 中间件
  5. 使用 applyMiddleware 对此进行扩展,即 redux-thunk 中间件加上 redux-devtools-extension 中间件
  6. 在 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 <TodoListUI 
    inputValue={this.state.inputValue}
    list={this.state.list}
    handleValueChange={this.handleValueChange}
    handleTnputKey={this.handleTnputKey}
    handleBtnClick={this.handleBtnClick}
    handleItemDelete={this.handleItemDelete}
    />;
  }

  // 在 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;

分析总结:

  1. 在actionCreators.js中引入axios
  2. 在没有引入redux-thunk中间件之前,只能在actionCreators中返回对象,引入以后就可以返回函数getTodoList()
  3. 将TodoList.js,componentDidMount中的axios.get()的内容全部剪切到getTodoList()中
  4. 当我们使用 getTodoList() 的时候,传递参数为 store 的 dispatch,从而在下面代码中直接调用dispatch(action)
  5. 直接使用 actionCreators.js 中的 initListAction 方法,并 dispatch 该 action
  6. 在 TodoList.js 中引用 actionCreators.js 中的 getTodoList(),并去除没再引用的initListAction
  7. 当我们 dispatch 了 action 的时候,我们就调用了 getTodoList(),从而获取数据

通过以上步骤,我们就使用 redux-thunk,将 axios 的接口调用抽取到了 actionCreators.js 中了。

原因总结:

我们为什么要将 axios 的接口调用抽取到 actionCreators.js 中呢?可以假设一下,当我们的项目足够复杂,逻辑复杂,代码很多以及数据接口有很多时,如果我们的接口调用都在容器组件中,页面就会很臃肿,而且在项目最后如果我们需要改动某个接口,我们就很难快速的找到接口,这无疑会增加工作量,浪费时间。

但是通过 redux-thunk 的调用,我们就把接口代码从容器组件中抽取出来,从而做到:接口代码逻辑是接口代码逻辑,业务代码逻辑是业务代码逻辑。

而且,通过 redux-thunk 的抽取,可以方便我们的自动化测试。当然,自动化测试是什么东东,我们还不清楚,咱也不知道咱也不敢说(哈哈)

9.2 thunk-saga 实现ajax数据请求

  • 安装thunk-saga
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
  • 在本案例中引入redux-saga

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;

 分析总结:

  1. 引入 applyMiddleware 和 compose 进行多个中间件的处理
  2. 引入 redux-saga 的 createSagaMiddleware方法
  3. 调用createSagaMiddleware方法创建saga中间件sagaMiddleware
  4. 创建并引入Store文件下的sagas.js组件
  5. 通过sagaMiddleware的run 方法运行sagas.js组件
  6. 使用ES6的 generator 函数定义 sagas.js 文件
  7. 将 generator 函数导出去
  • 使用redux-saga调用接口数据

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 <TodoListUI 
    inputValue={this.state.inputValue}
    list={this.state.list}
    handleValueChange={this.handleValueChange}
    handleTnputKey={this.handleTnputKey}
    handleBtnClick={this.handleBtnClick}
    handleItemDelete={this.handleItemDelete}
    />;
  }

  // 在 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 文件中,从而对接口进行统一管理。

分析总结:

  1. TodoList.js —— 删除 initListAction 以及 axios,并引入 actionCreators.js 中的 getInitList()
  2. actionTypes.js —— 定义 GET_INIT_LIST 并导出给 actionCreators.js 使用
  3. actionCreators.js —— 导入 actionTypes.js 中的 GET_INIT_LIST
  4. TodoList.js —— 调用 getInitList,并使用 dispatch 将 action 派发出去。这时候不仅 reducer.js 可以接收到这个 action,我们的 sagas.js 也可以接收到这个 action
  5. sagas.js ——引用 redux-saga/effets 中的 takeEvery
  6. sagas.js ——引入 GET_INIT_LIST 类型
  7. sagas.js ——使用 generator 函数
  8. sagas.js ——通过 takeEvery,表示只要我们接收到 GET_INIT_LIST 的类型,我们就执行 getInitList方法
  9. sagas.js ——定义 getInitList 方法
  10. 将 TodoList.js 的 axios 引入迁移到 sagas.js 中
  11. 引入 actionCreator.js 中的 initListAction
  12. 在 sagas.js 中处理异步请求函数getInitList ()
  13. 由于我们在 sagas.js 中没有引用到 store,所以不能使用 store.dispatch(),但是 redux-saga 给我们提供了 put 方法来代替 store.dispatch() 方法,所以我们引用 put 方法。等 action 处理完之后,再执行 put 方法:yield put(action)

在 src/store/sagas.js 中,我们还通过 try...catch... 方法,对接口进行处理,当接口不存在或者请求异常的时候,就会抛出错误。

十、React-redux的使用

在之前的章节中,我们学习了 React,也学习了 Redux,接触了解Redux 的中间件:Redux-Thunk 和 Redux-Saga。

其实在学习这章节以前我一直以为redux就是react-redux,redux是react-redux的简写,是不是有些小伙伴也有这样的想法啊,其实这俩个是不一样滴,本章节讲解下 React-Redux

  • 什么是 React-Redux

它是第三方模块,将所有组件分成两大类:UI 组件和容器组件

  • 核心API
  1. Provider  React-Redux 提供Provider组件在根组件外面包了一层,这样一来,Provider下的所有子组件就默认都可以拿到state了。
  2. connect  用于从 UI 组件生成容器组件。connect的意思就是将这两种组件连起来。
  • 详细讲解connect

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 =(
    <Provider store={store}>
        <TodoList></TodoList>
    </Provider>
)
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 (
        <div>
            <div>
                <input value={this.props.inputValue} onChange={this.props.handleChangeValue}/>
                <button>提交</button>
            </div>
            <ul>
                <li>{this.props.list}</li>
            </ul>
        </div>
    )
}
}
//建立一个从(外部的)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;
}

分析总结: 

  1. 重新创建TodoList的项目
  2. index.js——引入react-redux中的Provider,引入store
  3. 用Provider重新定义App,并连接store,使得所有的子组件都可以获得store中的内容
  4. TodoList.js组件——不需要import store  from store,也不需要在constructor中获取store,而是通过react-redux的connect来获取数据
  5. TodoList.js——导出connect,连接TodoList和store,并且需要传入俩个参数mapStateToProps,mapDispatchToProps
  6. 定义mapStateToProps方法,负责输入逻辑,即将state映射到 UI 组件的参数(props)
  7. 定义mapDispatchToProps方法,负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。该方法即是 TodoList.js 将 store.dispatch 方法映射到props 上,所以我们就可以通过 this.props 来定义方法
  8. 给input绑定handleChangeValue事件,通过this.props绑定方法
  9. mapDispatchToProps中定义handleChangeValue方法,并使用disptach将action派发到reducer.js,
  10. reducer.js——判断传来的action的类型,对之前的state进行深拷贝,获取action.value的值并返回

通过以上步骤,我们就简单的使用了react-redux。对于项目的应有的增删改我们就不一一介绍了,大家可以参考之前的内容,进行项目优化。

十一、总结

经过一段时间的学习,我们掌握了很多的知识点,包括

  1. react,redux,react-redux的基础知识点
  2. 学会Ant Design 的reactUI库使用,
  3. 使用axios配合Charles抓包工具实现异步请求数据,而且为了方便接口数据的管理,我们也学习了 Redux 的中间件 Redux-Thunk 和 Redux-Thunk
  4. 以及了解UI 组件、容器组件、无状态组件等
  5. 也对项目进行优化

这些基础知识点很多,需要我们多多练习,熟练掌握。下一个阶段我们就要进行实战啦,期待!


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值