React基础学习笔记

1章:React入门

1.1. React简介

1.1.1. 官网

  1. 英文官网: https://reactjs.org/

  2. 中文官网: https://react.docschina.org/

1.1.2. 介绍描述

  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)

  2. 由Facebook开源

1.1.3. React的特点

  1. 声明式编码

  2. 组件化编码

  3. React Native 编写原生应用

  4. 高效(优秀的Diffing算法)

1.1.4. React高效的原因

  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。

  2. DOM Diffing算法, 最小化页面重绘。

1.2. React的基本使用

1.2.1. 效果

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello_react</title>
</head>
<body>
    <!-- 准备好一个“容器” -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel"> /* 此处一定要写babel */
    //1.创建虚拟DOM
    const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
    //2.渲染虚拟DOM到页面
    //ReactDOM.render(虚拟DOM, 容器)
    ReactDOM.render(VDOM, document.getElementById('test'))
    </script>
</body>
</html>

1.2.2. 相关js库

  1. react.js:React核心库。

  2. react-dom.js:提供操作DOM的react扩展库。

  3. babel.min.js:解析JSX语法代码转为JS代码的库。

1.2.3. 创建虚拟DOM的两种方式

在这里插入图片描述

  1. 纯JS方式(一般不用)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>2_使用js创建虚拟DOM</title>
    </head>
    <body>
        <!-- 准备好一个“容器” -->
        <div id="test"></div>
    
        <!-- 引入react核心库 -->
        <script type="text/javascript" src="../js/react.development.js"></script>
        <!-- 引入react-dom,用于支持react操作DOM -->
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
    
        <script type="text/javascript">
          //1.创建虚拟DOM React.createElement(标签名, 标签属性, 标签内容)
          const VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span', {}, 'Hello,React'))
          //2.渲染虚拟DOM到页面
          ReactDOM.render(VDOM, document.getElementById('test'))
        </script>
    </body>
    </html>
    
  2. JSX方式

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>1_使用jsx创建虚拟DOM</title>
    </head>
    <body>
        <!-- 准备好一个“容器” -->
        <div id="test"></div>
    
        <!-- 引入react核心库 -->
        <script type="text/javascript" src="../js/react.development.js"></script>
        <!-- 引入react-dom,用于支持react操作DOM -->
        <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <!-- 引入babel,用于将jsx转为js -->
        <script type="text/javascript" src="../js/babel.min.js"></script>
    
        <script type="text/babel"> /* 此处一定要写babel */
        //1.创建虚拟DOM
        const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
          <h1 id="title">
            <span>Hello,React</span>
          </h1>
        )
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'))
        </script>
    </body>
    </html>
    

1.2.4. 虚拟DOM与真实DOM

  1. React提供了一些API来创建一种 “特别” 的一般js对象

    const VDOM = React.createElement('xx',{id:'xx'},'xx')
    

    上面创建的就是一个简单的虚拟DOM对象

  2. 虚拟DOM对象最终都会被React转换为真实的DOM

  3. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>3_虚拟DOM与真实DOM</title>
</head>
<body>
    <!-- 准备好一个“容器” -->
    <div id="test"></div>
    <div id="demo"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel"> /* 此处一定要写babel */
    //1.创建虚拟DOM
    const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
      <h1 id="title">
        <span>Hello,React</span>
      </h1>
    )
    //2.渲染虚拟DOM到页面
    ReactDOM.render(VDOM, document.getElementById('test'))

    const TDOM = document.getElementById('demo')
    console.log('虚拟DOM', VDOM);
    console.log('真实DOM', TDOM);
    debugger;
    // console.log(typeof VDOM);
    // console.log(VDOM instanceof Object);
    /*关于虚拟DOM:
        1.本质是Object类型的对象(一般对象)
        2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
        3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
     */
    </script>
</body>
</html>

1.3. React JSX

1.3.1. 效果

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>jsx语法规则</title>
    <style>
        .title {
            background-color: orange;
            width: 200px;
        }
    </style>
</head>
<body>
    <!-- 准备好一个“容器” -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
      const myId = 'aTgUiGu'
      const myData = 'HeLlo,rEaCt'

      //1.创建虚拟DOM
      const VDOM = (
        <div>
          <h2 className="title" id={myId.toLowerCase()}>
            <span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
          </h2>
          <h2 className="title" id={myId.toUpperCase()}>
            <span style={{color: 'white', fontSize: '29px'}}>{myData.toLowerCase()}</span>
          </h2>
          <input type="text"/>
        </div>
      )
      //2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))

      /* jsx语法规则:
          1.定义虚拟DOM时,不要写引号。
          2.标签中混入JS表达式时要用{}。
          3.样式的类名指定不要用class,要用className。
          4.内联样式,要用style={{key:value}}的形式去写。
          5.只有一个根标签
          6.标签必须闭合
          7.标签首字母
              (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
              (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
       */
    </script>
</body>
</html>

1.3.2. JSX

  1. 全称: JavaScript XML

  2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是

    React.createElement(component, props, ...children)
    

    方法的语法糖

  3. 作用: 用来简化创建虚拟DOM

    • 写法:

      var ele = <h1>Hello JSX!</h1>
      
    • 注意1:它不是字符串, 也不是HTML/XML标签

    • 注意2:它最终产生的就是一个JS对象

  4. 标签名任意: HTML标签或其它标签

  5. 标签属性任意: HTML标签属性或其它

  6. 基本语法规则

    • 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
    • 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
  7. babel.js的作用

    • 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
    • 只要用了JSX,都要加上type=“text/babel”, 声明需要babel来处理
  8. JSX语法规则

    • 定义虚拟DOM时,不要写引号。
    • 标签中混入JS表达式时要用{}。
    • 样式的类名指定不要用class,要用className。
    • 内联样式,要用style={{key:value}}的形式去写。
    • 只有一个根标签
    • 标签必须闭合
    • 标签首字母
      • 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
      • 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

1.3.3. 渲染虚拟DOM(元素)

  1. 语法:

    ReactDOM.render(virtualDOM, containerDOM)
    
  2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

  3. 参数说明

    • 参数一: 纯js或jsx创建的虚拟dom对象
    • 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

1.3.4. JSX练习

需求: 动态展示如下列表

在这里插入图片描述

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>jsx小练习</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      /*      一定注意区分:【js语句(代码)】与【js表达式】        1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方            下面这些都是表达式:            (1). a            (2). a+b            (3). demo(1)            (4). arr.map()            (5). function test () {}        2.语句(代码):            下面这些都是语句(代码):            (1).if(){}            (2).for(){}            (3).switch(){case:xxxx}   */      //模拟一些数据      const data = ['Angular', 'React', 'Vue']      //1.创建虚拟DOM      const VDOM = (        <div>          <h1>前端js框架列表</h1>          <ul>            {              data.map((item, index) => {                return <li key={index}>{item}</li>              })            }          </ul>        </div>      )      //2.渲染虚拟DOM到页面      ReactDOM.render(VDOM, document.getElementById('test'))    </script></body></html>

1.4. 模块与组件、模块化与组件化的理解

1.4.1. 模块

  1. 理解:向外提供特定功能的js程序, 一般就是一个js文件

  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。

  3. 作用:复用js, 简化js的编写, 提高js运行效率

1.4.2. 组件

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)

  2. 为什么要用组件: 一个界面的功能更复杂

  3. 作用:复用编码, 简化项目编码, 提高运行效率

1.4.3. 模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

1.4.4. 组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

在这里插入图片描述

第2章:React面向组件编程

2.1. 基本理解和使用

2.1.1. 使用React开发者工具调试

在这里插入图片描述

2.1.2. 效果

函数式组件:

在这里插入图片描述

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>1_函数式组件</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //1.创建函数式组件      function MyComponent() {        console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式        return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>      }      //2.渲染组件到页面      ReactDOM.render(<MyComponent/>, document.getElementById('test'))      /*执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?        1.React解析组件标签,找到了MyComponent组件。        2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。      */    </script></body></html>

类式组件:

在这里插入图片描述

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>2_类式组件</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //1.创建类式组件      class MyComponent extends React.Component {        render() {          //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。          //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。          console.log('render中的this:', this);          return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>        }      }      //2.渲染组件到页面      ReactDOM.render(<MyComponent/>, document.getElementById('test'))      /* 执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?        1.React解析组件标签,找到了MyComponent组件。        2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。        3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。      */    </script></body></html>

2.1.3. 注意

  1. 组件名必须首字母大写

  2. 虚拟DOM元素只能有一个根元素

  3. 虚拟DOM元素必须有结束标签

2.1.4. 渲染类组件标签的基本流程

  1. React内部会创建组件实例对象

  2. 调用render()得到虚拟DOM, 并解析为真实DOM

  3. 插入到指定的页面元素内部

2.2. 组件三大核心属性1: state

2.2.1. 效果

需求:定义一个展示天气信息的组件

  1. 默认展示天气炎热或凉爽
  2. 点击文字切换天气

在这里插入图片描述

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>state</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //1.创建组件      class Weather extends React.Component {        //构造器调用几次? ———— 1次        constructor(props) {          console.log('constructor');          super(props)          //初始化状态          this.state = {isHot: false, wind: '微风'}          //解决changeWeather中this指向问题          this.changeWeather = this.changeWeather.bind(this)        }        //render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数        render() {          console.log('render');          //读取状态          const {isHot, wind} = this.state          return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>        }        //changeWeather调用几次? ———— 点几次调几次        changeWeather() {          //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用          //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用          //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined          console.log('changeWeather');          //获取原来的isHot值          const isHot = this.state.isHot          //严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。          this.setState({isHot: !isHot})          console.log(this);          //严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!          //this.state.isHot = !isHot //这是错误的写法        }      }      //2.渲染组件到页面      ReactDOM.render(<Weather/>, document.getElementById('test'))    </script></body></html>

state简写

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>state简写方式</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //1.创建组件      class Weather extends React.Component {        //初始化状态        state = {isHot: false, wind: '微风'}        render() {          const {isHot, wind} = this.state          return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>        }        //自定义方法————要用赋值语句的形式+箭头函数        changeWeather = () => {          const isHot = this.state.isHot          this.setState({isHot: !isHot})        }      }      //2.渲染组件到页面      ReactDOM.render(<Weather/>, document.getElementById('test'))    </script></body></html>

2.2.2. 理解

  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)

  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.2.3. 强烈注意

  1. 组件中render方法中的this为组件实例对象
  2. 组件自定义的方法中this为undefined,如何解决?
    • 强制绑定this: 通过函数对象的bind()
    • 箭头函数
  3. 状态数据,不能直接修改或更新
    • 必须通过setState进行更新,且更新是一种合并,不是替换。

2.3. 组件三大核心属性2: props

2.3.1. 效果

需求:自定义用来显示一个人员信息的组件

  1. 姓名必须指定,且为字符串类型;
  2. 性别为字符串类型,如果性别没有指定,默认为男
  3. 年龄为字符串类型,且为数字类型,默认值为18

在这里插入图片描述

  • props基本使用

    <!DOCTYPE html><html lang="en"><head>	<meta charset="UTF-8">	<title>props基本使用</title></head><body>	<!-- 准备好一个“容器” -->	<div id="test1"></div>	<div id="test2"></div>	<div id="test3"></div>		<!-- 引入react核心库 -->	<script type="text/javascript" src="../js/react.development.js"></script>	<!-- 引入react-dom,用于支持react操作DOM -->	<script type="text/javascript" src="../js/react-dom.development.js"></script>	<!-- 引入babel,用于将jsx转为js -->	<script type="text/javascript" src="../js/babel.min.js"></script>	<script type="text/babel">		//创建组件		class Person extends React.Component{			render(){				// console.log(this);				const {name,age,sex} = this.props				return (					<ul>						<li>姓名:{name}</li>						<li>性别:{sex}</li>						<li>年龄:{age+1}</li>					</ul>				)			}		}		//渲染组件到页面		ReactDOM.render(<Person name="jerry" age={19}  sex="男"/>,document.getElementById('test1'))		ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))		const p = {name:'老刘',age:18,sex:'女'}		// console.log('@',...p);		// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))		ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))	</script></body></html>
    
  • 对props进行限制

    <!DOCTYPE html><html lang="en"><head>	<meta charset="UTF-8">	<title>对props进行限制</title></head><body>	<!-- 准备好一个“容器” -->	<div id="test1"></div>	<div id="test2"></div>	<div id="test3"></div>		<!-- 引入react核心库 -->	<script type="text/javascript" src="../js/react.development.js"></script>	<!-- 引入react-dom,用于支持react操作DOM -->	<script type="text/javascript" src="../js/react-dom.development.js"></script>	<!-- 引入babel,用于将jsx转为js -->	<script type="text/javascript" src="../js/babel.min.js"></script>	<!-- 引入prop-types,用于对组件标签属性进行限制 -->	<script type="text/javascript" src="../js/prop-types.js"></script>	<script type="text/babel">		//创建组件		class Person extends React.Component{			render(){				// console.log(this);				const {name,age,sex} = this.props				//props是只读的				//this.props.name = 'jack' //此行代码会报错,因为props是只读的				return (					<ul>						<li>姓名:{name}</li>						<li>性别:{sex}</li>						<li>年龄:{age+1}</li>					</ul>				)			}		}		//对标签属性进行类型、必要性的限制		Person.propTypes = {			name:PropTypes.string.isRequired, //限制name必传,且为字符串			sex:PropTypes.string,//限制sex为字符串			age:PropTypes.number,//限制age为数值			speak:PropTypes.func,//限制speak为函数		}		//指定默认标签属性值		Person.defaultProps = {			sex:'男',//sex默认值为男			age:18 //age默认值为18		}		//渲染组件到页面		ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))		ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))		const p = {name:'老刘',age:18,sex:'女'}		// console.log('@',...p);		// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))		ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))		function speak(){			console.log('我说话了');		}	</script></body></html>
    
  • props的简写方式

    <!DOCTYPE html><html lang="en"><head>	<meta charset="UTF-8">	<title>对props进行限制</title></head><body>	<!-- 准备好一个“容器” -->	<div id="test1"></div>	<div id="test2"></div>	<div id="test3"></div>		<!-- 引入react核心库 -->	<script type="text/javascript" src="../js/react.development.js"></script>	<!-- 引入react-dom,用于支持react操作DOM -->	<script type="text/javascript" src="../js/react-dom.development.js"></script>	<!-- 引入babel,用于将jsx转为js -->	<script type="text/javascript" src="../js/babel.min.js"></script>	<!-- 引入prop-types,用于对组件标签属性进行限制 -->	<script type="text/javascript" src="../js/prop-types.js"></script>	<script type="text/babel">		//创建组件		class Person extends React.Component{			constructor(props){				//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props				// console.log(props);				super(props)				console.log('constructor',this.props);			}			//对标签属性进行类型、必要性的限制			static propTypes = {				name:PropTypes.string.isRequired, //限制name必传,且为字符串				sex:PropTypes.string,//限制sex为字符串				age:PropTypes.number,//限制age为数值			}			//指定默认标签属性值			static defaultProps = {				sex:'男',//sex默认值为男				age:18 //age默认值为18			}						render(){				// console.log(this);				const {name,age,sex} = this.props				//props是只读的				//this.props.name = 'jack' //此行代码会报错,因为props是只读的				return (					<ul>						<li>姓名:{name}</li>						<li>性别:{sex}</li>						<li>年龄:{age+1}</li>					</ul>				)			}		}		//渲染组件到页面		ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))	</script></body></html>
    
  • 函数组件使用props

    <!DOCTYPE html><html lang="en"><head>	<meta charset="UTF-8">	<title>对props进行限制</title></head><body>	<!-- 准备好一个“容器” -->	<div id="test1"></div>	<div id="test2"></div>	<div id="test3"></div>		<!-- 引入react核心库 -->	<script type="text/javascript" src="../js/react.development.js"></script>	<!-- 引入react-dom,用于支持react操作DOM -->	<script type="text/javascript" src="../js/react-dom.development.js"></script>	<!-- 引入babel,用于将jsx转为js -->	<script type="text/javascript" src="../js/babel.min.js"></script>	<!-- 引入prop-types,用于对组件标签属性进行限制 -->	<script type="text/javascript" src="../js/prop-types.js"></script>	<script type="text/babel">		//创建组件		function Person (props){			const {name,age,sex} = props			return (					<ul>						<li>姓名:{name}</li>						<li>性别:{sex}</li>						<li>年龄:{age}</li>					</ul>				)		}		Person.propTypes = {			name:PropTypes.string.isRequired, //限制name必传,且为字符串			sex:PropTypes.string,//限制sex为字符串			age:PropTypes.number,//限制age为数值		}		//指定默认标签属性值		Person.defaultProps = {			sex:'男',//sex默认值为男			age:18 //age默认值为18		}		//渲染组件到页面		ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))	</script></body></html>
    

2.3.2. 理解

  1. 每个组件对象都会有props(properties的简写)属性

  2. 组件标签的所有属性都保存在props中

2.3.3. 作用

  1. 通过标签属性从组件外向组件内传递变化的数据

  2. 注意: 组件内部不要修改props数据

2.3.4. 编码操作

  1. 内部读取某个属性值

    this.props.name
    
  2. 对props中的属性值进行类型限制和必要性限制

    • 第一种方式(React v15.5 开始已弃用):

      Person.propTypes = { name: React.PropTypes.string.isRequired, age: React.PropTypes.number}
      
    • 第二种方式(新):使用prop-types库进限制(需要引入prop-types库)

      Person.propTypes = {  name: PropTypes.string.isRequired,  age: PropTypes.number. }
      
  3. 扩展属性: 将对象的所有属性通过props传递

    <Person {...person}/>
    
    1. 默认属性值:

      Person.defaultProps = {  age: 18,  sex:'男'}
      
    2. 组件类的构造函数

      constructor(props){  super(props)  console.log(props)//打印所有属性}
      

2.4. 组件三大核心属性3: refs与事件处理

2.4.1. 效果

需求:自定义组件, 功能说明如下:

  1. 点击按钮, 提示第一个输入框中的值
  2. 当第2个输入框失去焦点时, 提示这个输入框中的值

效果如下:

在这里插入图片描述

  • 字符串形式的ref

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>1_字符串形式的ref</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Demo extends React.Component {        //展示左侧输入框的数据        showData = () => {          const {input1} = this.refs          alert(input1.value)        }        //展示右侧输入框的数据        showData2 = () => {          const {input2} = this.refs          alert(input2.value)        }        render() {          return (            <div>              <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;              <button onClick={this.showData}>点我提示左侧的数据</button>              &nbsp;              <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>            </div>          )        }      }      //渲染组件到页面      ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))    </script></body></html>
    
  • 回调函数形式的ref

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>1_字符串形式的ref</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Demo extends React.Component {        //展示左侧输入框的数据        showData = () => {          const {input1} = this          alert(input1.value)        }        //展示右侧输入框的数据        showData2 = () => {          const {input2} = this          alert(input2.value)        }        render() {          return (            <div>              <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>&nbsp;              <button onClick={this.showData}>点我提示左侧的数据</button>              &nbsp;              <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>&nbsp;            </div>          )        }      }      //渲染组件到页面      ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))    </script></body></html>
    
  • 回调ref中回调执行次数的问题

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>3_回调ref中回调执行次数的问题</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Demo extends React.Component {        state = {isHot: false}        showInfo = () => {          const {input1} = this          alert(input1.value)        }        changeWeather = () => {          //获取原来的状态          const {isHot} = this.state          //更新状态          this.setState({isHot: !isHot})        }        saveInput = (c) => {          this.input1 = c;          console.log('@', c);        }        render() {          const {isHot} = this.state          return (            <div>              <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>              {/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}              <input ref={this.saveInput} type="text"/><br/><br/>              <button onClick={this.showInfo}>点我提示输入的数据</button>              <button onClick={this.changeWeather}>点我切换天气</button>            </div>          )        }      }      //渲染组件到页面      ReactDOM.render(<Demo/>, document.getElementById('test'))    </script></body></html>
    
  • createRef的使用

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>4_createRef</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Demo extends React.Component {        /*            React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的         */        myRef = React.createRef()        myRef2 = React.createRef()        //展示左侧输入框的数据        showData = () => {          alert(this.myRef.current.value);        }        //展示右侧输入框的数据        showData2 = () => {          alert(this.myRef2.current.value);        }        render() {          return (            <div>              <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;              <button onClick={this.showData}>点我提示左侧的数据</button>              &nbsp;              <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;            </div>          )        }      }      //渲染组件到页面      ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))    </script></body></html>
    

2.4.2. 理解

组件内的标签可以定义ref属性来标识自己

2.4.3. 编码

  1. 字符串形式的ref

    <input ref="input1"/>
    
  2. 回调形式的ref

    <input ref={(c)=>{this.input1 = c}}
    
  3. createRef创建ref容器

    myRef = React.createRef() <input ref={this.myRef}/>
    

2.4.4. 事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  2. 通过event.target得到发生事件的DOM元素对象
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>事件处理</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Demo extends React.Component {        /*         (1).通过onXxx属性指定事件处理函数(注意大小写)        	a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性        	b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效        (2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref         */        //创建ref容器        myRef = React.createRef()        myRef2 = React.createRef()        //展示左侧输入框的数据        showData = (event) => {          console.log(event.target);          alert(this.myRef.current.value);        }        //展示右侧输入框的数据        showData2 = (event) => {          alert(event.target.value);        }        render() {          return (            <div>              <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;              <button onClick={this.showData}>点我提示左侧的数据</button>              &nbsp;              <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;            </div>          )        }      }      //渲染组件到页面      ReactDOM.render(<Demo a="1" b="2"/>, document.getElementById('test'))    </script></body></html>

2.5. 收集表单数据

2.5.1. 效果

需求: 定义一个包含表单的组件

输入用户名密码后, 点击登录提示输入信息

在这里插入图片描述

  • 非受控组件

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>1_非受控组件</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Login extends React.Component {        handleSubmit = (event) => {          event.preventDefault() //阻止表单提交          const {username, password} = this          alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)        }        render() {          return (            <form onSubmit={this.handleSubmit}>              用户名:<input ref={c => this.username = c} type="text" name="username"/>              密码:<input ref={c => this.password = c} type="password" name="password"/>              <button>登录</button>            </form>          )        }      }      //渲染组件      ReactDOM.render(<Login/>, document.getElementById('test'))    </script></body></html>
    
  • 受控组件

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>2_受控组件</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>	    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Login extends React.Component {        //初始化状态        state = {          username: '', //用户名          password: '' //密码        }        //保存用户名到状态中        saveUsername = (event) => {          this.setState({username: event.target.value})        }        //保存密码到状态中        savePassword = (event) => {          this.setState({password: event.target.value})        }        //表单提交的回调        handleSubmit = (event) => {          event.preventDefault() //阻止表单提交          const {username, password} = this.state          alert(`你输入的用户名是:${username},你输入的密码是:${password}`)        }        render() {          return (            <form onSubmit={this.handleSubmit}>              用户名:<input onChange={this.saveUsername} type="text" name="username"/>              密码:<input onChange={this.savePassword} type="password" name="password"/>              <button>登录</button>            </form>          )        }      }      //渲染组件      ReactDOM.render(<Login/>, document.getElementById('test'))    </script></body></html>
    

2.5.2. 理解

包含表单的组件分类

  1. 受控组件
  2. 非受控组件

2.5.3 高阶函数-函数柯里化

  1. 高阶函数——函数柯里化

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>高阶函数_函数柯里化</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //#region      /*      高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。      	1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。      	2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。      		常见的高阶函数有:Promise、setTimeout、arr.map()等等      函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。      function sum(a){      	return(b)=>{      		return (c)=>{      			return a+b+c            }        }      }*/      //#endregion      //创建组件      class Login extends React.Component {        //初始化状态        state = {          username: '', //用户名          password: '' //密码        }        //保存表单数据到状态中        saveFormData = (dataType) => {          return (event) => {            this.setState({[dataType]: event.target.value})          }        }        //表单提交的回调        handleSubmit = (event) => {          event.preventDefault() //阻止表单提交          const {username, password} = this.state          alert(`你输入的用户名是:${username},你输入的密码是:${password}`)        }        render() {          return (            <form onSubmit={this.handleSubmit}>              用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>              密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>              <button>登录</button>            </form>          )        }      }      //渲染组件      ReactDOM.render(<Login/>, document.getElementById('test'))    </script></body></html>
    
  2. 不用函数柯里化的实现

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>2_不用函数柯里化的实现</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Login extends React.Component {        //初始化状态        state = {          username: '', //用户名          password: '' //密码        }        //保存表单数据到状态中        saveFormData = (dataType, event) => {          this.setState({[dataType]: event.target.value})        }        //表单提交的回调        handleSubmit = (event) => {          event.preventDefault() //阻止表单提交          const {username, password} = this.state          alert(`你输入的用户名是:${username},你输入的密码是:${password}`)        }        render() {          return (            <form onSubmit={this.handleSubmit}>              用户名:<input onChange={event => this.saveFormData('username', event)} type="text" name="username"/>              密码:<input onChange={event => this.saveFormData('password', event)} type="password" name="password"/>              <button>登录</button>            </form>          )        }      }      //渲染组件      ReactDOM.render(<Login/>, document.getElementById('test'))    </script></body></html>
    

2.6. 组件的生命周期

2.6.1. 效果

需求:定义组件实现以下功能:

1. 让指定的文本做显示 / 隐藏的渐变动画
2. 从完全可见,到彻底消失,耗时2S
3. 点击“不活了”按钮从界面中卸载组件

在这里插入图片描述

  • 引出生命周期

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>1_引出生命周期</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      //创建组件      //生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子      class Life extends React.Component {        state = {opacity: 1}        death = () => {          //卸载组件          ReactDOM.unmountComponentAtNode(document.getElementById('test'))        }        //组件挂载完毕        componentDidMount() {          console.log('componentDidMount');          this.timer = setInterval(() => {            //获取原状态            let {opacity} = this.state            //减小0.1            opacity -= 0.1            if (opacity <= 0) opacity = 1            //设置新的透明度            this.setState({opacity})          }, 200);        }        //组件将要卸载        componentWillUnmount() {          //清除定时器          clearInterval(this.timer)        }        //初始化渲染、状态更新之后        render() {          console.log('render');          return (            <div>              <h2 style={{opacity: this.state.opacity}}>React学不会怎么办?</h2>              <button onClick={this.death}>不活了</button>            </div>          )        }      }      //渲染组件      ReactDOM.render(<Life/>, document.getElementById('test'))    </script></body></html>
    

2.6.2. 理解

  1. 组件从创建到死亡它会经历一些特定的阶段。

  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。

  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

2.6.3. 生命周期流程图(旧)

在这里插入图片描述

生命周期的三个阶段(旧)

  1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    • shouldComponentUpdate()

    • componentWillUpdate()

    • render()

    • componentDidUpdate()

      1. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
        • componentWillUnmount()
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>2_react生命周期(旧)</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      /*       1. 初始化阶段: 由ReactDOM.render()触发---初次渲染        1.	constructor()        2.	componentWillMount()        3.	render()        4.	componentDidMount() =====> 常用        一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息      2. 更新阶段: 由组件内部this.setSate()或父组件render触发        1.	shouldComponentUpdate()        2.	componentWillUpdate()        3.	render() =====> 必须使用的一个        4.	componentDidUpdate()      3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发        1.	componentWillUnmount()  =====> 常用        一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息      */      //创建组件      class Count extends React.Component {        //构造器        constructor(props) {          console.log('Count---constructor');          super(props)          //初始化状态          this.state = {count: 0}        }        //加1按钮的回调        add = () => {          //获取原状态          const {count} = this.state          //更新状态          this.setState({count: count + 1})        }        //卸载组件按钮的回调        death = () => {          ReactDOM.unmountComponentAtNode(document.getElementById('test'))        }        //强制更新按钮的回调        force = () => {          this.forceUpdate()        }        //组件将要挂载的钩子        componentWillMount() {          console.log('Count---componentWillMount');        }        //组件挂载完毕的钩子        componentDidMount() {          console.log('Count---componentDidMount');        }        //组件将要卸载的钩子        componentWillUnmount() {          console.log('Count---componentWillUnmount');        }        //控制组件更新的“阀门”        shouldComponentUpdate() {          console.log('Count---shouldComponentUpdate');          return true        }        //组件将要更新的钩子        componentWillUpdate() {          console.log('Count---componentWillUpdate');        }        //组件更新完毕的钩子        componentDidUpdate() {          console.log('Count---componentDidUpdate');        }        render() {          console.log('Count---render');          const {count} = this.state          return (            <div>              <h2>当前求和为:{count}</h2>              <button onClick={this.add}>点我+1</button>              <button onClick={this.death}>卸载组件</button>              <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>            </div>          )        }      }      //父组件A      class A extends React.Component {        //初始化状态        state = {carName: '奔驰'}        changeCar = () => {          this.setState({carName: '奥拓'})        }        render() {          return (            <div>              <div>我是A组件</div>              <button onClick={this.changeCar}>换车</button>              <B carName={this.state.carName}/>            </div>          )        }      }      //子组件B      class B extends React.Component {        //组件将要接收新的props的钩子        componentWillReceiveProps(props) {          console.log('B---componentWillReceiveProps', props);        }        //控制组件更新的“阀门”        shouldComponentUpdate() {          console.log('B---shouldComponentUpdate');          return true        }        //组件将要更新的钩子        componentWillUpdate() {          console.log('B---componentWillUpdate');        }        //组件更新完毕的钩子        componentDidUpdate() {          console.log('B---componentDidUpdate');        }        render() {          console.log('B---render');          return (            <div>我是B组件,接收到的车是:{this.props.carName}</div>          )        }      }      //渲染组件      ReactDOM.render(<Count/>, document.getElementById('test'))    </script></body></html>

2.6.4. 生命周期流程图(新)

在这里插入图片描述

生命周期的三个阶段(新)

  1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

    • constructor()
    • getDerivedStateFromProps
    • render()
    • componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    • getDerivedStateFromProps

    • shouldComponentUpdate()

    • render()

    • getSnapshotBeforeUpdate

    • componentDidUpdate()

      1. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
        • componentWillUnmount()
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>3_react生命周期(新)</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/17.0.1/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>    <script type="text/babel">      //创建组件      class Count extends React.Component {        /*         1. 初始化阶段: 由ReactDOM.render()触发---初次渲染            1.	constructor()            2.	getDerivedStateFromProps             3.	render()            4.	componentDidMount() =====> 常用            一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息         2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发            1.	getDerivedStateFromProps            2.	shouldComponentUpdate()            3.	render()            4.	getSnapshotBeforeUpdate            5.	componentDidUpdate()         3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发            1.	componentWillUnmount()  =====> 常用            一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息        */        //构造器        constructor(props) {          console.log('Count---constructor');          super(props)          //初始化状态          this.state = {count: 0}        }        //加1按钮的回调        add = () => {          //获取原状态          const {count} = this.state          //更新状态          this.setState({count: count + 1})        }        //卸载组件按钮的回调        death = () => {          ReactDOM.unmountComponentAtNode(document.getElementById('test'))        }        //强制更新按钮的回调        force = () => {          this.forceUpdate()        }        //若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps        static getDerivedStateFromProps(props, state) {          console.log('getDerivedStateFromProps', props, state);          return null        }        //在更新之前获取快照        getSnapshotBeforeUpdate() {          console.log('getSnapshotBeforeUpdate');          return 'atguigu'        }        //组件挂载完毕的钩子        componentDidMount() {          console.log('Count---componentDidMount');        }        //组件将要卸载的钩子        componentWillUnmount() {          console.log('Count---componentWillUnmount');        }        //控制组件更新的“阀门”        shouldComponentUpdate() {          console.log('Count---shouldComponentUpdate');          return true        }        //组件更新完毕的钩子        componentDidUpdate(preProps, preState, snapshotValue) {          console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);        }        render() {          console.log('Count---render');          const {count} = this.state          return (            <div>              <h2>当前求和为:{count}</h2>              <button onClick={this.add}>点我+1</button>              <button onClick={this.death}>卸载组件</button>              <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>            </div>          )        }      }      //渲染组件      ReactDOM.render(<Count count={199}/>, document.getElementById('test'))    </script></body></html>
  • getSnapShotBeforeUpdate的使用场景

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>4_getSnapShotBeforeUpdate的使用场景</title>    <style>        .list {            width: 200px;            height: 150px;            background-color: skyblue;            overflow: auto;        }        .news {            height: 30px;        }    </style></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/17.0.1/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>    <script type="text/babel">      class NewsList extends React.Component {        state = {newsArr: []}        componentDidMount() {          setInterval(() => {            //获取原状态            const {newsArr} = this.state            //模拟一条新闻            const news = '新闻' + (newsArr.length + 1)            //更新状态            this.setState({newsArr: [news, ...newsArr]})          }, 1000);        }        getSnapshotBeforeUpdate() {          return this.refs.list.scrollHeight        }        componentDidUpdate(preProps, preState, height) {          this.refs.list.scrollTop += this.refs.list.scrollHeight - height        }        render() {          return (            <div className="list" ref="list">              {                this.state.newsArr.map((n, index) => {                  return <div key={index} className="news">{n}</div>                })              }            </div>          )        }      }      ReactDOM.render(<NewsList/>, document.getElementById('test'))    </script></body></html>
    

2.6.5. 重要的勾子

  1. render:初始化渲染或更新渲染调用

  2. componentDidMount:开启监听, 发送ajax请求

  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

2.6.6. 即将废弃的勾子

  1. componentWillMount

  2. componentWillReceiveProps

  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

2.7. 虚拟DOM与DOM Diffing算法

2.7.1. 效果

需求:验证虚拟DOM Diffing算法的存在

在这里插入图片描述

  • 验证Diffing算法

    <!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>验证diff算法</title></head><body>    <!-- 准备好一个“容器” -->    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/17.0.1/react.development.js"></script>    <!-- 引入react-dom,用于支持react操作DOM -->    <script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>    <!-- 引入babel,用于将jsx转为js -->    <script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>    <script type="text/babel">      class Time extends React.Component {        state = {date: new Date()}        componentDidMount() {          setInterval(() => {            this.setState({              date: new Date()            })          }, 1000)        }        render() {          return (            <div>              <h1>hello</h1>              <input type="text"/>              <span>							现在是:{this.state.date.toTimeString()}                <input type="text"/>						</span>            </div>          )        }      }      ReactDOM.render(<Time/>, document.getElementById('test'))    </script></body></html>
    
  • key的作用

    <!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>key的作用</title></head><body>    <div id="test"></div>    <!-- 引入react核心库 -->    <script type="text/javascript" src="../js/react.development.js"></script>    <!-- 引入react-dom -->    <script type="text/javascript" src="../js/react-dom.development.js"></script>    <!-- 引入babel -->    <script type="text/javascript" src="../js/babel.min.js"></script>    <script type="text/babel">      /*     经典面试题:        1). react/vue中的key有什么作用?(key的内部原理是什么?)        2). 为什么遍历列表时,key最好不要用index?          1. 虚拟DOM中key的作用:            1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。            2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,                 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:                a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:                  (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM                  (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM                b. 旧虚拟DOM中未找到与新虚拟DOM相同的key                  根据数据创建新的真实DOM,随后渲染到到页面          2. 用index作为key可能会引发的问题:            1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:                会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。            2. 如果结构中还包含输入类的DOM:                会产生错误DOM更新 ==> 界面有问题。            3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,                仅用于渲染列表用于展示,使用index作为key是没有问题的。         3. 开发中如何选择key?:            1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。            2.如果确定只是简单的展示数据,用index也是可以的。     */      /*       慢动作回放----使用index索引值作为key      初始数据:          {id:1,name:'小张',age:18},          {id:2,name:'小李',age:19},      初始的虚拟DOM:          <li key=0>小张---18<input type="text"/></li>          <li key=1>小李---19<input type="text"/></li>      更新后的数据:          {id:3,name:'小王',age:20},          {id:1,name:'小张',age:18},          {id:2,name:'小李',age:19},      更新数据后的虚拟DOM:          <li key=0>小王---20<input type="text"/></li>          <li key=1>小张---18<input type="text"/></li>          <li key=2>小李---19<input type="text"/></li>      -----------------------------------------------------------------      慢动作回放----使用id唯一标识作为key      初始数据:          {id:1,name:'小张',age:18},          {id:2,name:'小李',age:19},      初始的虚拟DOM:          <li key=1>小张---18<input type="text"/></li>          <li key=2>小李---19<input type="text"/></li>      更新后的数据:          {id:3,name:'小王',age:20},          {id:1,name:'小张',age:18},          {id:2,name:'小李',age:19},      更新数据后的虚拟DOM:          <li key=3>小王---20<input type="text"/></li>          <li key=1>小张---18<input type="text"/></li>          <li key=2>小李---19<input type="text"/></li>*/      class Person extends React.Component {        state = {          persons: [            {id: 1, name: '小张', age: 18},            {id: 2, name: '小李', age: 19},          ]        }        add = () => {          const {persons} = this.state          const p = {id: persons.length + 1, name: '小王', age: 20}          this.setState({persons: [p, ...persons]})        }        render() {          return (            <div>              <h2>展示人员信息</h2>              <button onClick={this.add}>添加一个小王</button>              <h3>使用index(索引值)作为key</h3>              <ul>                {                  this.state.persons.map((personObj, index) => {                    return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>                  })                }              </ul>              <hr/>              <hr/>              <h3>使用id(数据的唯一标识)作为key</h3>              <ul>                {                  this.state.persons.map((personObj) => {                    return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>                  })                }              </ul>            </div>          )        }      }      ReactDOM.render(<Person/>, document.getElementById('test'))    </script></body></html>
    

2.7.2. 基本原理图

在这里插入图片描述

第3章:React应用(基于React脚手架)

3.1. 使用create-react-app创建react应用

3.1.1. react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目

    • 包含了所有需要的配置(语法检查、jsx编译、devServer…)
    • 下载好了所有相关的依赖
    • 可以直接运行一个简单效果
  2. react提供了一个用于创建react项目的脚手架库: create-react-app

  3. 项目的整体技术架构为: react + webpack + es6 + eslint

  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

3.1.2. 创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

3.1.3. react脚手架项目结构

public ---- 静态资源文件夹

​ favicon.icon ------ 网站页签图标

index.html --------主页面

​ logo192.png ------- logo图

​ logo512.png ------- logo图

​ manifest.json ----- 应用加壳的配置文件

​ robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

​ App.css -------- App组件的样式

App.js --------- App组件

​ App.test.js ---- 用于给App做测试

​ index.css ------ 样式

index.js ------- 入口文件

​ logo.svg ------- logo图

​ reportWebVitals.js

​ — 页面性能分析文件(需要web-vitals库的支持)

​ setupTests.js

​ ---- 组件单元测试的文件(需要jest-dom库的支持)

3.1.4. 功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件
    • 3.1 动态显示初始化数据
      • 3.1.1 数据类型
      • 3.1.2 数据名称
      • 3.1.3 保存在哪个组件?
    • 3.2 交互(从绑定事件监听开始)

3.1.5 样式模块化

在css文件名中加入.module,例如:index.module.css

引入的时候使用import hello from './index.module.css'后,所有的样式都保存在hello对象中

使用的时候使用{hello.title}即可进行区分

实现样式模块化

3.1.6 props限制

  • 下载prop-typesyarn add prop-types

  • 引入import PropTypes from 'prop-types'

  • 类组件中使用static propTypes = {}进行限制

    // 对接收的props进行类型、必要性的限制static propTypes = {    addTodo : PropTypes.func.isRequired}
    

3.2. 组件的组合使用-TodoList

功能: 组件化实现此功能

1. 显示所有todo列表
2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本

在这里插入图片描述

src

​ —components

​ —Footer

​ —index.css

​ —index.jsx

​ —Header

​ —index.css

​ —index.jsx

​ —Item

​ —index.css

​ —index.jsx

​ —List

​ —index.css

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//引入Appimport App from './App'ReactDOM.render(<App/>, document.getElementById('root'))
    
  • App.jsx

    import React, {Component} from 'react'import Header from './components/Header'import List from './components/List'import Footer from './components/Footer'import './App.css'export default class App extends Component {  //状态在哪里,操作状态的方法就在哪里  //初始化状态  state = {    todos: [      {id: '001', name: '吃饭', done: true},      {id: '002', name: '睡觉', done: true},      {id: '003', name: '打代码', done: false},      {id: '004', name: '逛街', done: false}    ]  }  //addTodo用于添加一个todo,接收的参数是todo对象  addTodo = (todoObj) => {    //获取原todos    const {todos} = this.state    //追加一个todo    const newTodos = [todoObj, ...todos]    //更新状态    this.setState({todos: newTodos})  }  //updateTodo用于更新一个todo对象  updateTodo = (id, done) => {    //获取状态中的todos    const {todos} = this.state    //匹配处理数据    const newTodos = todos.map((todoObj) => {      if (todoObj.id === id) return {...todoObj, done}      else return todoObj    })    this.setState({todos: newTodos})  }  //deleteTodo用于删除一个todo对象  deleteTodo = (id) => {    //获取原来的todos    const {todos} = this.state    //删除指定id的todo对象    const newTodos = todos.filter((todoObj) => {      return todoObj.id !== id    })    //更新状态    this.setState({todos: newTodos})  }  //checkAllTodo用于全选  checkAllTodo = (done) => {    //获取原来的todos    const {todos} = this.state    //加工数据    const newTodos = todos.map((todoObj) => {      return {...todoObj, done}    })    //更新状态    this.setState({todos: newTodos})  }  //clearAllDone用于清除所有已完成的  clearAllDone = () => {    //获取原来的todos    const {todos} = this.state    //过滤数据    const newTodos = todos.filter((todoObj) => {      return !todoObj.done    })    //更新状态    this.setState({todos: newTodos})  }  render() {    const {todos} = this.state    return (      <div className="todo-container">        <div className="todo-wrap">          <Header addTodo={this.addTodo}/>          <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>          <Footer todos={todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/>        </div>      </div>    )  }}
    
  • App.css

    /*base*/body {    background: #fff;}.btn {    display: inline-block;    padding: 4px 12px;    margin-bottom: 0;    font-size: 14px;    line-height: 20px;    text-align: center;    vertical-align: middle;    cursor: pointer;    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);    border-radius: 4px;}.btn-danger {    color: #fff;    background-color: #da4f49;    border: 1px solid #bd362f;}.btn-danger:hover {    color: #fff;    background-color: #bd362f;}.btn:focus {    outline: none;}.todo-container {    width: 600px;    margin: 0 auto;}.todo-container .todo-wrap {    padding: 10px;    border: 1px solid #ddd;    border-radius: 5px;}
    
  • Footer

    • index.jsx

      import React, {Component} from 'react'import './index.css'export default class Footer extends Component {  //全选checkbox的回调  handleCheckAll = (event) => {    this.props.checkAllTodo(event.target.checked)  }  //清除已完成任务的回调  handleClearAllDone = () => {    this.props.clearAllDone()  }  render() {    const {todos} = this.props    //已完成的个数    const doneCount = todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)    //总数    const total = todos.length    return (      <div className="todo-footer">        <label>          <input type="checkbox" onChange={this.handleCheckAll}                 checked={doneCount === total && total !== 0 ? true : false}/>        </label>        <span>					<span>已完成{doneCount}</span> / 全部{total}				</span>        <button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>      </div>    )  }}
      
    • index.css

      /*footer*/.todo-footer {    height: 40px;    line-height: 40px;    padding-left: 6px;    margin-top: 5px;}.todo-footer label {    display: inline-block;    margin-right: 20px;    cursor: pointer;}.todo-footer label input {    position: relative;    top: -1px;    vertical-align: middle;    margin-right: 5px;}.todo-footer button {    float: right;    margin-top: 5px;}
      
  • Header

    • index.jsx

      import React, {Component} from 'react'import PropTypes from 'prop-types'import {nanoid} from 'nanoid'import './index.css'export default class Header extends Component {  //对接收的props进行:类型、必要性的限制  static propTypes = {    addTodo: PropTypes.func.isRequired  }  //键盘事件的回调  handleKeyUp = (event) => {    //解构赋值获取keyCode,target    const {keyCode, target} = event    //判断是否是回车按键    if (keyCode !== 13) return    //添加的todo名字不能为空    if (target.value.trim() === '') {      alert('输入不能为空')      return    }    //准备好一个todo对象    const todoObj = {id: nanoid(), name: target.value, done: false}    //将todoObj传递给App    this.props.addTodo(todoObj)    //清空输入    target.value = ''  }  render() {    return (      <div className="todo-header">        <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"/>      </div>    )  }}
      
    • index.css

      /*header*/.todo-header input {    width: 560px;    height: 28px;    font-size: 14px;    border: 1px solid #ccc;    border-radius: 4px;    padding: 4px 7px;}.todo-header input:focus {    outline: none;    border-color: rgba(82, 168, 236, 0.8);    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
      
  • Item

    • index.jsx

      import React, {Component} from 'react'import './index.css'export default class Item extends Component {  state = {mouse: false} //标识鼠标移入、移出  //鼠标移入、移出的回调  handleMouse = (flag) => {    return () => {      this.setState({mouse: flag})    }  }  //勾选、取消勾选某一个todo的回调  handleCheck = (id) => {    return (event) => {      this.props.updateTodo(id, event.target.checked)    }  }  //删除一个todo的回调  handleDelete = (id) => {    if (window.confirm('确定删除吗?')) {      this.props.deleteTodo(id)    }  }  render() {    const {id, name, done} = this.props    const {mouse} = this.state    return (      <li style={{backgroundColor: mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)}          onMouseLeave={this.handleMouse(false)}>        <label>          <input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>          <span>{name}</span>        </label>        <button onClick={() => this.handleDelete(id)} className="btn btn-danger"                style={{display: mouse ? 'block' : 'none'}}>删除        </button>      </li>    )  }}
      
    • index.css

      /*item*/li {    list-style: none;    height: 36px;    line-height: 36px;    padding: 0 5px;    border-bottom: 1px solid #ddd;}li label {    float: left;    cursor: pointer;}li label li input {    vertical-align: middle;    margin-right: 6px;    position: relative;    top: -1px;}li button {    float: right;    display: none;    margin-top: 3px;}li:before {    content: initial;}li:last-child {    border-bottom: none;}
      
  • List

    • index.jsx

      import React, {Component} from 'react'import PropTypes from 'prop-types'import Item from '../Item'import './index.css'export default class List extends Component {  //对接收的props进行:类型、必要性的限制  static propTypes = {    todos: PropTypes.array.isRequired,    updateTodo: PropTypes.func.isRequired,    deleteTodo: PropTypes.func.isRequired,  }  render() {    const {todos, updateTodo, deleteTodo} = this.props    return (      <ul className="todo-main">        {          todos.map(todo => {            return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>          })        }      </ul>    )  }}
      
    • index.css

      /*main*/.todo-main {    margin-left: 0px;    border: 1px solid #ddd;    border-radius: 2px;    padding: 0px;}.todo-empty {    height: 40px;    line-height: 40px;    border: 1px solid #ddd;    border-radius: 2px;    padding-left: 5px;    margin-top: 10px;}
      

3.2.1 todoList案例相关知识点

  1. 拆分组件、实现静态组件,注意:className、style的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?
    • 某个组件使用:放在其自身的state中
    • 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信:
    • 【父组件】给【子组件】传递数据:通过props传递
    • 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
  5. 状态在哪里,操作状态的方法就在哪里

第4章:React ajax

4.1. 理解

4.1.1. 前置说明

  1. React本身只关注于界面, 并不包含发送ajax请求的代码

  2. 前端应用需要通过ajax请求与后台进行交互(json数据)

  3. react应用中需要集成第三方ajax库(或自己封装)

4.1.2. 常用的ajax请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用

  2. axios: 轻量级, 建议使用

    • 封装XmlHttpRequest对象的ajax
    • promise风格
    • 可以用在浏览器端和node服务器端

4.2. axios

4.2.1. 文档

https://github.com/axios/axios

4.2.2. 相关API

  • GET请求

    axios.get('/user?ID=12345')  .then(function (response) {    console.log(response.data);  })  .catch(function (error) {    console.log(error);  });axios.get('/user', {    params: {      ID: 12345    }  })  .then(function (response) {    console.log(response);  })  .catch(function (error) {    console.log(error);  });
    
  • POST请求

    axios.post('/user', {  firstName: 'Fred',  lastName: 'Flintstone'}).then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});
    

4.2.3 React处理跨域的方法

  1. package.json中增加proxy跨域配置

    "proxy":"http://localhost:5000"
    

    在这里插入图片描述

    配置好后,即可在React中访问3000端口转接到5000(默认在3000启动项目)

    在这里插入图片描述

    注意:配置好后,首先回到3000端口请求资源,没有的才会转接到5000服务器端口。例如如果请求index.html,则不会转接到5000端口

  2. 在src目录下新建setupProxy.js,并进行如下配置

    const proxy = require('http-proxy-middleware')module.exports = function (app) {  app.use(    proxy('/api1', { //遇见/api1前缀的请求,就会触发该代理配置      target: 'http://localhost:5000', //请求转发给谁      changeOrigin: true,//控制服务器收到的请求头中Host的值      pathRewrite: {'^/api1': ''} //重写请求路径(必须)    }),    proxy('/api2', {      target: 'http://localhost:5001',      changeOrigin: true,      pathRewrite: {'^/api2': ''}    }),  )}
    

    在这里插入图片描述

  3. 总结

    • 方法一

      在package.json中追加如下配置

      "proxy":"http://localhost:5000"
      

      说明:

      • 优点:配置简单,前端请求资源时可以不加任何前缀。
      • 缺点:不能配置多个代理。
      • 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
    • 方法二

      • 第一步:创建代理配置文件

        在src下创建配置文件:src/setupProxy.js

      • 编写setupProxy.js配置具体代理规则:

        const proxy = require('http-proxy-middleware')module.exports = function(app) {  app.use(    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)      changeOrigin: true, //控制服务器接收到的请求头中host字段的值      /*      	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000      	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000      	changeOrigin默认值为false,但我们一般将changeOrigin值设为true      */      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)    }),    proxy('/api2', {       target: 'http://localhost:5001',      changeOrigin: true,      pathRewrite: {'^/api2': ''}    })  )}
        

      说明:

      • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
      • 缺点:配置繁琐,前端请求资源时必须加前缀。

4.3. 案例—github用户搜索

4.3.1. 效果

在这里插入图片描述

请求地址: https://api.github.com/search/users?q=xxxxxx

src

​ —components

​ —List

​ —index.css

​ —index.jsx

​ —Search

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import Search from './components/Search'import List from './components/List'export default class App extends Component {  state = { //初始化状态    users: [], //users初始值为数组    isFirst: true, //是否为第一次打开页面    isLoading: false,//标识是否处于加载中    err: '',//存储请求相关的错误信息  }  //更新App的state  updateAppState = (stateObj) => {    this.setState(stateObj)  }  render() {    return (      <div className="container">        <Search updateAppState={this.updateAppState}/>        <List {...this.state}/>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//引入Appimport App from './App'ReactDOM.render(<App/>, document.getElementById('root'))
    
  • setupProxy.js

    const proxy = require('http-proxy-middleware')module.exports = function (app) {  app.use(    proxy('/api1', { //遇见/api1前缀的请求,就会触发该代理配置      target: 'http://localhost:5000', //请求转发给谁      changeOrigin: true,//控制服务器收到的请求头中Host的值      pathRewrite: {'^/api1': ''} //重写请求路径(必须)    })  )}
    
  • components/List/index.css

    .album {    min-height: 50rem; /* Can be removed; just added for demo purposes */    padding-top: 3rem;    padding-bottom: 3rem;    background-color: #f7f7f7;}.card {    float: left;    width: 33.333%;    padding: .75rem;    margin-bottom: 2rem;    border: 1px solid #efefef;    text-align: center;}.card > img {    margin-bottom: .75rem;    border-radius: 100px;}.card-text {    font-size: 85%;}
    
  • components/List/index.jsx

    import React, {Component} from 'react'import './index.css'export default class List extends Component {  render() {    const {users, isFirst, isLoading, err} = this.props    return (      <div className="row">        {          isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :            isLoading ? <h2>Loading......</h2> :              err ? <h2 style={{color: 'red'}}>{err}</h2> :                users.map((userObj) => {                  return (                    <div key={userObj.id} className="card">                      <a rel="noreferrer" href={userObj.html_url} target="_blank">                        <img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>                      </a>                      <p className="card-text">{userObj.login}</p>                    </div>                  )                })        }      </div>    )  }}
    
  • components/Search/index.jsx

    import React, { Component } from 'react'import axios from 'axios'export default class Search extends Component {	search = ()=>{		//获取用户的输入(连续解构赋值+重命名)		const {keyWordElement:{value:keyWord}} = this		//发送请求前通知App更新状态		this.props.updateAppState({isFirst:false,isLoading:true})		//发送网络请求		axios.get(`/api1/search/users?q=${keyWord}`).then(			response => {				//请求成功后通知App更新状态				this.props.updateAppState({isLoading:false,users:response.data.items})			},			error => {				//请求失败后通知App更新状态				this.props.updateAppState({isLoading:false,err:error.message})			}		)	}	render() {		return (			<section className="jumbotron">				<h3 className="jumbotron-heading">搜索github用户</h3>				<div>					<input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;					<button onClick={this.search}>搜索</button>				</div>			</section>		)	}}
    

4.4. 消息订阅-发布机制

  1. 工具库: PubSubJS

  2. 下载: npm install pubsub-js --save

  3. 使用:

    • import PubSub from ‘pubsub-js’ //引入
    • PubSub.subscribe(‘delete’, function(data){ }); //订阅
    • PubSub.publish(‘delete’, data) //发布消息

src

​ —components

​ —List

​ —index.css

​ —index.jsx

​ —Search

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import Search from './components/Search'import List from './components/List'export default class App extends Component {  render() {    return (      <div className="container">        <Search/>        <List/>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//引入Appimport App from './App'ReactDOM.render(<App/>, document.getElementById('root'))
    
  • setupProxy.js

    const proxy = require('http-proxy-middleware')module.exports = function (app) {  app.use(    proxy('/api1', { //遇见/api1前缀的请求,就会触发该代理配置      target: 'http://localhost:5000', //请求转发给谁      changeOrigin: true,//控制服务器收到的请求头中Host的值      pathRewrite: {'^/api1': ''} //重写请求路径(必须)    })  )}
    
  • components/List/index.css

    .album {    min-height: 50rem; /* Can be removed; just added for demo purposes */    padding-top: 3rem;    padding-bottom: 3rem;    background-color: #f7f7f7;}.card {    float: left;    width: 33.333%;    padding: .75rem;    margin-bottom: 2rem;    border: 1px solid #efefef;    text-align: center;}.card > img {    margin-bottom: .75rem;    border-radius: 100px;}.card-text {    font-size: 85%;}
    
  • components/List/index.jsx

    import React, {Component} from 'react'import PubSub from 'pubsub-js'import './index.css'export default class List extends Component {  state = { //初始化状态    users: [], //users初始值为数组    isFirst: true, //是否为第一次打开页面    isLoading: false,//标识是否处于加载中    err: '',//存储请求相关的错误信息  }  componentDidMount() {    this.token = PubSub.subscribe('atguigu', (_, stateObj) => {      this.setState(stateObj)    })  }  componentWillUnmount() {    PubSub.unsubscribe(this.token)  }  render() {    const {users, isFirst, isLoading, err} = this.state    return (      <div className="row">        {          isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :            isLoading ? <h2>Loading......</h2> :              err ? <h2 style={{color: 'red'}}>{err}</h2> :                users.map((userObj) => {                  return (                    <div key={userObj.id} className="card">                      <a rel="noreferrer" href={userObj.html_url} target="_blank">                        <img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>                      </a>                      <p className="card-text">{userObj.login}</p>                    </div>                  )                })        }      </div>    )  }}
    
  • components/Search/index.jsx

    import React, {Component} from 'react'import PubSub from 'pubsub-js'import axios from 'axios'export default class Search extends Component {  search = () => {    //获取用户的输入(连续解构赋值+重命名)    const {keyWordElement: {value: keyWord}} = this    //发送请求前通知List更新状态    PubSub.publish('atguigu', {isFirst: false, isLoading: true})    //发送网络请求    axios.get(`/api1/search/users?q=${keyWord}`).then(      response => {        //请求成功后通知List更新状态        PubSub.publish('atguigu', {isLoading: false, users: response.data.items})      },      error => {        //请求失败后通知App更新状态        PubSub.publish('atguigu', {isLoading: false, err: error.message})      }    )  }  render() {    return (      <section className="jumbotron">        <h3 className="jumbotron-heading">搜索github用户</h3>        <div>          <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;          <button onClick={this.search}>搜索</button>        </div>      </section>    )  }}
    

4.5. 扩展:Fetch

4.5.1. 文档

  1. https://github.github.io/fetch/

  2. https://segmentfault.com/a/1190000003810652

4.5.2. 特点

  1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求

  2. 老版本浏览器可能不支持

4.5.3. 相关API

  • GET请求

    fetch(url).then(function(response) {    return response.json()  }).then(function(data) {    console.log(data)  }).catch(function(e) {    console.log(e)  });
    
  • POST请求

    fetch(url, {    method: "POST",    body: JSON.stringify(data),  }).then(function(data) {    console.log(data)  }).catch(function(e) {    console.log(e)  })
    

src

​ —components

​ —List

​ —index.css

​ —index.jsx

​ —Search

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import Search from './components/Search'import List from './components/List'export default class App extends Component {  render() {    return (      <div className="container">        <Search/>        <List/>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//引入Appimport App from './App'ReactDOM.render(<App/>, document.getElementById('root'))
    
  • setupProxy.js

    const proxy = require('http-proxy-middleware')module.exports = function (app) {  app.use(    proxy('/api1', { //遇见/api1前缀的请求,就会触发该代理配置      target: 'http://localhost:5000', //请求转发给谁      changeOrigin: true,//控制服务器收到的请求头中Host的值      pathRewrite: {'^/api1': ''} //重写请求路径(必须)    })  )}
    
  • components/List/index.css

    .album {    min-height: 50rem; /* Can be removed; just added for demo purposes */    padding-top: 3rem;    padding-bottom: 3rem;    background-color: #f7f7f7;}.card {    float: left;    width: 33.333%;    padding: .75rem;    margin-bottom: 2rem;    border: 1px solid #efefef;    text-align: center;}.card > img {    margin-bottom: .75rem;    border-radius: 100px;}.card-text {    font-size: 85%;}
    
  • components/List/index.jsx

    import React, {Component} from 'react'import PubSub from 'pubsub-js'import './index.css'export default class List extends Component {  state = { //初始化状态    users: [], //users初始值为数组    isFirst: true, //是否为第一次打开页面    isLoading: false,//标识是否处于加载中    err: '',//存储请求相关的错误信息  }  componentDidMount() {    this.token = PubSub.subscribe('atguigu', (_, stateObj) => {      this.setState(stateObj)    })  }  componentWillUnmount() {    PubSub.unsubscribe(this.token)  }  render() {    const {users, isFirst, isLoading, err} = this.state    return (      <div className="row">        {          isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :            isLoading ? <h2>Loading......</h2> :              err ? <h2 style={{color: 'red'}}>{err}</h2> :                users.map((userObj) => {                  return (                    <div key={userObj.id} className="card">                      <a rel="noreferrer" href={userObj.html_url} target="_blank">                        <img alt="head_portrait" src={userObj.avatar_url} style={{width: '100px'}}/>                      </a>                      <p className="card-text">{userObj.login}</p>                    </div>                  )                })        }      </div>    )  }}
    
  • components/Search/index.jsx

    import React, {Component} from 'react'import PubSub from 'pubsub-js'// import axios from 'axios'export default class Search extends Component {  search = async () => {    //获取用户的输入(连续解构赋值+重命名)    const {keyWordElement: {value: keyWord}} = this    //发送请求前通知List更新状态    PubSub.publish('atguigu', {isFirst: false, isLoading: true})    //#region 发送网络请求---使用axios发送    /* axios.get(`/api1/search/users2?q=${keyWord}`).then(      response => {        //请求成功后通知List更新状态        PubSub.publish('atguigu',{isLoading:false,users:response.data.items})      },      error => {        //请求失败后通知App更新状态        PubSub.publish('atguigu',{isLoading:false,err:error.message})      }    ) */    //#endregion    //发送网络请求---使用fetch发送(未优化)    /* fetch(`/api1/search/users2?q=${keyWord}`).then(      response => {        console.log('联系服务器成功了');        return response.json()      },      error => {        console.log('联系服务器失败了',error);        return new Promise(()=>{})      }    ).then(      response => {console.log('获取数据成功了',response);},      error => {console.log('获取数据失败了',error);}    ) */    //发送网络请求---使用fetch发送(优化)    try {      const response = await fetch(`/api1/search/users2?q=${keyWord}`)      const data = await response.json()      console.log(data);      PubSub.publish('atguigu', {isLoading: false, users: data.items})    } catch (error) {      console.log('请求出错', error);      PubSub.publish('atguigu', {isLoading: false, err: error.message})    }  }  render() {    return (      <section className="jumbotron">        <h3 className="jumbotron-heading">搜索github用户</h3>        <div>          <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;          <button onClick={this.search}>搜索</button>        </div>      </section>    )  }}
    

4.6 github搜索案例相关知识点

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。

  2. ES6小知识点:解构赋值+重命名

    let obj = {a:{b:1}}const {a} = obj; //传统解构赋值const {a:{b}} = obj; //连续解构赋值const {a:{b:value}} = obj; //连续解构赋值+重命名
    
  3. 消息订阅与发布机制

    • 先订阅,再发布(理解:有一种隔空对话的感觉)
    • 适用于任意组件间通信
    • 要在组件的componentWillUnmount中取消订阅
  4. fetch发送请求(关注分离的设计思想)

    try {    const response= await fetch(`/api1/search/users2?q=${keyWord}`)    const data = await response.json()    console.log(data);} catch (error) {	console.log('请求出错',error);}
    

第5章:React路由

5.1. 相关理解

5.1.1. SPA的理解

  1. 单页Web应用(single page web application,SPA)。

  2. 整个应用只有一个完整的页面

  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。

  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

5.1.2. 路由的理解

  1. 什么是路由?
    • 一个路由就是一个映射关系(key:value)
    • key为路径, value可能是function或component
  2. 路由分类
    • 后端路由:
      • 理解: value是function, 用来处理客户端提交的请求。
      • 注册路由: router.get(path, function(req, res))
      • 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
    • 前端路由:
      • 浏览器端路由,value是component,用于展示页面内容。
      • 注册路由: <Route path="/test" component={Test}>
      • 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

5.1.3. react-router-dom的理解

  1. react的一个插件库。
  2. 专门用来实现一个SPA应用。
  3. 基于react的项目基本都会用到此库。

5.1.4. 安装

yarn add react-router-dom

5.2. react-router-dom相关API

5.2.1. 内置组件

  1. <BrowserRouter>

  2. <HashRouter>

  3. <Route>

  4. <Redirect>

  5. <Link>

  6. <NavLink>

  7. <Switch>

5.2.2. 其它

  1. history对象

  2. match对象

  3. withRouter函数

5.3. 基本路由使用

5.3.1. 效果

请添加图片描述

src

​ —components

​ —About

​ —index.jsx

​ —Home

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Home from './components/Home'import About from './components/About'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <div className="page-header"><h2>React Router Demo</h2></div>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <Link className="list-group-item" to="/about">About</Link>              <Link className="list-group-item" to="/home">Home</Link>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Route path="/about" component={About}/>                <Route path="/home" component={Home}/>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    return (      <h3>我是About的内容</h3>    )  }}
    
  • components/Home/index.jsx

    import React, {Component} from 'react'export default class Home extends Component {  render() {    return (      <h3>我是Home的内容</h3>    )  }}
    

5.3.2. 准备

  1. 下载react-router-dom: npm install --save react-router-dom
  2. 引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">

5.3.3. 知识点

  1. 明确好界面中的导航区、展示区
  2. 导航区的a标签改为Link标签
    <Link to="/xxxxx">Demo</Link>
  3. 展示区写Route标签进行路径的匹配
    <Route path='/xxxx' component={Demo}/>
  4. <App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>

5.3.4. 路由组件和一般组件

  1. 写法不同:

    • 一般组件:<Demo/>
    • 路由组件:<Route path="/demo" component={Demo}/>
  2. 存放位置不同:

    • 一般组件:components
    • 路由组件:pages
  3. 接收到的props不同:

    • 一般组件:写组件标签时传递了什么,就能收到什么

    • 路由组件:路由组件会收到三个重要的参数history、location、match

      • history:

        go: ƒ go(n)

        goBack: ƒ goBack()

        goForward: ƒ goForward()

        push: ƒ push(path, state)

        replace: ƒ replace(path, state)

      • location:

        pathname: “/about”

        search: “”

        state: undefined

      • match:

        params: {}

        path: “/about”

        url: “/about”

5.3.5 NavLink

NavLink可以控制选中样式,默认选中为active。可以使用activeClassName指定选中样式名。

5.3.6 封装NavLink

  1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
  2. 标签体内容是一个特殊的标签属性
  3. 通过this.props.children可以获取标签体内容

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Route path="/about" component={About}/>                <Route path="/home" component={Home}/>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'export default class Home extends Component {  render() {    return (      <h3>我是Home的内容</h3>    )  }}
    

5.3.7 Switch的使用

  1. 通常情况下,path和component是一一对应的关系。
  2. Switch可以提高路由匹配效率(单一匹配)。
<Switch>    <Route path="/about" component={About}/>    <Route path="/home" component={Home}/>    <Route path="/home" component={Test}/></Switch>

5.3.8 解决多级路径刷新页面样式丢失的问题

  1. public/index.html 中 引入样式时不写 ./ 写 / (常用)
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
  3. 使用HashRouter

5.3.9 路由精准匹配与模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

  2. 开启严格匹配:<Route exact={true} path="/about" component={About}/>

    使用参数exact开启严格匹配

  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

5.3.10 路由重定向(Redirect的使用)

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

  2. 具体编码:

    <Switch>    <Route path="/about" component={About}/>    <Route path="/home" component={Home}/>    <Redirect to="/about"/></Switch>
    

5.4. 嵌套路由使用

效果

在这里插入图片描述

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'export default class Message extends Component {  render() {    return (      <div>        <ul>          <li>            <a href="/message1">message001</a>&nbsp;&nbsp;          </li>          <li>            <a href="/message2">message002</a>&nbsp;&nbsp;          </li>          <li>            <a href="/message/3">message003</a>&nbsp;&nbsp;          </li>        </ul>      </div>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.5. 向路由组件传递参数数据

5.3.1 效果

在这里插入图片描述

5.3.2 向路由组件传递params参数

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        <Route path="/home/message/detail/:id/:title" component={Detail}/>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    const {id, title} = this.props.match.params    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    })    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.3.3 向路由组件传递search参数

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递search参数 */}                  <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}        {/* search参数无需声明接收,正常注册路由即可 */}        <Route path="/home/message/detail" component={Detail}/>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'import qs from 'querystring'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    // const {id,title} = this.props.match.params     // 接收search参数    const {search} = this.props.location    const {id, title} = qs.parse(search.slice(1))    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    })    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.3.4 向路由组件传递state参数

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递search参数 */}                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递state参数 */}                  <Link to={{                    pathname: '/home/message/detail',                    state: {id: msgObj.id, title: msgObj.title}                  }}>{msgObj.title}</Link>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}        {/* search参数无需声明接收,正常注册路由即可 */}        {/* <Route path="/home/message/detail" component={Detail}/> */}        {/* state参数无需声明接收,正常注册路由即可 */}        <Route path="/home/message/detail" component={Detail}/>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'// import qs from 'querystring'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    // const {id,title} = this.props.match.params     // 接收search参数    // const {search} = this.props.location    // const {id,title} = qs.parse(search.slice(1))    // 接收state参数    const {id, title} = this.props.location.state || {}    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    }) || {}    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.3.5 向路由组件传递参数总结

  1. params参数

    • 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
    • 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
    • 接收参数:this.props.match.params
  2. search参数

    • 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
    • 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    • 接收参数:this.props.location.search

    备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

  3. state参数

    • 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    • 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
    • 接收参数:this.props.location.state

    备注:刷新也可以保留住参数

5.6. 多种路由跳转方式

给link添加replace即可开启replace模式

效果

在这里插入图片描述

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink replace to="/about">About</MyNavLink>              <MyNavLink replace to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink replace to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink replace to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递search参数 */}                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递state参数 */}                  <Link replace to={{                    pathname: '/home/message/detail',                    state: {id: msgObj.id, title: msgObj.title}                  }}>{msgObj.title}</Link>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}        {/* search参数无需声明接收,正常注册路由即可 */}        {/* <Route path="/home/message/detail" component={Detail}/> */}        {/* state参数无需声明接收,正常注册路由即可 */}        <Route path="/home/message/detail" component={Detail}/>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'// import qs from 'querystring'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    // const {id,title} = this.props.match.params     // 接收search参数    // const {search} = this.props.location    // const {id,title} = qs.parse(search.slice(1))    // 接收state参数    const {id, title} = this.props.location.state || {}    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    }) || {}    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.7. 编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退

  • this.prosp.history.push()
  • this.prosp.history.replace()
  • this.prosp.history.goBack()
  • this.prosp.history.goForward()
  • this.prosp.history.go()

例如:

this.props.history.replace(url)this.props.history.push(url)
replaceShow = (id, title) => {    //replace跳转+携带params参数    //this.props.history.replace(`/home/message/detail/${id}/${title}`)    //replace跳转+携带search参数    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)    //replace跳转+携带state参数    this.props.history.replace(`/home/message/detail`, {id, title})}pushShow = (id, title) => {    //push跳转+携带params参数    // this.props.history.push(`/home/message/detail/${id}/${title}`)    //push跳转+携带search参数    // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)    //push跳转+携带state参数    this.props.history.push(`/home/message/detail`, {id, title})}

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'export default class Header extends Component {  render() {    // console.log('Header组件收到的props是',this.props);    return (      <div className="page-header"><h2>React Router Demo</h2></div>    )  }}
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  replaceShow = (id, title) => {    //replace跳转+携带params参数    //this.props.history.replace(`/home/message/detail/${id}/${title}`)    //replace跳转+携带search参数    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)    //replace跳转+携带state参数    this.props.history.replace(`/home/message/detail`, {id, title})  }  pushShow = (id, title) => {    //push跳转+携带params参数    // this.props.history.push(`/home/message/detail/${id}/${title}`)    //push跳转+携带search参数    // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)    //push跳转+携带state参数    this.props.history.push(`/home/message/detail`, {id, title})  }  back = () => {    this.props.history.goBack()  }  forward = () => {    this.props.history.goForward()  }  go = () => {    this.props.history.go(-2)  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递search参数 */}                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递state参数 */}                  <Link to={{                    pathname: '/home/message/detail',                    state: {id: msgObj.id, title: msgObj.title}                  }}>{msgObj.title}</Link>                  &nbsp;                  <button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>                  &nbsp;                  <button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}        {/* search参数无需声明接收,正常注册路由即可 */}        {/* <Route path="/home/message/detail" component={Detail}/> */}        {/* state参数无需声明接收,正常注册路由即可 */}        <Route path="/home/message/detail" component={Detail}/>        <button onClick={this.back}>回退</button>        &nbsp;        <button onClick={this.forward}>前进</button>        &nbsp;        <button onClick={this.go}>go</button>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'// import qs from 'querystring'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    // const {id,title} = this.props.match.params     // 接收search参数    // const {search} = this.props.location    // const {id,title} = qs.parse(search.slice(1))    // 接收state参数    const {id, title} = this.props.location.state || {}    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    }) || {}    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  /* componentDidMount(){    setTimeout(()=>{      this.props.history.push('/home/message')    },2000)  } */  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.8 withRouter的使用

  • withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
  • withRouter的返回值是一个新组件
  • 通过withRouter包裹组件,可以让组件也是用路由API

在这里插入图片描述

src

​ —components

​ —Header

​ —index.jsx

​ —MyNavLink

​ —index.jsx

​ —pages

​ —About

​ ----index.jsx

​ —Home

​ —Message

​ —Detail

​ —index.jsx

​ —index.jsx

​ —News

​ —index.jsx

​ —index.jsx

​ —App.css

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'import {Route, Switch, Redirect} from 'react-router-dom'import Home from './pages/Home' //Home是路由组件import About from './pages/About' //About是路由组件import Header from './components/Header' //Header是一般组件import MyNavLink from './components/MyNavLink'export default class App extends Component {  render() {    return (      <div>        <div className="row">          <div className="col-xs-offset-2 col-xs-8">            <Header/>          </div>        </div>        <div className="row">          <div className="col-xs-2 col-xs-offset-2">            <div className="list-group">              {/* 原生html中,靠<a>跳转不同的页面 */}              {/* <a className="list-group-item" href="./about.html">About</a>							<a className="list-group-item active" href="./home.html">Home</a> */}              {/* 在React中靠路由链接实现切换组件--编写路由链接 */}              <MyNavLink to="/about">About</MyNavLink>              <MyNavLink to="/home">Home</MyNavLink>            </div>          </div>          <div className="col-xs-6">            <div className="panel">              <div className="panel-body">                {/* 注册路由 */}                <Switch>                  <Route path="/about" component={About}/>                  <Route path="/home" component={Home}/>                  <Redirect to="/about"/>                </Switch>              </div>            </div>          </div>        </div>      </div>    )  }}
    
  • index.js

    //引入react核心库import React from 'react'//引入ReactDOMimport ReactDOM from 'react-dom'//import {BrowserRouter} from 'react-router-dom'//引入Appimport App from './App'ReactDOM.render(  <BrowserRouter>    <App/>  </BrowserRouter>,  document.getElementById('root'))
    
  • components/Header/index.jsx

    import React, {Component} from 'react'import {withRouter} from 'react-router-dom'class Header extends Component {  back = () => {    this.props.history.goBack()  }  forward = () => {    this.props.history.goForward()  }  go = () => {    this.props.history.go(-2)  }  render() {    console.log('Header组件收到的props是', this.props);    return (      <div className="page-header">        <h2>React Router Demo</h2>        <button onClick={this.back}>回退</button>        &nbsp;        <button onClick={this.forward}>前进</button>        &nbsp;        <button onClick={this.go}>go</button>      </div>    )  }}export default withRouter(Header)//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API//withRouter的返回值是一个新组件
    
  • components/MyNavLink/index.jsx

    import React, {Component} from 'react'import {NavLink} from 'react-router-dom'export default class MyNavLink extends Component {  render() {    // console.log(this.props);    return (      <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>    )  }}
    
  • pages/About/index.jsx

    import React, {Component} from 'react'export default class About extends Component {  render() {    // console.log('About组件收到的props是',this.props);    return (      <h3>我是About的内容</h3>    )  }}
    
  • pages/Home/index.jsx

    import React, {Component} from 'react'import MyNavLink from '../../components/MyNavLink'import {Route, Switch, Redirect} from 'react-router-dom'import News from './News'import Message from './Message'export default class Home extends Component {  render() {    return (      <div>        <h3>我是Home的内容</h3>        <div>          <ul className="nav nav-tabs">            <li>              <MyNavLink to="/home/news">News</MyNavLink>            </li>            <li>              <MyNavLink to="/home/message">Message</MyNavLink>            </li>          </ul>          {/* 注册路由 */}          <Switch>            <Route path="/home/news" component={News}/>            <Route path="/home/message" component={Message}/>            <Redirect to="/home/news"/>          </Switch>        </div>      </div>    )  }}
    
  • pages/Home/Message/index.jsx

    import React, {Component} from 'react'import {Link, Route} from 'react-router-dom'import Detail from './Detail'export default class Message extends Component {  state = {    messageArr: [      {id: '01', title: '消息1'},      {id: '02', title: '消息2'},      {id: '03', title: '消息3'},    ]  }  replaceShow = (id, title) => {    //replace跳转+携带params参数    //this.props.history.replace(`/home/message/detail/${id}/${title}`)    //replace跳转+携带search参数    // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)    //replace跳转+携带state参数    this.props.history.replace(`/home/message/detail`, {id, title})  }  pushShow = (id, title) => {    //push跳转+携带params参数    // this.props.history.push(`/home/message/detail/${id}/${title}`)    //push跳转+携带search参数    // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)    //push跳转+携带state参数    this.props.history.push(`/home/message/detail`, {id, title})  }  back = () => {    this.props.history.goBack()  }  forward = () => {    this.props.history.goForward()  }  go = () => {    this.props.history.go(-2)  }  render() {    const {messageArr} = this.state    return (      <div>        <ul>          {            messageArr.map((msgObj) => {              return (                <li key={msgObj.id}>                  {/* 向路由组件传递params参数 */}                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递search参数 */}                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}                  {/* 向路由组件传递state参数 */}                  <Link to={{                    pathname: '/home/message/detail',                    state: {id: msgObj.id, title: msgObj.title}                  }}>{msgObj.title}</Link>                  &nbsp;                  <button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>                  &nbsp;                  <button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>                </li>              )            })          }        </ul>        <hr/>        {/* 声明接收params参数 */}        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}        {/* search参数无需声明接收,正常注册路由即可 */}        {/* <Route path="/home/message/detail" component={Detail}/> */}        {/* state参数无需声明接收,正常注册路由即可 */}        <Route path="/home/message/detail" component={Detail}/>        <button onClick={this.back}>回退</button>        &nbsp;        <button onClick={this.forward}>前进</button>        &nbsp;        <button onClick={this.go}>go</button>      </div>    )  }}
    
  • pages/Home/Message/Detail/index.jsx

    import React, {Component} from 'react'// import qs from 'querystring'const DetailData = [  {id: '01', content: '你好,中国'},  {id: '02', content: '你好,尚硅谷'},  {id: '03', content: '你好,未来的自己'}]export default class Detail extends Component {  render() {    console.log(this.props);    // 接收params参数    // const {id,title} = this.props.match.params     // 接收search参数    // const {search} = this.props.location    // const {id,title} = qs.parse(search.slice(1))    // 接收state参数    const {id, title} = this.props.location.state || {}    const findResult = DetailData.find((detailObj) => {      return detailObj.id === id    }) || {}    return (      <ul>        <li>ID:{id}</li>        <li>TITLE:{title}</li>        <li>CONTENT:{findResult.content}</li>      </ul>    )  }}
    
  • pages/Home/News/index.jsx

    import React, {Component} from 'react'export default class News extends Component {  /* componentDidMount(){    setTimeout(()=>{      this.props.history.push('/home/message')    },2000)  } */  render() {    return (      <ul>        <li>news001</li>        <li>news002</li>        <li>news003</li>      </ul>    )  }}
    

5.9 BrowserRouter与HashRouter

  1. 底层原理不一样:
    • BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    • HashRouter使用的是URL的哈希值。
  2. path表现形式不一样
    • BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    • HashRouter的路径包含#,例如:localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响
    • BrowserRouter没有任何影响,因为state保存在history对象中。
    • HashRouter刷新后会导致路由state参数的丢失!!!
  4. 备注:HashRouter可以用于解决一些路径错误相关的问题。

第6章:React UI组件库

6.1.流行的开源React UI组件库

6.1.1. material-ui(国外)

  1. 官网: http://www.material-ui.com/#/

  2. github: https://github.com/callemall/material-ui

6.1.2. ant-design(国内蚂蚁金服)

  1. 官网: https://ant.design/index-cn

  2. Github: https://github.com/ant-design/ant-design/

  3. 基本使用

    • 安装

      yarn add antd

    • 使用

      • 引入组件

        import {Button} from 'antd'

      • 引入样式

        import 'antd/dist/antd.css'

6.1.3. 按需引入样式

在 create-react-app 中使用

$ yarn add react-app-rewired customize-cra
/* package.json */
"scripts": {
    -   "start": "react-scripts start",
    +   "start": "react-app-rewired start",
    -   "build": "react-scripts build",
    +   "build": "react-app-rewired build",
    -   "test": "react-scripts test",
    +   "test": "react-app-rewired test",
}

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。

module.exports = function override(config, env) {
  // do stuff with the webpack config...
  return config;
};

使用 babel-plugin-impor

注意:antd 默认支持基于 ES module 的 tree shaking,js 代码部分不使用这个插件也会有按需加载的效果。

babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件(原理),现在我们尝试安装它并修改 config-overrides.js 文件。

$ yarn add babel-plugin-import
+ const { override, fixBabelImports } = require('customize-cra');

- module.exports = function override(config, env) {
-   // do stuff with the webpack config...
-   return config;
- };
+ module.exports = override(
+   fixBabelImports('import', {
+     libraryName: 'antd',
+     libraryDirectory: 'es',
+     style: 'css',
+   }),
+ );

然后移除前面在 src/App.css 里全量添加的 @import '~antd/dist/antd.css'; 样式代码,并且按下面的格式引入模块。

// src/App.js
import React, { Component } from 'react';
- import Button from 'antd/es/button';
+ import { Button } from 'antd';
import './App.css';

class App extends Component {
    render() {
        return (
            <div className="App">
            	<Button type="primary">Button</Button>
            </div>
        );
    }
}

export default App;

最后重启 yarn start 访问页面,antd 组件的 js 和 css 代码都会按需加载,你在控制台也不会看到这样的警告信息。关于按需加载的原理和其他方式可以阅读这里

6.1.4. antd自定义主题

按照 配置主题 的要求,自定义主题需要用到 less 变量覆盖功能。我们可以引入 customize-cra 中提供的 less 相关的函数 addLessLoader 来帮助加载 less 样式,同时修改 config-overrides.js 文件如下。

$ yarn add less less-loader
- const { override, fixBabelImports } = require('customize-cra');
+ const { override, fixBabelImports, addLessLoader } = require('customize-cra');

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
-   style: 'css',
+   style: true,
  }),
+ addLessLoader({
+   lessOptions:{
+       javascriptEnabled: true,
+       modifyVars: { '@primary-color': '#1DA57A' },
+   }
+ }),
);

这里利用了 less-loadermodifyVars 来进行主题配置,变量和其他配置方式可以参考 配置主题 文档。

修改后重启 yarn start,如果看到一个绿色的按钮就说明配置成功了。

你也可以使用 cracocraco-antd 来实现和 customize-cra 一样的修改 create-react-app 配置的功能。

6.1.5. antd的按需引入+自定主题

  1. 安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader

  2. 修改package.json

    ....
    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test",
        "eject": "react-scripts eject"
    },
    ....
    
  3. 根目录下创建config-overrides.js

    //配置具体的修改规则
    const { override, fixBabelImports,addLessLoader} = require('customize-cra');
    module.exports = override(
        fixBabelImports('import', {
            libraryName: 'antd',
            libraryDirectory: 'es',
            style: true,
        }),
        addLessLoader({
            lessOptions:{
                javascriptEnabled: true,
                modifyVars: { '@primary-color': 'green' },
            }
        }),
    );
    
  4. 备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css’应该删掉

第7章:redux

7.1. redux理解

7.1.1. 学习文档

  1. 英文文档: https://redux.js.org/

  2. 中文文档: http://www.redux.org.cn/

  3. Github: https://github.com/reactjs/redux

7.1.2. redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。

  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。

  3. 作用: 集中式管理react应用中多个组件共享的状态。

7.1.3. 什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。

  2. 一个组件需要改变另一个组件的状态(通信)。

  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

7.1.4. redux工作流程

在这里插入图片描述

7.2. redux的三个核心概念

7.2.1. action

  1. 动作的对象

  2. 包含2个属性

    • type:标识属性, 值为字符串, 唯一, 必要属性
    • data:数据属性, 值类型任意, 可选属性
  3. 例子:

    { type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
    

7.2.2. reducer

  1. 用于初始化状态、加工状态。

  2. 加工时,根据旧的state和action, 产生新的state的纯函数。

7.2.3. store

  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?

    • import {createStore} from ‘redux’
    • import reducer from ‘./reducers’
    • const store = createStore(reducer)
  3. 此对象的功能?

    • getState(): 得到state
    • dispatch(action): 分发action, 触发reducer调用, 产生新的state
    • subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

7.3. redux的核心API

7.3.1. createstore()

作用:创建包含指定reducer的store对象

7.3.2. store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    • state
    • reducer
  3. 核心方法:

    • getState()
    • dispatch(action)
    • subscribe(listener)
  4. 具体编码:

    • store.getState()
    • store.dispatch({type:‘INCREMENT’, number})
    • store.subscribe(render)

7.3.3. applyMiddleware()

作用:应用上基于redux的中间件(插件库)

7.3.4. combineReducers()

作用:合并多个reducer函数

7.4. 使用redux编写应用

效果

在这里插入图片描述

7.5. redux异步编程

7.5.1理解:

  1. redux默认是不能进行异步处理的,

  2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

7.5.2. 使用异步中间件

npm install --save redux-thunk

7.6. react-redux

7.6.1. 理解

  1. 一个react插件库

  2. 专门用来简化react应用中使用redux

7.6.2. react-Redux将所有组件分成两大类

  1. UI组件

    • 只负责 UI 的呈现,不带有任何业务逻辑
    • 通过props接收数据(一般数据和函数)
    • 不使用任何 Redux 的 API
    • 一般保存在components文件夹下
  2. 容器组件

    • 负责管理数据和业务逻辑,不负责UI的呈现
    • 使用 Redux 的 API
    • 一般保存在containers文件夹下

7.6.3. 相关API

  1. Provider:让所有组件都可以得到state数

    <Provider store={store}>
      <App />
    </Provider>
    
  2. connect:用于包装 UI 组件生成容器组件

    import { connect } from 'react-redux'
      connect(
        mapStateToprops,
        mapDispatchToProps
      )(Counter)
    
  3. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性

    const mapStateToprops = function (state) {
      return {
        value: state
      }
    }
    
  4. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

7.7. 使用上redux调试工具

7.7.1. 安装chrome浏览器插件

在这里插入图片描述

7.7.2. 下载工具依赖包

npm install --save-dev redux-devtools-extension

7.8. 纯函数和高阶函数

7.8.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束

    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

7.8.2. 高阶函数

  1. 理解: 一类特别的函数

    • 情况1: 参数是函数
    • 情况2: 返回是函数
  2. 常见的高阶函数:

    • 定时器设置函数
    • 数组的forEach()/map()/filter()/reduce()/find()/bind()
    • promise
    • react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能

7.9. Redux案例 —— 求和案例

7.9.1. 纯React版

—components

​ —Count

​ —index.jsx

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './components/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    
    export default class Count extends Component {
    
      state = {count: 0}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        const {count} = this.state
        this.setState({count: count + value * 1})
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        const {count} = this.state
        this.setState({count: count - value * 1})
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        const {count} = this.state
        if (count % 2 !== 0) {
          this.setState({count: count + value * 1})
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        const {count} = this.state
        setTimeout(() => {
          this.setState({count: count + value * 1})
        }, 500)
      }
    
      render() {
        return (
          <div>
            <h1>当前求和为:{this.state.count}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    

7.9.2. Redux精简版

  1. 去除Count组件自身的状态

  2. src下建立:

    -redux

    ​ -store.js

    -count_reducer.js

  3. store.js:

    • 引入redux中的createStore函数,创建一个store
    • createStore调用时要传入一个为其服务的reducer
    • 记得暴露store对象
  4. count_reducer.js:

    • reducer的本质是一个函数,接收:preState,action,返回加工后的状态

    • reducer有两个作用:初始化状态,加工状态

    • reducer被第一次调用时,是store自动触发的,

      传递的preState是undefined,

      传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

  5. 在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>

    备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

—components

​ —Count

​ —index.jsx

​ —redux

​ —count_reducer.js

​ —store.js

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './components/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
    store.subscribe(() => {
      ReactDOM.render(<App/>, document.getElementById('root'))
    })
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    //引入store,用于获取redux中保存状态
    import store from '../../redux/store'
    
    export default class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      /* componentDidMount(){
        //检测redux中状态的变化,只要变化,就调用render
        store.subscribe(()=>{
          this.setState({})
        })
      } */
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        store.dispatch({type: 'increment', data: value * 1})
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        store.dispatch({type: 'decrement', data: value * 1})
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        const count = store.getState()
        if (count % 2 !== 0) {
          store.dispatch({type: 'increment', data: value * 1})
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        setTimeout(() => {
          store.dispatch({type: 'increment', data: value * 1})
        }, 500)
      }
    
      render() {
        return (
          <div>
            <h1>当前求和为:{store.getState()}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
  • redux/count_reducer.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log(preState);
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case 'increment': //如果是加
          return preState + data
        case 'decrement': //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './count_reducer'
    //暴露store
    export default createStore(countReducer)
    

7.9.3. Redux完整版

新增文件:

  1. count_action.js 专门用于创建action对象
  2. constant.js 放置容易写错的type值

—components

​ —Count

​ —index.jsx

​ —redux

​ —constant.js

​ —count_actions.js

​ —count_reducer.js

​ —store.js

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './components/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
    store.subscribe(() => {
      ReactDOM.render(<App/>, document.getElementById('root'))
    })
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    //引入store,用于获取redux中保存状态
    import store from '../../redux/store'
    //引入actionCreator,专门用于创建action对象
    import {createIncrementAction, createDecrementAction} from '../../redux/count_action'
    
    export default class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      /* componentDidMount(){
        //检测redux中状态的变化,只要变化,就调用render
        store.subscribe(()=>{
          this.setState({})
        })
      } */
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        store.dispatch(createIncrementAction(value * 1))
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        store.dispatch(createDecrementAction(value * 1))
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        const count = store.getState()
        if (count % 2 !== 0) {
          store.dispatch(createIncrementAction(value * 1))
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        setTimeout(() => {
          store.dispatch(createIncrementAction(value * 1))
        }, 500)
      }
    
      render() {
        return (
          <div>
            <h1>当前求和为:{store.getState()}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  • redux/count_action.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
  • redux/count_reducer.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log(preState);
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './count_reducer'
    //暴露store
    export default createStore(countReducer)
    

7.9.4. 异步action版

  1. 明确:延迟的动作不想交给组件自身,想交给action
  2. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
  3. 具体编码:
    • yarn add redux-thunk,并配置在store中
    • 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
    • 异步任务有结果后,分发一个同步的action去真正操作数据。
  4. 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

—components

​ —Count

​ —index.jsx

​ —redux

​ —constant.js

​ —count_actions.js

​ —count_reducer.js

​ —store.js

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './components/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
    store.subscribe(() => {
      ReactDOM.render(<App/>, document.getElementById('root'))
    })
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    //引入store,用于获取redux中保存状态
    import store from '../../redux/store'
    //引入actionCreator,专门用于创建action对象
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/count_action'
    
    export default class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      /* componentDidMount(){
        //检测redux中状态的变化,只要变化,就调用render
        store.subscribe(()=>{
          this.setState({})
        })
      } */
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        store.dispatch(createIncrementAction(value * 1))
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        store.dispatch(createDecrementAction(value * 1))
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        const count = store.getState()
        if (count % 2 !== 0) {
          store.dispatch(createIncrementAction(value * 1))
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        // setTimeout(()=>{
        store.dispatch(createIncrementAsyncAction(value * 1, 500))
        // },500)
      }
    
      render() {
        return (
          <div>
            <h1>当前求和为:{store.getState()}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  • redux/count_action.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(createIncrementAction(data))
        }, time)
      }
    }
    
  • redux/count_reducer.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log(preState);
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './count_reducer'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    //暴露store
    export default createStore(countReducer, applyMiddleware(thunk))
    

7.9.5. react-redux基本使用

  1. 明确两个概念:

    • UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
    • 容器组件:负责和redux通信,将结果交给UI组件。
  2. 使用前需要安装react-redux。

    yarn add react-redux

  3. 如何创建一个容器组件————靠react-redux 的 connect函数

    • connect(mapStateToProps,mapDispatchToProps)(UI组件)
      • mapStateToProps:映射状态,返回值是一个对象
      • mapDispatchToProps:映射操作状态的方法,返回值是一个对象
  4. 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

  5. 备注2:mapDispatchToProps,也可以是一个对象

—components

​ —Count

​ —index.jsx

​ —containers

​ —Count

​ —index.jsx

​ —redux

​ —constant.js

​ —count_actions.js

​ —count_reducer.js

​ —store.js

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './containers/Count'
    import store from './redux/store'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            {/* 给容器组件传递store */}
            <Count store={store}/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
    //监测redux中状态的改变,如redux的状态发生了改变,那么重新渲染App组件
    store.subscribe(() => {
      ReactDOM.render(<App/>, document.getElementById('root'))
    })
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    
    export default class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        this.props.jia(value * 1)
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        this.props.jian(value * 1)
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        if (this.props.count % 2 !== 0) {
          this.props.jia(value * 1)
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        this.props.jiaAsync(value * 1, 500)
      }
    
      render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
  • containers/Count/index.jsx

    //引入Count的UI组件
    import CountUI from '../../components/Count'
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/count_action'
    
    //引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    /* 
    	1.mapStateToProps函数返回的是一个对象;
    	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    	3.mapStateToProps用于传递状态
    */
    function mapStateToProps(state) {
      return {count: state}
    }
    
    /* 
    	1.mapDispatchToProps函数返回的是一个对象;
    	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    	3.mapDispatchToProps用于传递操作状态的方法
    */
    function mapDispatchToProps(dispatch) {
      return {
        jia: number => dispatch(createIncrementAction(number)),
        jian: number => dispatch(createDecrementAction(number)),
        jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time)),
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  • redux/count_action.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(createIncrementAction(data))
        }, time)
      }
    }
    
  • redux/count_reducer.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log(preState);
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './count_reducer'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    //暴露store
    export default createStore(countReducer, applyMiddleware(thunk))
    

7.9.6. react-redux优化

  1. 容器组件和UI组件整合一个文件

  2. 无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。

  3. 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

  4. mapDispatchToProps也可以简单的写成一个对象

  5. 一个组件要和redux“打交道”要经过哪几步?

    • 定义好UI组件—不暴露

    • 引入connect生成一个容器组件,并暴露,写法如下:

      connect(
          state => ({key:value}), //映射状态
          {key:xxxxxAction} //映射操作状态的方法
      )(UI组件)
      
    • 在UI组件中通过this.props.xxxxxxx读取和操作状态

—components

​ —Count

​ —index.jsx

​ —redux

​ —constant.js

​ —count_actions.js

​ —count_reducer.js

​ —store.js

​ —App.jsx

​ —index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './containers/Count'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    )
    
  • components/Count/index.jsx

    import React, {Component} from 'react'
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/count_action'
    //引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    //定义UI组件
    class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        this.props.jia(value * 1)
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        this.props.jian(value * 1)
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        if (this.props.count % 2 !== 0) {
          this.props.jia(value * 1)
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        this.props.jiaAsync(value * 1, 500)
      }
    
      render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
          <div>
            <h1>当前求和为:{this.props.count}</h1>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      state => ({count: state}),
    
      //mapDispatchToProps的一般写法
      /* dispatch => ({
        jia:number => dispatch(createIncrementAction(number)),
        jian:number => dispatch(createDecrementAction(number)),
        jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
      }) */
    
      //mapDispatchToProps的简写
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
    )(Count)
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
  • redux/count_action.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(createIncrementAction(data))
        }, time)
      }
    }
    
  • redux/count_reducer.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from './constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log(preState);
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './count_reducer'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    //暴露store
    export default createStore(countReducer, applyMiddleware(thunk))
    

7.9.7. react-redux数据共享版

  1. 定义一个Pserson组件,和Count组件通过redux共享数据。
  2. 为Person组件编写:reducer、action,配置constant常量。
  3. 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,
    合并后的总状态是一个对象!!!
  4. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

—containers

​ —Count

​ —index.jsx

​ —Person

​ —index.jsx

—redux

​ —actions

​ —count.js

​ —person.js

​ —reducers

​ —count.js

​ —person.js

​ —constant.js

​ —store.js

App.jsx

index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './containers/Count'
    import Person from './containers/Person'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
            <hr/>
            <Person/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    )
    
  • containers/Count/index.jsx

    import React, {Component} from 'react'
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/actions/count'
    //引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    //定义UI组件
    class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        this.props.jia(value * 1)
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        this.props.jian(value * 1)
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        if (this.props.count % 2 !== 0) {
          this.props.jia(value * 1)
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        this.props.jiaAsync(value * 1, 500)
      }
    
      render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
          <div>
            <h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
            <h4>当前求和为:{this.props.count}</h4>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      state => ({
        count: state.he,
        renshu: state.rens.length
      }),
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
    )(Count)
    
  • containers/Person/index.jsx

    import React, {Component} from 'react'
    import {nanoid} from 'nanoid'
    import {connect} from 'react-redux'
    import {createAddPersonAction} from '../../redux/actions/person'
    
    class Person extends Component {
    
      addPerson = () => {
        const name = this.nameNode.value
        const age = this.ageNode.value
        const personObj = {id: nanoid(), name, age}
        this.props.jiaYiRen(personObj)
        this.nameNode.value = ''
        this.ageNode.value = ''
      }
    
      render() {
        return (
          <div>
            <h2>我是Person组件,上方组件求和为{this.props.he}</h2>
            <input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
            <input ref={c => this.ageNode = c} type="text" placeholder="输入年龄"/>
            <button onClick={this.addPerson}>添加</button>
            <ul>
              {
                this.props.yiduiren.map((p) => {
                  return <li key={p.id}>{p.name}--{p.age}</li>
                })
              }
            </ul>
          </div>
        )
      }
    }
    
    export default connect(
      state => ({yiduiren: state.rens, he: state.he}),//映射状态
      {jiaYiRen: createAddPersonAction}//映射操作状态的方法
    )(Person)
    
  • redux/actions/count.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(createIncrementAction(data))
        }, time)
      }
    }
    
  • redux/actions/person.js

    import {ADD_PERSON} from '../constant'
    
    //创建增加一个人的action动作对象
    export const createAddPersonAction = personObj => ({type: ADD_PERSON, data: personObj})
    
  • redux/reducers/count.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log('countReducer@#@#@#');
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/reducers/person.js

    import {ADD_PERSON} from '../constant'
    
    //初始化人的列表
    const initState = [{id: '001', name: 'tom', age: 18}]
    
    export default function personReducer(preState = initState, action) {
      // console.log('personReducer@#@#@#');
      const {type, data} = action
      switch (type) {
        case ADD_PERSON: //若是添加一个人
          return [data, ...preState]
        default:
          return preState
      }
    }
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    export const ADD_PERSON = 'add_person'
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware, combineReducers} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './reducers/count'
    //引入为Count组件服务的reducer
    import personReducer from './reducers/person'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    
    //汇总所有的reducer变为一个总的reducer
    const allReducer = combineReducers({
      he: countReducer,
      rens: personReducer
    })
    
    //暴露store
    export default createStore(allReducer, applyMiddleware(thunk))
    

7.9.8. react-redux开发者工具

  1. yarn add redux-devtools-extension

  2. store中进行配置

    import {composeWithDevTools} from 'redux-devtools-extension'
    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
    

—containers

​ —Count

​ —index.jsx

​ —Person

​ —index.jsx

—redux

​ —actions

​ —count.js

​ —person.js

​ —reducers

​ —count.js

​ —person.js

​ —constant.js

​ —store.js

App.jsx

index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './containers/Count'
    import Person from './containers/Person'
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
            <hr/>
            <Person/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    )
    
  • containers/Count/index.jsx

    import React, {Component} from 'react'
    //引入action
    import {
      createIncrementAction,
      createDecrementAction,
      createIncrementAsyncAction
    } from '../../redux/actions/count'
    //引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    //定义UI组件
    class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        this.props.jia(value * 1)
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        this.props.jian(value * 1)
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        if (this.props.count % 2 !== 0) {
          this.props.jia(value * 1)
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        this.props.jiaAsync(value * 1, 500)
      }
    
      render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
          <div>
            <h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
            <h4>当前求和为:{this.props.count}</h4>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      state => ({
        count: state.he,
        renshu: state.rens.length
      }),
      {
        jia: createIncrementAction,
        jian: createDecrementAction,
        jiaAsync: createIncrementAsyncAction,
      }
    )(Count)
    
  • containers/Person/index.jsx

    import React, {Component} from 'react'
    import {nanoid} from 'nanoid'
    import {connect} from 'react-redux'
    import {createAddPersonAction} from '../../redux/actions/person'
    
    class Person extends Component {
    
      addPerson = () => {
        const name = this.nameNode.value
        const age = this.ageNode.value * 1
        const personObj = {id: nanoid(), name, age}
        this.props.jiaYiRen(personObj)
        this.nameNode.value = ''
        this.ageNode.value = ''
      }
    
      render() {
        return (
          <div>
            <h2>我是Person组件,上方组件求和为{this.props.he}</h2>
            <input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
            <input ref={c => this.ageNode = c} type="text" placeholder="输入年龄"/>
            <button onClick={this.addPerson}>添加</button>
            <ul>
              {
                this.props.yiduiren.map((p) => {
                  return <li key={p.id}>{p.name}--{p.age}</li>
                })
              }
            </ul>
          </div>
        )
      }
    }
    
    export default connect(
      state => ({yiduiren: state.rens, he: state.he}),//映射状态
      {jiaYiRen: createAddPersonAction}//映射操作状态的方法
    )(Person)
    
  • redux/actions/count.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const createIncrementAction = data => ({type: INCREMENT, data})
    export const createDecrementAction = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const createIncrementAsyncAction = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(createIncrementAction(data))
        }, time)
      }
    }
    
  • redux/actions/person.js

    import {ADD_PERSON} from '../constant'
    
    //创建增加一个人的action动作对象
    export const createAddPersonAction = personObj => ({type:ADD_PERSON,data:personObj})
    
  • redux/reducers/count.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log('countReducer@#@#@#');
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/reducers/person.js

    import {ADD_PERSON} from '../constant'
    
    //初始化人的列表
    const initState = [{id: '001', name: 'tom', age: 18}]
    
    export default function personReducer(preState = initState, action) {
      // console.log('personReducer@#@#@#');
      const {type, data} = action
      switch (type) {
        case ADD_PERSON: //若是添加一个人
          //preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
          return [data, ...preState]
        default:
          return preState
      }
    }
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    export const ADD_PERSON = 'add_person'
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware, combineReducers} from 'redux'
    //引入为Count组件服务的reducer
    import countReducer from './reducers/count'
    //引入为Count组件服务的reducer
    import personReducer from './reducers/person'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    //引入redux-devtools-extension
    import {composeWithDevTools} from 'redux-devtools-extension'
    
    //汇总所有的reducer变为一个总的reducer
    const allReducer = combineReducers({
      he: countReducer,
      rens: personReducer
    })
    
    //暴露store 
    export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
    

7.9.9. 最终版

  1. 所有变量名字要规范,尽量触发对象的简写形式。
  2. reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

—containers

​ —Count

​ —index.jsx

​ —Person

​ —index.jsx

—redux

​ —actions

​ —count.js

​ —person.js

​ —reducers

​ —count.js

​ —index.js

​ —person.js

​ —constant.js

​ —store.js

App.jsx

index.js

  • App.jsx

    import React, {Component} from 'react'
    import Count from './containers/Count' //引入的Count的容器组件
    import Person from './containers/Person' //引入的Person的容器组件
    
    export default class App extends Component {
      render() {
        return (
          <div>
            <Count/>
            <hr/>
            <Person/>
          </div>
        )
      }
    }
    
  • index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store'
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
      <Provider store={store}>
        <App/>
      </Provider>,
      document.getElementById('root')
    )
    
  • containers/Count/index.jsx

    import React, {Component} from 'react'
    //引入action
    import {
      increment,
      decrement,
      incrementAsync
    } from '../../redux/actions/count'
    //引入connect用于连接UI组件与redux
    import {connect} from 'react-redux'
    
    //定义UI组件
    class Count extends Component {
    
      state = {carName: '奔驰c63'}
    
      //加法
      increment = () => {
        const {value} = this.selectNumber
        this.props.increment(value * 1)
      }
      //减法
      decrement = () => {
        const {value} = this.selectNumber
        this.props.decrement(value * 1)
      }
      //奇数再加
      incrementIfOdd = () => {
        const {value} = this.selectNumber
        if (this.props.count % 2 !== 0) {
          this.props.increment(value * 1)
        }
      }
      //异步加
      incrementAsync = () => {
        const {value} = this.selectNumber
        this.props.incrementAsync(value * 1, 500)
      }
    
      render() {
        //console.log('UI组件接收到的props是',this.props);
        return (
          <div>
            <h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
            <h4>当前求和为:{this.props.count}</h4>
            <select ref={c => this.selectNumber = c}>
              <option value="1">1</option>
              <option value="2">2</option>
              <option value="3">3</option>
            </select>&nbsp;
            <button onClick={this.increment}>+</button>
            &nbsp;
            <button onClick={this.decrement}>-</button>
            &nbsp;
            <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
            &nbsp;
            <button onClick={this.incrementAsync}>异步加</button>
            &nbsp;
          </div>
        )
      }
    }
    
    //使用connect()()创建并暴露一个Count的容器组件
    export default connect(
      state => ({
        count: state.count,
        personCount: state.persons.length
      }),
      {increment, decrement, incrementAsync}
    )(Count)
    
  • containers/Person/index.jsx

    import React, {Component} from 'react'
    import {nanoid} from 'nanoid'
    import {connect} from 'react-redux'
    import {addPerson} from '../../redux/actions/person'
    
    class Person extends Component {
    
      addPerson = () => {
        const name = this.nameNode.value
        const age = this.ageNode.value * 1
        const personObj = {id: nanoid(), name, age}
        this.props.addPerson(personObj)
        this.nameNode.value = ''
        this.ageNode.value = ''
      }
    
      render() {
        return (
          <div>
            <h2>我是Person组件,上方组件求和为{this.props.count}</h2>
            <input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
            <input ref={c => this.ageNode = c} type="text" placeholder="输入年龄"/>
            <button onClick={this.addPerson}>添加</button>
            <ul>
              {
                this.props.persons.map((p) => {
                  return <li key={p.id}>{p.name}--{p.age}</li>
                })
              }
            </ul>
          </div>
        )
      }
    }
    
    export default connect(
      state => ({
        persons: state.persons,
        count: state.count
      }),//映射状态
      {addPerson}//映射操作状态的方法
    )(Person)
    
  • redux/actions/count.js

    /* 
    	该文件专门为Count组件生成action对象
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    //同步action,就是指action的值为Object类型的一般对象
    export const increment = data => ({type: INCREMENT, data})
    export const decrement = data => ({type: DECREMENT, data})
    
    //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
    export const incrementAsync = (data, time) => {
      return (dispatch) => {
        setTimeout(() => {
          dispatch(increment(data))
        }, time)
      }
    }
    
  • redux/actions/person.js

    import {ADD_PERSON} from '../constant'
    
    //创建增加一个人的action动作对象
    export const addPerson = personObj => ({type: ADD_PERSON, data: personObj})
    
  • redux/reducers/count.js

    /* 
    	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
    	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
    */
    import {INCREMENT, DECREMENT} from '../constant'
    
    const initState = 0 //初始化状态
    export default function countReducer(preState = initState, action) {
      // console.log('countReducer@#@#@#');
      //从action对象中获取:type、data
      const {type, data} = action
      //根据type决定如何加工数据
      switch (type) {
        case INCREMENT: //如果是加
          return preState + data
        case DECREMENT: //若果是减
          return preState - data
        default:
          return preState
      }
    }
    
  • redux/reducers/index.js

    /* 
    	该文件用于汇总所有的reducer为一个总的reducer
    */
    //引入combineReducers,用于汇总多个reducer
    import {combineReducers} from 'redux'
    //引入为Count组件服务的reducer
    import count from './count'
    //引入为Person组件服务的reducer
    import persons from './person'
    
    //汇总所有的reducer变为一个总的reducer
    export default combineReducers({
      count,
      persons
    })
    
  • redux/reducers/person.js

    import {ADD_PERSON} from '../constant'
    
    //初始化人的列表
    const initState = [{id: '001', name: 'tom', age: 18}]
    
    export default function personReducer(preState = initState, action) {
      // console.log('personReducer@#@#@#');
      const {type, data} = action
      switch (type) {
        case ADD_PERSON: //若是添加一个人
          //preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
          return [data, ...preState]
        default:
          return preState
      }
    }
    
  • redux/constant.js

    /* 
    	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
    */
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    export const ADD_PERSON = 'add_person'
    
  • redux/store.js

    /* 
    	该文件专门用于暴露一个store对象,整个应用只有一个store对象
    */
    
    //引入createStore,专门用于创建redux中最为核心的store对象
    import {createStore, applyMiddleware} from 'redux'
    //引入汇总之后的reducer
    import reducer from './reducers'
    //引入redux-thunk,用于支持异步action
    import thunk from 'redux-thunk'
    //引入redux-devtools-extension
    import {composeWithDevTools} from 'redux-devtools-extension'
    
    //暴露store 
    export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
    

7.9.10. 总结

  • 在src/index.js中引入store,并设置subscribe的原因是:监测redux中状态的改变,如redux的状态发生了改变,那么要重新渲染App组件。如果使用了react-redux,则不需要进行监视
  • 在src/index.js使用Provider包裹App,目的是让App所有的后代容器组件都能接收到store
  • 在src/App.jsx中,如果使用了react-redux,则需要向容器组件传递store作为props
  • mapStateToProps函数返回的是一个对象
    • 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    • mapStateToProps用于传递状态
  • mapDispatchToProp函数返回的是一个对象
    • 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    • mapDispatchToProps用于传递操作状态的方法
  • 利用serve包可以查看打包后的文件,全局安装后对应的html目录执行serve build命令即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KaiSarH

如果觉得文章不错,可以支持下~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值