react入门学习实现一个TodoList

一 : 脚手架

react官方提供的脚手架工具:Create-react-app

优点:

  • 官方提供,比较健壮;
  • 使用简单,可定制性强;
  • 调试代码方便;

如果没有深厚webpack配置功底,能够确保驾驭更复杂的脚手架工具的话,使用这个脚手架是一个最佳选择

脚手架的代码不能直接运行,需要脚手架工具编译才可以在浏览器运行,grunt/webpack等工具来帮助写脚手架

Create React App:

npm install -g create-react-app
create-react-app my-app

cd my-app
npm start

生成文件

my-app
  - README.md  项目说明文件,支持markdown语法
  - package.json 任何一个脚手架工具,都会有,代表node包文件
  - .gitinore  使用git管理代码的时候,一些文件不想传到git仓库上,路径写下来
  - node_modules 这个项目一些第三方依赖的包,脚手架使用时需要的一些第三方依赖
  - public
      - favicon.ico 网页标题icon
      - index.html  
      - manifest.json 把网页当成APP使用,保存快捷图标时的配置
  - src 项目源代码
       - index.js  入口
       - App.test.js 自动化测试文件
package.json
{
  "name": "my-app",  //项目名称
  "version": "0.1.0",  //版本号
  "private": true,   //私有项目
  "dependencies": {   //安装的第三方依赖
    "react": "^16.8.4",
    "react-dom": "^16.8.4",
    "react-scripts": "2.1.8"
  },
  "scripts": {  //提供指令调用   npm run start启动
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

serviceWorker的作用:

PWA: progressive web application
通过写网页的形式,来写手机app应用,引入serviceWorker,借助网页来写手机APP应用的功能
效果:写了一个网页,并上传到支持https协议的服务器上,这个网页会具备这样的特性(当用户第一次访问这个网页,需要联网才能看到,下次断网的情况下,依然可以看到上次访问过的页面)serviceWorker会把之前浏览过的页面,存储在浏览器缓存中。

src/index.js

import * as serviceWorker from './serviceWorker';  引用

serviceWorker.unregister();  调用

把网页当成APP显示时的配置
手机/电脑 通过快捷方式,直接进入网址

{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [  快捷方式图标
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",  跳转网址
  "display": "standalone",  
  "theme_color": "#000000",  主题颜色
  "background_color": "#ffffff"
}

二:组件

src/index.js


import App from './App';
加载一个App的文件,这个App 就是一个小的组件

App来自App.js文件(src/App.js)

---------------------
src/App.js
import React, { Component } from 'react';

定义一个类App,继承了React.Component的类
当一个类继承了React.Component的类时候,它就是一个组件了
class App extends Component {  
  render() {  
    return (
      
hello world!
); } render函数返回什么,这个组件就展示什么内容 } export default App; 导出 { Component } 等价于 React.Component import { Component } from 'react'; 等价于 import React from 'react'; const Component = React.Component -------------------- src/index.js import App from './App'; 引入组件 ReactDOM.render(, document.getElementById('root'));

ReactDOM.render() 在做什么:

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(, document.getElementById('root'));

ReactDOM  第三方模块
ReactDOM有一个方法render,可以把一个组件挂载到一个dom节点上

 是jsx语法,如果使用了jsx语法,
就一定要在项目中引入import React from 'react'语法

组件名首字母必须大写

import app from './App';
ReactDOM.render(, document.getElementById('root'));
这种写法会报错,jsx不支持
在JSX语法中,如果要使用自己创建的组件,组件开头必须用大写字母开头

三:最基础的JSX语法

src/App.js
import React, { Component } from 'react';

class App extends Component {
  render() {
    // JSX
    return (
      
hello, 阿鲁提尔
); } } export default App; 在react中,在js写html标签,就是JSX语法 自定义组件
小写开头,h5原始标签

四:使用React编写Todolist功能

src/TodoList.js


import React, { Component,Fragment } from 'react';

class Todolist extends Component {

  //在react中定义数据
  // 在js中,一个类就一定会有一个constructor构造函数
  // 当创建一个Todolist实例,constructor这个函数是优于其他任何函数
  // 最先被执行的函数
  // 会接受一个props参数
  constructor(props){
    super(props)  
    //Todolist 继承了React.Component组件,所以在创建Todolist时,super是调用了Component的构造函数
    //调用一次

    //定义数据
    //数据定义在状态里面,this.state就是Todolist的状态
    //state负责存储组件里面的数据
    this.state = {
      inputValue:'',
      list:['学习英文','学习React']
    }
  }

  render() {
    return (
      
        
    { this.state.list.map((item,index)=>{ return (
  • {item}
  • ) }) }
) } handleInputChange(e){ //改变数据,向里面传入对象 this.setState({ inputValue: e.target.value }) // this.state.inputValue = e.target.value } handleBtnClick(){ this.setState({ list:[...this.state.list,this.state.inputValue], inputValue:'' }) } handleItemDelete(index){ const list = [...this.state.list] list.splice(index,1) this.setState({ list, }) 错误写法,删除出bug,留下做个标记,一会慢慢研究 // let list = this.state.list.splice(index,1) // this.setState({ // list, // }) 强烈不推荐错误写法,违反书写规定 // this.state.list.splice(index,1) // this.setState({ // list:this.state.list // }) //immutable //state 不允许我们做任何的改变 所以赋值出来再修改 //一旦修改state里面的内容,后面会影响React性能优化 } } export default Todolist; //React规定,最外层必须包裹一个元素 // 如果想最外层包裹一个元素,这个元素又不被显示出来 //在react16版本里面,提供了一个Fragment占位符

五:JSX语法细节补充

0. JSX中的注释:

 {/* do something*/}

1. 中value和defaultValue的区别:

单独使用 时,虽然页面可以正常显示,但是控制台会出现如上报错。

  • 如果input 表单只是显示默认值,不会手动去修改该值,则值需要使用defaultValue
  • 如果需要获取用户输入的值,则一定要用value属性,但是一定要配合onchange事件一起使用,否则值是不会变的

2. 样式className代替class(与类冲突)


3. dangerouslySetInnerHTML的作用:
在上面的TodoList中,input输入

学习node.js

,

也会被当成文本显示,dangerouslySetInnerHTML的作用就是,不显示

标签,当做html添加到页面

dangerouslySetInnerHTML 危险的设置元素innerHTML属性
  • {/* {item} 使用了dangerouslySetInnerHTML = {{__html:item}},这里item就不需要写了 */}
  • //dangerouslySetInnerHTML = {{__html:item}}

    dangerouslySetInnerHTML实现的功能

    4. for和htmlFor的区别

    
    
    
    增加输入框选择区域的功能,虽然可以正常显示,但是会出现如下报错
    


    这是因为这个for会和循环的for产生歧义。要换成htmlFor

    六:组件的拆分与组件之间的传值

    父组件向子组件传值

    src/TodoList.js
    
    import TodoItem from './TodoItem';
    
      { this.state.list.map((item,index)=>{ return (
      //使用组件,通过属性传值 {/*
    • */}
      ) }) }
    src/TodoItem.js import React,{Component} from 'react'; class TodoItem extends Component{ render(){ return
    {this.props.content}
    {/*子组件通过this.props.content 获取值*/} } } export default TodoItem;

    子组件向父组件传值

    src/TodoList.js
    import React,{Component,Fragment} from 'react';
    import './style.css'
    import TodoItem from './TodoItem';
    
    
    class TodoList extends Component{
      constructor(props){
        super(props)
    
        this.state={
          InputValue:'',
          list:['学习英语','学习React']
        }
      }
    
      render(){
        return (
          
            
      { this.state.list.map((item,index)=>{ return (
      ) }) }
    ) } handleInputValue(e){ this.setState({ InputValue: e.target.value }) } handleUpInputValue(){ this.setState({ list:[...this.state.list,this.state.InputValue], InputValue:'' }) } handleEnterInput(e){ if(e.keyCode=="13"&&!this.state.InputValue==''){ this.setState({ list:[...this.state.list,this.state.InputValue], InputValue:'' }) }else if(e.keyCode=="13"&&this.state.InputValue==''){ console.log("不能为空") } } handleRemoveInputValue(index){ const list = [...this.state.list] list.splice(index,1) this.setState({ list, }) } } export default TodoList; ---------------------------------------------------- src/TodoItem.js import React,{Component} from 'react'; class TodoItem extends Component{ constructor(props){ super(props); this.handleClick = this.handleClick.bind(this); } render(){ return (
    {this.props.content}
    ) } handleClick(){ this.props.deleteItem(this.props.index) } } export default TodoItem;
    总结:  父组件中(src/TodoList.js)
    
    
      把函数传给子组件,并把this指向TodoList
      把index和item通过属性传递给TodoItem 
      在TodoItem组件中,使用
        this.props.content
        this.props.index
        this.props.deleteItem
      即可使用
    
    
    子组件中(src/TodoItem.js)
    render(){
      return (
        
    {this.props.content} 绑定item
    ) } handleClick(){ this.props.deleteItem(this.props.index) 调用父级函数,并把父级item传递进去 }

    七:TodoList代码优化

    src/TodoItem.js
    
    import React, { Component } from 'react';
    
    class TodoItem extends Component {
      constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
      }
    
      render() {
        const {content} = this.props
        return (
          
  • {content}
  • ) } handleClick(){ const { deleteItem,index } = this.props; deleteItem(index) } } export default TodoItem;
    src/TodoList.js
    
    import React, { Component,Fragment } from 'react';
    import TodoItem from './TodoItem';
    import './style.css';
    
    class Todolist extends Component {
      constructor(props){
        super(props)
        this.state = {
          inputValue:'',
          list:['学习英文','学习React']
        }
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
      }
    
      render() {
    
        return (
          
            
      { this.getTodoItem() }
    ) } getTodoItem(){ return this.state.list.map((item,index)=>{ return ( ) }) } handleInputChange(e){ // 新版支持一个函数 // this.setState(()=>{ // return { // inputValue: e.target.value // } // }) // ---------------- // ES6里面也可以去掉return,直接返回一个对象 // 有一个括号,表示返回里面的对象 // this.setState(()=>({ // inputValue: e.target.value // }) // ) //这样写报错的原因,setState如果传一个函数,是一个异步的,为了性能上的提升 //异步函数 e.target.value会有一些问题 // ---------------- // 解决方法 const value = e.target.value //在外层保存一下 this.setState(()=>({ inputValue: value })); // ---------------- // 最初版 // this.setState({ // inputValue: e.target.value // }) } handleBtnClick(){ //prevState 指的是修改数据之前是什么样的 //等价于this.state this.setState((prevState)=>({ list:[...prevState.list,prevState.inputValue], inputValue:'' })); } handleItemDelete(index){ this.setState((prevState)=>{ const list = [...prevState.list]; list.splice(index,1) return {list} }); } } export default Todolist;

    八:React高级内容

    React developer tools 安装及使用插件:
    google浏览器扩展程序 安装React Developer Tools

    不是React开发的网站

    React开发环境

    线上React开发的网站

    安装完成之后,控制台会增加一个React菜单,可以查看页面结构和数据
    PropTypes 与 DefaultProps 的应用:
    每个组件都有自己的prop参数,这个参数是从父组件接收的一些属性。

    • PropTypes的作用是在接受参数的时候,对参数类型做限制。
    • DefaultProps的作用是定义参数的默认值。
    src/TodoItem.js
    
    import React, { Component } from 'react';
    ----------------
    引入propTypes
    import PropTypes from 'prop-types';
    ------------------
    class TodoItem extends Component {
      constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
      }
    
      render() {
        const {test,content} = this.props
        return (
          
  • {test} - {content}
  • ) } handleClick(){ const { deleteItem,index } = this.props; deleteItem(index) } } -------------------------- 使用 //组件名字TodoItem,对它的属性做强校验 TodoItem.propTypes = { content: PropTypes.string, //content的类型必须是string //PropTypes是上面引入的PropTypes deleteItem: PropTypes.func, index: PropTypes.number, test: PropTypes.number test是没传的值,不会做检验 如果要求必须传test这个属性可以这样写: test: PropTypes.string.isRequired 也可以写判断 content: PropTypes.oneOfType([PropTypes.number,PropTypes.string]) 数字或者字符串 } 有的时候必须要求传递test,但父组件确实没办法传递,可以给test定义一个默认值,来解决报错问题 TodoItem.defaultProps = { test: 'hello world' } -------------------------- export default TodoItem; 从父级传递来的属性有: content,deleteItem,index

    PropTypes 不会阻止程序的正常运行,但是会在控制台弹出一个报错。


    isRequired:表示必须要在父组件里传递,不传递会报警告

    PropTypes的类型:

      optionalArray: PropTypes.array,
      optionalBool: PropTypes.bool,
      optionalFunc: PropTypes.func,
      optionalNumber: PropTypes.number,
      optionalObject: PropTypes.object,
      optionalString: PropTypes.string,
      optionalSymbol: PropTypes.symbol,
     
      // Anything that can be rendered: numbers, strings, elements or an array
      // (or fragment) containing these types.
      optionalNode: PropTypes.node,
     
      // A React element (ie. ).
      optionalElement: PropTypes.element,
      optionalMessage: PropTypes.instanceOf(Message),
      ...
    

    Typechecking With PropTypes - 文档

    props,state与render函数的关系:

    • 当组件的state或者props发生改变的时候,render函数就会重新执行
    • 当父组件的render函数被运行时,它的子组件的render都会被将被重新运行一次(可以从两个方面理解,1.自身的props变化,重新运行;2.父组件重新渲染,顺带把包含的子组件一起重新运行。)
      React中的 虚拟DOM:
      深入了解虚拟DOM:
      虚拟DOM中的Diff算法:
      React中ref的使用:
      在React中使用ref来操作DOM
    src/TodoList.js
    
      render() {
    
        return (
          
            
    {this.input = input}} {/*构建一个ref引用,这个引用叫this.input指向对应的input dom节点*/} />
      { this.getTodoItem() }
    ) } handleInputChange(e){ console.log(e.target) //其实就是dom节点 // const value = e.target.value const value = this.input.value //e.target使用this.input来替换 this.setState(()=>({ inputValue: value })); }

    不推荐使用ref,原因:React中建议数据驱动的方式来编写我们的代码,尽量不要直接操作DOM

      render() {
    
        return (
          
            
    {this.input = input}} />
      this.ul=ul}> {/*绑定ul*/} { this.getTodoItem() }
    ) } handleBtnClick(){ //点击按钮 //prevState 指的是修改数据之前是什么样的 //等价于this.state this.setState((prevState)=>({ list:[...prevState.list,prevState.inputValue], inputValue:'' })); console.log(this.ul.querySelectorAll("li").length); //当点击添加时,获取的页面数据长度总少一个,因为this.setState是异步的 正确办法,使用this.setState第二个参数,也是一个函数,当第一个函数被执行成功的回调 this.setState((prevState)=>({ list:[...prevState.list,prevState.inputValue], inputValue:'' }),()=>{ console.log(this.ul.querySelectorAll("li").length); }); }

    九:React中的生命周期函数


    生命周期函数指在某一个时刻组件会自动调用执行的函数。

    Initialization 是组件初始化
    constructor(props) {
        super(props);
        this.state = {
          inputValue:''
        }
      }
    在这里会定义state,接收props
    constructor是es6语法中自带的函数,所以不算React的生命周期函数。
    但是和React生命周期函数没有太大的区别
    
    Mounting 挂载(组件第一次挂载到页面上)
    • componentWillMount(){}
      在组件即将被挂载到页面的时刻自动执行

    • render(){}
      组件渲染

    • componentDidMount(){}
      在组件被挂载到页面之后,自动被执行

    componentWillMount和componentDidMount只会在组件第一次被放到页面上的时候执行(第一次挂载的时候)

    Updation 组件更新的时候
    • shouldComponentUpdate(){}
      组件被更新之前,他会自动被执行(当组件即将被变更的时候)
      shouldComponentUpdate会要求返回一个布尔类型的结果
    shouldComponentUpdate(){
      return true
    }
    

    shouldComponentUpdate根据单次直译:你的组件需要被更新吗?

    • componentWillUpdate(){}
      组件被更新之前,它会自动执行,但是他在shouldComponentUpdate之后执行
      如果shouldComponentUpdate返回true它才执行
      如果返回false,这个函数就不会被执行了

    • render(){}
      渲染组件

    • componentDidUpdate(){}
      组件更新完成之后,他会被执行

    • componentWillReceiveProps(){}
      一个组件如果是顶层组件,没有props传递进来,是不会执行的
      当一个组件从父组件接受了参数
      如果这个组件第一次存在于父组件中,不会执行
      如果这个组件之前已经存在于父组件中,才会执行
      (只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被执行)

    Unmounting 组件从页面去除
    • componentWillUnmount(){}
      当这个组件即将被从页面中剔除的时候,会被执行

    React developer tools插件使用:

    打开F12,勾选中Highlight Updates,每次组件被更新,会有闪框提示

    每次输入内容,子组件都会更新

    对于一个组件,render函数被执行有两种情况:

    • 在props和state发生变化的时候子组件会被渲染;
    • 父组件render函数执行

    input输入内容时,子组件被重复渲染在于第二种情况:这个逻辑没问题,但会带来性能上的损耗
    父组件input框内容发生变化,子组件没有必要进行重新渲染,现在的机制会造成子组件进行很多无谓的渲染。
    性能优化:

    src/TodoItem.js  子组件使用shouldComponentUpdate
    shouldComponentUpdate(){
      return false;
    }
    {/*shouldComponentUpdate 我的子组件被渲染一次之后,如果子组件需要被更新,那么强制要求不更新*/}
    
    子组件只会被render一次,之后不会被重新渲染

    上面写法不是最优写法

    src/TodoItem.js
    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    
    class TodoItem extends Component {
      constructor(props) {
        super(props)
        this.handleClick = this.handleClick.bind(this)
        //React提升代码性能一:把this作用域的修改放到constructor里面来做,可以保证整个程序里面作用域的绑定只会执行一次,而且可以避免组件一些无谓的渲染
      }
    
      // React提升代码性能二:React底层setState内置了性能机制,是异步的函数,可以把多次数据的改变结合成一次来做,降低虚拟DOM的使用频率
    
      //React提升代码性能三:React底层用了虚拟DOM这个概念,还有同层比对,配值概念,来提升虚拟DOM比对速度,来提升React性能
      shouldComponentUpdate(nextProps,nextState){
        //React提升代码性能四:当我的组件要被更新的时候,props会被更新成什么
        // nextProps 指接下来props会被更新成什么样
        // nextState 指接下来state会被更新成什么样
        if(nextProps.content !== this.props.content){
          //如果接下来变化的content不等于当前props.content,说明组件接收的content值发生了变化
          //需要让这个组件重新渲染
          return true;
        }else{
          //没有变化 返回false,组件没有被重新渲染的必要
          return false
        }
      }
    
      render() {
        console.log("child render")
        const {content} = this.props
        return (
          
  • {content}
  • ) } handleClick(){ const { deleteItem,index } = this.props; deleteItem(index) } } TodoItem.propTypes = { content: PropTypes.string, deleteItem: PropTypes.func, index: PropTypes.number, } export default TodoItem;

    通过shouldComponentUpdate生命周期提升了组件性能,避免一个组件做无谓的render
    render函数从新执行就意味着React底层需要对组件生成一份虚拟DOM,和之前虚拟DOM做比对,虽然虚拟DOM的比对比真实DOM比对性能要快得多,但是能省略比对过程当然
    可以解决更多的性能,shouldComponentUpdate就是做这件事用的

    在React中想发送一个ajax请求:
    不能再render函数中发送ajax请求,会造成死循环,render函数会被反复执行,只要在input框内输入内容,render函数就会重新执行,就重新发送一次ajax请求,这样不合理。ajax获取一次数据就行了,render函数会获取很多次ajax

    在React中,哪一个生命周期函数只会被执行一次?
    componentDidMount(){} 在组建被挂在到页面上的时候,会被执行一次,之后就不会再被重新执行了,把ajax请求放到这里比较合适

    思考:componentWillMount(){}也只执行一次,把ajax请求放到这里可不可以?
    也是没有任何问题的,但是当我们去写react native,或者用React做服务器端同构也就是更深一点的技术,可能会和以后一些更高端技术产生冲突。为了避免冲突,ajax就放到componentDidMount中,永远都不会有任何问题。

    思考:把ajax请求放到constructor中可不可以?
    也可以,因为constructor也是只执行一次的函数,推荐把ajax写在componentWillMount中

    在React项目中如何发送一个ajax请求?
    React没有内置ajax,需要安装一些第三方模块来帮助发送ajax请求。

    npm install -g yarn
    
    进入目录
    yarn add axios
    
    src/TodoList.js
    import React, { Component,Fragment } from 'react';
    import TodoItem from './TodoItem';
    import axios from 'axios';  //引入axios
    import './style.css';
    
    class Todolist extends Component {
      constructor(props){
        super(props)
        this.state = {
          inputValue:'',
          list:[]
        }
        this.handleInputChange = this.handleInputChange.bind(this);
        this.handleBtnClick = this.handleBtnClick.bind(this);
        this.handleItemDelete = this.handleItemDelete.bind(this);
      }
    
      render() {
        return (
          
            
      { this.getTodoItem() }
    ) } //使用axios componentDidMount(){ axios.get('/api/todolist') .then(()=>{alert('succ')}) .catch(()=>{alert('error')}) } //构建一个ref引用,这个引用叫this.input指向对应的input dom节点 getTodoItem(){ return this.state.list.map((item,index)=>{ return ( ) }) } handleInputChange(e){ // console.log(e.target) //其实就是dom节点 // const value = e.target.value //在外层保存一下 const value = e.target.value //e.target使用this.input来替换 this.setState(()=>({ inputValue: value })); } handleBtnClick(){ this.setState((prevState)=>({ list:[...prevState.list,prevState.inputValue], inputValue:'' })); } handleItemDelete(index){ this.setState((prevState)=>{ const list = [...prevState.list]; list.splice(index,1) return {list} }); } } export default Todolist;

    使用Charles实现本地数据mock
    前后端分离,需要前端本地进行接口数据的模拟
    下载安装charles工具
    桌面创建todolist.json,希望发送todolist接口的时候,能够把桌面上的todolist.json返回回来

    todolist.json
    
    ["学习React","学习英语","学习Vue"]
    


    需要借助charles工具来解决这个问题
    charles工具=>Tools=>Map Local
    add 增加一条配置


    请求成功

    查看数据

    charles可以抓到浏览器向外发送的一些请求,然后可以对一些请求做一些处理,比如看到请求地址http://localhost:3000/api/todolist,只要请求这个地址,就把桌面文件返回,charles就是一个中间的代理服务器

      componentDidMount(){
        axios.get('/api/todolist')
          .then((res)=>{
            console.log(res.data)
            // this.setState(()=>{
            //   return {
            //     list: res.data
            //   }
            // })
            this.setState(()=>({
              list:[...res.data]  
              /构建一个新的数组传递,避免不小心把res.data做了修改/
            }))
          })
          .catch(()=>{alert('error')})
      }
    

    十:React中实现CSS过渡动画

    十一:Redux入门

    想做大型应用,需要在React基础上配套一个数据层框架结合使用,全球范围内比较好的搭配数据层框架——Redux。


    Redux基础设计理念:把组件之中的数据放到公用存储区域存储,组件改变数据不需要传递,改变Store里的数据之后,其他组件会感知到Store数据发生改变,再去获取数据,这样不管组件层次有多深,走的流程相同。

    Redux = Reducer + Flux

    Redux的起源:React在2013年开源时,Facebook团队除了放出React框架,还放出了一个框架Flux,Flux框架是官方推出的最原始辅助React使用的数据层框架。业界使用Flux发现一些缺点,比如公共存储区域Store可以有很多store组成,数据存储的时候可能存在数据依赖问题,总之不是特别好用,有人把Flux做了一个升级,升级成了目前使用的Redux。
    在Redux里面除了借鉴Flux以前很多设计理念之外,又引入了Reducer概念。
    Redux的工作流程:

    Redux的工作流程图例

    • Store存放了所有数据(存储数据的公共区域)
    • React Components(组件)从Store里拿数据,每个组件也要去改Store里数据
      流程简述一:

      流程简述二:
      首先由一个组件,组件要去获取Store里一些数据,和Store说"我要获取数据"这句话就是Action Creators,Action Creators创建了一句话之后告诉Store,Store接收到"我要获取数据",Store并不知道需要什么样的数据,他去查一下,Reducers知道应该给你什么样的数据,Reducers告诉Store应该给组件什么样的数据,Store知道了之后把数据给到组件。

    使用Antd美化TodoList页面:
    Antd是React的UI框架。

    yarn add antd
    
    引入样式:
    import 'antd/dist/antd.css'; 
    
    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 (
          
    ({item})} />
    ) } } export default TodoList;

    Antd工具在开发一些后台管理系统的时候用的非常多。

    创建Redux中的Store:
    安装Redux
    
    yarn add redux
    新建文件 src/store/index.js  /仓库(图书管理员)
    
    import { createStore } from 'redux';
    import reducer from './reducer';
    
    const store = createStore(reducer);  // 把笔记本传递给store
    
    export default store;
    --------------------------------------------------------
    
    新建文件 src/store/reducer.js   /记录本
    const defaultState = {
        inputValue:'111',
        list:[1,2]
    } 
    
    export default (state=defaultState,action)=>{  //函数接受两个参数
        return state;
    }
    
    / reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
    / state 存放整个图书馆里所有书籍信息
    / state=defaultState 默认什么信息都不存储
    -----------------------------------------------------------------
    
    src/TodoList.js
    import React, { Component } from 'react';
    import 'antd/dist/antd.css';
    import { Input,Button,List } from 'antd';
    import store from './store';  /简化写法 import store from './store/index.js'; 
    
    class TodoList extends Component {
    
      constructor(props){
        super(props);
        this.state = store.getState();
        console.log(this.state)  /通过store.getState() 获取store中的数据
      }
      render() {
        return (
          
    ({item})} />
    ) } } export default TodoList;
    Action和Reducer的编写:

    安装工具 拓展程序 redux devtools



    安装完成之后,控制台会多出redux选项


    如何使用 redux devtools
    
    src/store/index.js文件
    // 在代码中添加 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    
    import { createStore } from 'redux';
    import reducer from './reducer';
    
    const store = createStore(
        reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );  // 把笔记本传递给store
    
    / window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    / 当做第二个参数传递进去,意思是如果有这个变量就执行这个变量对应的方法
    
    / __REDUX_DEVTOOLS_EXTENSION__这个变量是Redux DevTools的浏览器拓展
    / 意思是如果下面安装了Redux DevTools,那么就在页面上使用这个工具
    
    
    export default store;
    
    src/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.handleBtnClick = this.handleBtnClick.bind(this)
    
        this.handleStoreChange = this.handleStoreChange.bind(this)
        store.subscribe(this.handleStoreChange)  
        //这个组件去订阅Store,只要Store数据发生改变
        //subscribe()里面写一个函数,这个函数就被自动执行
    
        console.log(this.state)  //通过store.getState() 获取store中的数据
    
        
      }
      render() {
        return (
          
    ({item})} />
    ) } handleInputChange(e){ const action = { type:'change_input_value', value: e.target.value } store.dispatch(action) // 把action 传递给Store // Store需要去查小手册(Reducers),把当前数据和action一起传递给小手册(Reducers) // Store会把当前Store存的数据,和接收到的action一起转发给reducers // reducers来告诉Store来做什么 } handleStoreChange(){ this.setState(store.getState()) //当感知到Store数据发生改变,就调用store.getState(),从store里从新取一次数据 //再调用setState 替换当前组件数据 } 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;
    src/store/reducer.js
    
    const defaultState = {
        inputValue:'',
        list:[1,2]
    } 
    
    //为什么需要深拷贝?
    //Redux的限制,reducer可以接受state,但绝不能修改state
    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  //返回给Store,替换Store的老数据
        }
        if(action.type === "add_todo_item"){
            const newState = JSON.parse(JSON.stringify(state))  //深拷贝
            newState.list.push(newState.inputValue);
            newState.inputValue="";
            return newState  //返回给Store,替换Store的老数据
        }
        if(action.type === "delete_todo_item"){
            const newState = JSON.parse(JSON.stringify(state))  //深拷贝
            newState.list.splice(action.index,1);
            return newState  //返回给Store,替换Store的老数据
        }
        return state;
    }
    
    // reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
    // state 存放整个图书馆里所有书籍信息
    // state=defaultState 默认什么信息都不存储
    
    // state指的是上一次存储数据
    // action指的是用户传过来的那句话
    
    ActionTypes的拆分:
    新建src/store/actionTypes.js 文件存放 action Type
    
    export const CHANGE_INPUT_VALUE = 'change_input_value';  //export 导出
    export const ADD_TODO_ITEM = 'add_todo_item'; 
    export const DELETE_TODO_ITEM = 'delete_todo_item'; 
    
    使用:
    src/TodoList.js
    import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './store/actionTypes';
    
    CHANGE_INPUT_VALUE 替换 'change_input_value'
    ADD_TODO_ITEM 替换 'add_todo_item'
    DELETE_TODO_ITEM 替换 'delete_todo_item'
    
    
    src/store/reducer.js
    import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './actionTypes';
    
    CHANGE_INPUT_VALUE 替换 'change_input_value'
    ADD_TODO_ITEM 替换 'add_todo_item'
    DELETE_TODO_ITEM 替换 'delete_todo_item'
    
    
    使用actionCreator 统一创建 action
    新建 src/store/actionCreators.js
    
    import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes';
    
    export const getInputChangeAction = (value)=> ({
        type: CHANGE_INPUT_VALUE,
        value
    })
    
    export const getAddItemAction = ()=> ({
        type: ADD_TODO_ITEM
    })
    
    export const getDeleteItemAction = (index)=> ({
        type: DELETE_TODO_ITEM,
        index
    })
    
    使用:
    src/TodoList.js
    import { getInputChangeAction,getAddItemAction,getDeleteItemAction } from './store/actionCreators'
    
      handleInputChange(e){
        // const action = {
        //   type: CHANGE_INPUT_VALUE,
        //   value: e.target.value
        // }
        const action = getInputChangeAction(e.target.value)
        store.dispatch(action)  
      }
    
      handleBtnClick(){
        // const action = {
        //   type: ADD_TODO_ITEM
        // }
        const action = getAddItemAction()
        store.dispatch(action) 
      }
      
      handleItemDelete(index){
        // const action = {
        //   type: DELETE_TODO_ITEM,
        //   index
        // }
        const action = getDeleteItemAction(index)
        store.dispatch(action) 
      }
    

    好处:

    • 提高代码可维护性
    • 方便前端自动测试化工具
    Redux 知识点复习补充

    Redux 设计和使用的三项原则:

    • Store是唯一的
    整个项目只有一个store 在src/store/index.js
    
    (整个应用中只有一个store公共存储空间)
    
    • 只有store能够改变自己的内容(在reducer中改变了store的数据是不被允许的,在reducer中是复制一个新的对象进行操作)
    • Reducer必须是纯函数
      纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
      state和action都确定的时候,那么return出来的结果永远都是固定的,不纯的函数,比如newState.inputValue = new Date(),因为return newState的值不是固定的
      副作用指 state.inputValue = action.value,这段代码就是有副作用的代码,对接受的参数做了修改
    Redux 核心API
    • createStore 创建store
    • store.dispatch 派发action(action传递给Store)
    • store.getState 获取store数据内容
    • store.subscribe 订阅store的改变

    十二:Redux进阶

    UI组件和容器组件

    UI组件(傻瓜组件)- 只负责页面的一些显示
    容器组件(聪明组件)- 负责页面逻辑处理

    划分组件的原因:把组件的逻辑和渲染放到一个组件中管理维护起来比较困难。或内容比较多。需要对组件进行一个拆分。

    • UI组件负责页面渲染
    • 容器组件负责页面逻辑
    创建UI组件
    src/TodoListUI.js
    
    import React, { Component } from 'React';
    import { Input,Button,List } from 'antd';
    
    class TodoListUI extends Component {
      render() {
        return (
          
    ({this.props.handleItemDelete(index)}}>{item})} />
    ) } } export default TodoListUI;
    创建一个容器组件
    src/TodoList.js
    
    import React, { Component } from 'react';
    import 'antd/dist/antd.css';
    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.handleBtnClick = this.handleBtnClick.bind(this)
        this.handleStoreChange = this.handleStoreChange.bind(this)
        this.handleItemDelete = this.handleItemDelete.bind(this)
        store.subscribe(this.handleStoreChange)
      }
      render() {
        return (
          
        )
      }
    
      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;
    
    无状态组件

    上面的UI组件(傻瓜组件)只有一个render函数,就可以用无状态组件来定义这个组件。

    无状态组件如何定义?
    无状态组件其实就是一个函数。

    src/TodoListUI.js 改写 UI组件改成无状态组件
    import React, { Component } from 'react';
    import { Input,Button,List } from 'antd';
    
    const TodoListUI = (props)=>{ 
      // {/* this.props.inputValue 改成props.inputValue */}
      // {/*不需要this 从传参获取*/} 
      return (
        
    ({props.handleItemDelete(index)}}>{item})} />
    ) } //接收一个props 同时要求返回一个JSX // class TodoListUI extends Component { // render() { // return ( //
    //
    // // //
    // ({this.props.handleItemDelete(index)}}>{item})} // /> //
    // ) // } // } export default TodoListUI;

    当一个普通组件只有render函数,可以使用无状态组件替换掉普通组件
    优点:无状态组件性能比较高。
    无状态组件就是一个函数,而class类生成对象还会有一些生命周期函数,执行起来需要执行render和生命周期函数,执行的东西远比函数执行的多。
    注:虽然理论上UI组件只做页面渲染,有时候简单做一些逻辑也是可以的。

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

    componentDidMount 中获取数据


    实现从/todolist.json借口获取数据并绑定
    todolist.json
    
    ["学习React","学习英语","学习Vue"]
    
    src/index.js
    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import TodoList from './TodoList';
    
    ReactDOM.render(, document.getElementById('root'));
    
    src/TodoList.js
    
    import React, { Component } from 'react';
    import 'antd/dist/antd.css';
    import store from './store';
    import { getInputChangeAction, getAddItemAction, getDeleteItemAction,initListAction } from './store/actionCreators'
    import TodoListUI from './TodoListUI';
    import axios from 'axios';  //引入第三方请求组件
    
    class TodoList extends Component {
    
      constructor(props) {
        super(props);
        this.state = store.getState();
        this.handleInputChange = this.handleInputChange.bind(this)
        this.handleBtnClick = this.handleBtnClick.bind(this)
    
        this.handleStoreChange = this.handleStoreChange.bind(this)
        this.handleItemDelete = this.handleItemDelete.bind(this)
        store.subscribe(this.handleStoreChange)
        //这个组件去订阅Store,只要Store数据发生改变
        //subscribe()里面写一个函数,这个函数就被自动执行
    
        console.log(this.state)  //通过store.getState() 获取store中的数据
    
    
      }
      render() {
        return (
          
        )
      }
    
      componentDidMount(){
        axios.get('/todolist.json').then((res)=>{  //成功函数
          const data = res.data;  //接口获取到数据,改变store中的数据
          //store有个方法 dispatch 先要创建一个action
          const action = initListAction(data)  
          store.dispatch(action)  
          //把action传递给store ,store拿到之前的数据连同action一同传递给reducer
          console.log(data)
        }).catch((fail)=>{  //失败函数
    
        })
      }
    
      handleInputChange(e) {
        // const action = {
        //   type: CHANGE_INPUT_VALUE,
        //   value: e.target.value
        // }
        const action = getInputChangeAction(e.target.value)
        store.dispatch(action)
        // 把action 传递给Store  
        // Store需要去查小手册(Reducers),把当前数据和action一起传递给小手册(Reducers)
        // Store会把当前Store存的数据,和接收到的action一起转发给reducers
        // reducers来告诉Store来做什么
      }
    
      handleStoreChange() {
        this.setState(store.getState())
        //当感知到Store数据发生改变,就调用store.getState(),从store里从新取一次数据
        //再调用setState 替换当前组件数据
      }
    
      handleBtnClick() {
        // const action = {
        //   type: ADD_TODO_ITEM
        // }
        const action = getAddItemAction()
        store.dispatch(action)
      }
    
      handleItemDelete(index) {
        // const action = {
        //   type: DELETE_TODO_ITEM,
        //   index
        // }
        const action = getDeleteItemAction(index)
        store.dispatch(action)
      }
    }
    
    export default TodoList;
    
    src/TodoListUI.js
    
    import React, { Component } from 'react';
    import { Input,Button,List } from 'antd';
    
    const TodoListUI = (props)=>{ 
      // {/* this.props.inputValue 改成props.inputValue */}
      // {/*不需要this 从传参获取*/} 
      return (
        
    ({props.handleItemDelete(index)}}>{item})} />
    ) } //接收一个props 同时要求返回一个JSX // class TodoListUI extends Component { // render() { // return ( //
    //
    // // //
    // ({this.props.handleItemDelete(index)}}>{item})} // /> //
    // ) // } // } export default TodoListUI;
    src/store/actionCreators.js
    
    import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
    
    export const getInputChangeAction = (value)=> ({
        type: CHANGE_INPUT_VALUE,
        value
    })
    
    export const getAddItemAction = ()=> ({
        type: ADD_TODO_ITEM
    })
    
    export const getDeleteItemAction = (index)=> ({
        type: DELETE_TODO_ITEM,
        index
    })
    
    export const initListAction = (data) => ({  //接受list.json接口数据
        type: INIT_LIST_ACTION,
        data
    })
    
    src/store/actionTypes.js
    
    export const CHANGE_INPUT_VALUE = 'change_input_value';  //export 导出
    export const ADD_TODO_ITEM = 'add_todo_item'; 
    export const DELETE_TODO_ITEM = 'delete_todo_item'; 
    export const INIT_LIST_ACTION = 'init_list_action';   //接受list.json 定义常量
    
    src/store/index.js
    
    import { createStore } from 'redux';
    import reducer from './reducer';
    
    const store = createStore(
        reducer,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );  // 把笔记本传递给store
    
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    // 当做第二个参数传递进去,意思是如果有这个变量就执行这个变量对应的方法
    
    // __REDUX_DEVTOOLS_EXTENSION__这个变量是Redux DevTools的浏览器拓展
    // 意思是如果下面安装了Redux DevTools,那么就在页面上使用这个工具
    
    export default store;
    
    src/store/reducer.js
    
    import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION} from './actionTypes';
    
    const defaultState = {
        inputValue:'',
        list:[]
    } 
    
    //为什么需要深拷贝?
    //Redux的限制,reducer可以接受state,但绝不能修改state
    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  //返回给Store,替换Store的老数据
        }
        if(action.type === ADD_TODO_ITEM){
            const newState = JSON.parse(JSON.stringify(state))  //深拷贝
            newState.list.push(newState.inputValue);
            newState.inputValue="";
            return newState  //返回给Store,替换Store的老数据
        }
        if(action.type === DELETE_TODO_ITEM){
            const newState = JSON.parse(JSON.stringify(state))  //深拷贝
            newState.list.splice(action.index,1);
            return newState  //返回给Store,替换Store的老数据
        }
        if(action.type === INIT_LIST_ACTION){
            const newState = JSON.parse(JSON.stringify(state))  //深拷贝
            newState.list = action.data
            return newState  //返回给Store,替换Store的老数据
        }
        return state;
    }
    
    // reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
    // state 存放整个图书馆里所有书籍信息
    // state=defaultState 默认什么信息都不存储
    
    // state指的是上一次存储数据
    // action指的是用户传过来的那句话
    
    使用Redux-thunk中间件进行ajax请求发送

    ajax异步或者复杂逻辑放在组件里实现时,组件会显得过于臃肿,希望移除到其他地方统一管理。
    Redux-thunk中间件可以把异步请求或复杂逻辑放到action中处理
    Redux-thunk是Redux的一个中间件。
    在React中Redux-thunk这个中间件用的非常多。
    Redux-thunk:github地址

    yarn add redux-thunk   //安装
    
    使用  在store中
    import { createStore, applyMiddleware } from 'redux';   要引入 applyMiddleware 
    import thunk from 'redux-thunk';  //引入redux-thunk模块
    import rootReducer from './reducers/index';  
    
    // Note: this API requires redux@>=3.1.0
    const store = createStore(
      reducer,
      applyMiddleware(thunk)  //第二个参数引入
    );
    
    意思是 创建Store时,使用reducer构建初始数据,然后创建store时,store会使用一个中间件thunk
    

    我们说的中间件,是redux的中间件,不是react的

    引入多个中间件
    引入thunk 以及 redux-devtools
    githup 搜索 redux-devtools 查到文档
    
    const composeEnhancers =
      typeof window === 'object' &&
      window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?   
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
          // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
        }) : compose;
    
    const enhancer = composeEnhancers(
      applyMiddleware(...middleware),
      // other store enhancers if any
    );
    
    使用
    
    通过这种编码,既支持window下的devtools,同时也引入了Redux-thunk
    
    src/store/index.js
    
    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;
      // 如果window的__REDUX_DEVTOOLS_EXTENSION_COMPOSE__存在,就去调用一下这个方法
      // 否则就等于一个compose函数,compose函数需要从redux中引入进来
    
    const enhancer = composeEnhancers(  //让上面存储的composeEnhancers,执行一下
        // 顺便把thunk通过applyMiddleware执行一下,传递进去
        applyMiddleware(thunk),  
    );
    
    const store = createStore(reducer, enhancer);
    
    // 实际上 __REDUX_DEVTOOLS_EXTENSION__ 也是redux的中间件
    // 我们说的中间件,是redux的中间件
    export default store;
    
    

    上面代码只做了一件事,安装了thunk,然后在store创建的时候,使用了thunk,代码从GitHub对应的指南里面拷贝过来的
    配置好了之后,围绕redux-thunk来编写代码

    src/TodoList.js
    把异步操作的代码从组件中移除,移除到action里面
    import { getInputChangeAction, getAddItemAction, getDeleteItemAction,getTodoList } from './store/actionCreators'
    // 引入getTodoList
    
      componentDidMount(){
        const action = getTodoList();
        store.dispatch(action)  
        /dispatch了一个函数,实际上store只能接受一个对象
        /store发现是一个函数就会干一件事,帮助自动执行一下对应的函数
        /action对应的函数是actionCreators.js 中getTodoList 的 return 函数
        // axios.get('/todolist.json').then((res)=>{
        //   const data = res.data; 
        //   const action = initListAction(data)  
        //   store.dispatch(action)  
        // }).catch((fail)=>{})
        // 把异步操作的代码从组件中移除,移除到action里面
      }
    

    当使用了Readux-thunk之后 action可以是一个函数了(只有用了thunk之后,才能是函数)

    getTodoList 的 rerun 函数再去取json的数据,获取数据,去改变store中的数据
    只要改变store中的数据,又要走redux的流程
    去调用之前写的initListAction,去创建action
    想去调用store.dispatch方法
    
    store.dispatch方法如何获取到?
    返回的函数自动就会接收dispatch方法
    只要调用dispatch 把 action派发出去就行
    这个action 实际是一个对象
    store会判断action是一个对象,直接就接收这个对象,改变原始状态
    
    src/store/actionCreators.js
    
    import axios from 'axios';  //引入第三方请求组件
    
    /* ------------未变化------------- */
    import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
    
    export const getInputChangeAction = (value)=> ({
        type: CHANGE_INPUT_VALUE,
        value
    })
    
    export const getAddItemAction = ()=> ({
        type: ADD_TODO_ITEM
    })
    
    export const getDeleteItemAction = (index)=> ({
        type: DELETE_TODO_ITEM,
        index
    })
    
    export const initListAction = (data) => ({  //接受list.json接口数据
        type: INIT_LIST_ACTION,
        data
    })
    /* ------------未变化------------- */
    
    // 当使用了Readux-thunk之后 action可以是一个函数了
    
    export const getTodoList = () => {  //调用getTodoList生成内容是函数的action时
        return (dispatch) => {  // 这个函数能够接收到dispatch方法
            axios.get('/todolist.json').then((res) => {
              const data = res.data; 
              const action = initListAction(data)
              dispatch(action) //直接调用dispatch方法即可
            }).catch((fail)=>{})
        }
        // 正常来说,return应该是一个对象 但使用了Readux-thunk之后,return结果可以是一个函数
        // 在这个函数里面 可以做异步操作
    }
    

    疑问:把ajax放到componentDidMount中不是挺好的,为什么要这么麻烦
    如果把异步函数放到组件的生命周期函数中来做,这个生命周期函数有可能变得越来越复杂,越来越多,这个组件会变得越来越大,所以建议把复杂的业务逻辑或异步函数拆分到一个地方去管理,现在借助Redux-thunk就可以放到actionCreators中去管理了
    放到这里管理又带来一个额外好处,做自动化测试的时候,去测试getTodoList方法会非常的简单,比测试组件的生命周期函数要简单的多。
    反复写5遍中间件的使用流程,捋清流程

    你可能感兴趣的:(react入门学习实现一个TodoList)