React

目录

1.React的简介

1.1React是什么?

1.2React是谁开发的?

1.3为什么要学?

1.4React的特点 

2.React的基本使用 

2.1效果

2.2相关js库 

2.3hello-react 

2.3两种创建虚拟DOM的方式

2.3.1JSX创建虚拟DOM

2.3.2JS创建虚拟DOM

2.4虚拟DOM与真实DOM

3.JSX的语法规则

3.1JSX的小练习

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

4.1.模块

4.2.组件

4.3.模块化

4.4.组件化

5.React面向组件编程

5.1. 基本理解和使用

5.2. 组件实例的三大核心属性1- state

5.3. 组件三大核心属性2- props

5.3.1. 理解

5.3.2props的基本使用 

5.3.2内部读取某个属性值

5.3.2props中的属性值进行类型限制和必要性限制

5.3.3函数式组件中使用props(只能使用props,state和refs不能使用)

5.4. 组件三大核心属性3--refs与事件处理

5.4.1字符串形式的ref 

 5.4.2回调函数形式的ref

 5.4.3createRef的使用

5.4.4事件处理

5.5收集表单数据 

5.5.1非受控组件表单(现用现取)

 5.5.2受控组件表单

5.6React组件的生命周期

5.6.1引出组件的生命周期

5.6.2组件的生命周期(旧)

5.6.3组件的生命周期(新)

5.7虚拟DOM与DOM Diffing算法 

6.React应用(基于React脚手架)

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

6.1.1. react脚手架

6.1.2. 创建项目并启动 

6.1.3. react脚手架项目结构

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

 6.2. 组件的组合使用-TodoList

6.2.1子组件给父组件传递数据

7.React ajax

7.1. 理解

7.1.1. 前置说明

7.1.2. 常用的ajax请求库

7.2. axios

7.3消息订阅-发布机制 

7.4扩展:Fetch

7.React路由

7.1. 相关理解

7.1.1. SPA的理解

7.2. react-router-dom相关API 

7.2.1. 内置组件

7.3. 基本路由使用

7.4路由组件与一般组件 

7.5路由的严格匹配与模糊匹配 

7.6Navigate的使用     

7.8Redirect的使用    

7.9路由的严格匹配与模糊匹配 

7.10嵌套路由 

7.11向路由组件传递参数数据

7.12编程式路由导航

7.13withRouter的使用(把一般组件变成路由组件才有的api)

8.React UI组件库 

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

9.redux

9.1. redux理解

9.1.2. redux是什么 

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

9.1.4. redux工作流程 

9.2redux的三个核心概念

9.2.1action

9.2.3store

9.3redux的核心API

9.3.2store对象

9.3.3 applyMiddleware()

9.3.4 combineReducers

9.4. 使用redux编写应用 

9.5redux异步编程 

9.5.1理解:

9.5.2. 使用异步中间件

9.6. react-redux 

9.6.1. 理解

2.容器组件 

9.6.3. 相关API 

9.7. 使用上redux调试工具

9.7.1. 安装chrome浏览器插件

9.7.2. 下载工具依赖包

9.8. 纯函数和高阶函数

9.8.1. 纯函数

10.扩展知识 

10.1setState(两种写法)

10.2 lazyLoad

10.3. Hooks

10.3.1React Hook/Hooks是什么?

10.3.2. 三个常用的Hook

10.4. Fragment

10.5. Context

10.6. 组件优化

10.7. render props

10.8. 错误边界

10.9. 组件通信方式总结


1.React的简介

1.1React是什么?

React是用于构建用户界面的JavaScript库,react只关注界面(或者试图)

是一个将数据渲染为HTML视图的JavaScript库,你给我数据,我帮你渲染视图

需求:在页面上面展示学生的信息?

步骤:  1,发送请求获取数据(ajax)

          2,处理数据(遍历,过滤,整理格式)

          3,操作DOM呈现视图 (使用js,jquery将数据放入指定的节点里面)  [react来做]

1.2React是谁开发的?

Facebook开发,且开源

1.3为什么要学?

1.4React的特点 

 产生的问题:开始是两个人,页面上面产生了两个真实DOM,现在增加了一个人,三个人,页面上面产生了三个DOM,相当于每次操作都是产生新的DOM,没有进行复用

React的做法: 

 在数据进行变动的时候,虚拟DOM中会进行比较,假设遇到相同的就不会新的真实DOM,直接复用上次真实的DOM

2.React的基本使用 

2.1效果

2.2相关js 

  1. react.development.js:React核心库。
  2. react-dom.js:提供操作DOMreact扩展库。
  3. bbel.min.js:解析JSX语法代码转为JS代码的库。

2.3hello-react 

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

 

2.3两种创建虚拟DOM的方式

2.3.1JSX创建虚拟DOM

<!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">
      //1.创建虚拟DOM
		const VDOM = <h1 id="title">Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
  </script>
</body>
</html>

2.3.2JS创建虚拟DOM

<!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
		const VDOM = React.createElement('h1',{id:'title'},'Hello,React')
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>
</html>

需求:将hello,react写在<span>里面

jsx写法

原生js写法

 

原生js创建虚拟DOM太繁琐了,react打造的jsx简化了创建虚拟DOM的过程,所以REACT使用jsx

2.4虚拟DOM与真实DOM

 总结:关于虚拟DOM

1.本质是Object类型的对象(一般对象)
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

3.JSX的语法规则

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

<!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'))
	</script>
</body>
</html>

3.1JSX的小练习

 

一定注意区分:【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}

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

4.1.模块

1.理解:向外提供特定功能的js程序, 一般就是一个js文件
2.为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
3.作用:复用js, 简化js的编写, 提高js运行效率

4.2.组件

1.理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
2.为什么要用组件: 一个界面的功能更复杂
3.作用:复用编码, 简化项目编码, 提高运行效率

4.3.模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
4.4.组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

5.React面向组件编程

5.1. 基本理解和使用

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

5.1.2. 创建组件的两种方式

方式一:函数式组件(函数名就是组件名)

<!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">
    //1.创建函数式组件
     function MyComponent(){
            console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
			return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
		}
     //2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
  </script>
</body>
</html>

执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么? 

1.React解析组件标签,找到了MyComponent组件,找不到则报错
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。 

注意:

  1. 组件名必须首字母大写
  2. 组件必须有返回值
  3. 渲染组件的时候必须写组件标签,且标签闭合 

 方式二:类式组件

先复习下类的相关知识

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>1_类的基本知识</title>
</head>
<body>
	<script type="text/javascript" >
		/* 
			总结:
				1.类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
				2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
				3.类中所定义的方法,都放在了类的原型对象上,供实例去使用。
		*/
		//创建一个Person类
		class Person {
			//构造器方法
			constructor(name,age){
				//构造器中的this是谁?—— 类的实例对象
				this.name = name
				this.age = age
			}
			//一般方法
			speak(){
				//speak方法放在了哪里?——类的原型对象上,供实例使用
				//通过Person实例调用speak时,speak中的this就是Person实例
				console.log(`我叫${this.name},我年龄是${this.age}`);
			}
		}

		//创建一个Student类,继承于Person类
		class Student extends Person {
			constructor(name,age,grade){
				super(name,age)
				this.grade = grade
			}
			//重写从父类继承过来的方法
			speak(){
				console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
				this.study()
			}
			study(){
				//study方法放在了哪里?——类的原型对象上,供实例使用
				//通过Student实例调用study时,study中的this就是Student实例
				console.log('我很努力的学习');
			}
		}
	</script>
</body>
</html>

总结:
1.类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
3.类中所定义的方法,都放在了类的原型对象上,供实例去使用。

开始创建类式组件: 

类式组建创建的必要条件:

(1)必须继承React.Component

(2)必须有render方法

(3)必须要有return 返回值

<!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">
    //1.创建类式组件
    class MyComponent extends React.Component{
				render(){
					return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
				}
		}
     //2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
  </script>
</body>
</html>

执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

5.2. 组件实例的三大核心属性1- state

三大核心属性主要探讨的是类式组件,因为函数式组件没有实例对象,没有自己的this

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

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

 需求2

react里面的点击事件

将函数调用方法放入组件内

this问题: 

构造器中的this一定是当前实例的对象,构造器就是用来创建实例对象的

render()里面的this是因为react创建了组件实例对象,然后通过实例对象调用render()方法

changeWeather()中的this是undefined,说明changeWeather不是通过当前实例对象调用的,因为changeWeather()是点击事件onClick的回调,不是通过实例调用,是直接调用,所以这里的this就是undefined

在类中写的自定义的方法,一般都是作为事件的回调去调用的,这样他们的this就会丢失,this就是undefined

(方法一:)解决changeWeather中的this指向问题,在构造器中进行绑定函数

那么此时this就能找到了

<!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})
	
			}
		}
		//2.渲染组件到页面
		ReactDOM.render(<Weather/>,document.getElementById('test'))
				
	</script>
</body>
</html>

 总结:

构造器只调用一次,因为只创建了一次实例对象

render()调用多次,实例初始化完成后就是调用一次

changeWeather是点击几次就会调用几次

 (方法二)简写方式:寻找this

箭头函数没有自己的this,如果里面写了this就会去找箭头函数外侧的this作为自己的this,以后在类中自定义方法都要写成赋值语句+箭头函数 

5.3. 组件三大核心属性2- props

5.3.1. 理解

  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所有属性都保存在props中,其数据由外部传入
  3. 属性的初识值通常通过组件的属性值进行传递,并合并到组件实例对象的this.props中。

5.3.2props的基本使用 

5.3.2内部读取某个属性值

this.props.属性名

5.3.2props中的属性值进行类型限制和必要性限制

需要引入prop-types库

<!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>

 简写方式

5.3.3函数式组件中使用props(只能使用props,state和refs不能使用)

props与state的区别

props不能被其所在的组件修改,从父组件传递进来的属性不会在组件内部更改;state只能在所在组件内部更改,或在外部调用setState函数对状态进行间接修改。

5.4. 组件三大核心属性3--refs与事件处理

需求

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

  1. 点击按钮, 提示第一个输入框中的值

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

5.4.1字符串形式的ref 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>创建组件</title>
</head>
<body>
  <!-- 准备一个容器 -->
 <div id="test1"></div>
 <div id="test2"></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{
		//展示左侧输入框的数据
		showData = ()=>{
				const {input1} = this.refs
				alert(input1.value)
			}
			//展示右侧输入框的数据
			showData2 = ()=>{
				const {input2} = this.refs
				alert(input2.value)
			}
      render(){
				//读取状态
				console.log(this)
				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>
				)
			}
		}
		
     //2.渲染组件到页面
		ReactDOM.render(<MyComponent name="tom" sex="男" age="19"/>,document.getElementById('test1'))
 </script>
</body>
</html>

 5.4.2回调函数形式的ref

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>创建组件</title>
</head>
<body>
  <!-- 准备一个容器 -->
 <div id="test1"></div>
 <div id="test2"></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{
		//展示左侧输入框的数据
		showData = ()=>{
			const {input1} = this
			alert(input1.value)
			}
			//展示右侧输入框的数据
			showData2 = ()=>{
				const {input2} = this
				alert(input2.value)
			}
      render(){
				//读取状态
				console.log(this)
				return (
					<div>
						<input ref={(c)=>{this.input1 = c }} type="text" placeholder="点击按钮提示数据"/>&nbsp;
						<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
						<input ref={(c)=>{this.input2 = c }} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
					</div>
				)
			}
		}
     //2.渲染组件到页面
		ReactDOM.render(<MyComponent name="tom" sex="男" age="19"/>,document.getElementById('test1'))
 </script>
</body>
</html>

 5.4.3createRef的使用

<!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/>,document.getElementById('test'))
	</script>
</body>
</html>

5.4.4事件处理

总结 

(1).通过onXxx属性指定事件处理函数(注意大小写)
        a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
        b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ——为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ———不要过度使用ref

5.5收集表单数据 

需求: 定义一个包含表单的组件输入用户名密码后,

点击登录提示输入信息

5.5.1非受控组件表单(现用现取)

在虚拟DOM节点上声明一个ref属性,并且将创建好的引用赋值给这个ref属性.react会自动将输入框中输入的值放在实例的ref属性上

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>收集表单数据</title>
</head>
<body>
  <!-- 准备一个容器 -->
 <div id="test1"></div>
 <div id="test2"></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 LoginComponent extends React.Component{
			handleSubmit =(event) => {
				event.preventDefault() //阻止表单提交
				const {username,password} = this
				alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
			}
				render(){
					return(
						<form action="" 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>
					)
				}
		}
     //2.渲染组件到页面
		ReactDOM.render(<LoginComponent />,document.getElementById('test1'))
 </script>
</body>
</html>

 5.5.2受控组件表单

用state来获取和设置输入元素值的组件,称之为受控组件

<!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>

1、受控组件

受控组件依赖于状态

受控组件的修改会实时映射到状态值上,此时可以对输入的内容进行校验

受控组件只有继承React.Component才会有状态

受控组件必须要在表单上使用onChange事件来绑定对应的事件

上述代码的优化方式一(采用高阶函数,颗粒化):

<!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 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>

 上述代码的优化方式二

<!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>

5.6React组件的生命周期

5.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>

componentDidMount(){}组件挂载完毕调用,只会调用一次

componentWillUnmount(){}组件将要卸载,调用

render(){}组件初始化渲染,每次状态更新时调用,调用多次

都是通过组件的实例对象进行调用

5.6.2组件的生命周期(旧)

<!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>

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()  =====> 常用
            一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息 

5.6.3组件的生命周期(新)

<!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>

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

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()

 重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, : 清理定时器

5.7虚拟DOM与DOM Diffing算法 

 经典面试题:
  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也是可以的。

6.React应用(基于React脚手架)

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

6.1.1. react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
    1. 包含了所有需要的配置(语法检查、jsx编译、devServer
    2. 下载好了所有相关的依赖
    3. 可以直接运行一个简单效果
  2. react提供了一个用于创建react项目的脚手架库: create-react-app
  3. 项目的整体技术架构为:  react + webpack + es6 + eslint
  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

6.1.2. 创建项目并启动 

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

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

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

第四步,启动项目:npm start

6.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库的支持)

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

1. 拆分组件: 拆分界面,抽取组件

2. 实现静态组件: 使用组件实现静态页面效果

3. 实现动态组件

3.1 动态显示初始化数据

3.1.1 数据类型

3.1.2 数据名称

3.1.2 保存在哪个组件?

3.2 交互(从绑定事件监听开始)

 6.2. 组件的组合使用-TodoList

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

父组件给子组件传递数据

组件给父组件传递数据

父组件先给子组件传递一个函数,子组件再给父组件传递数据的时候,调用下这个函数即可

7.React ajax

7.1. 理解

7.1.1. 前置说明

  1. React本身只关注于界面, 并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax(或自己封装)

7.1.2. 常用的ajax请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用
  2. axios: 轻量级, 建议使用
      1. 封装XmlHttpRequest对象的ajax
      2.  promise风格
      3. 可以用在浏览器端和node服务器端

7.2. axios

GET请求

POST请求

7.3消息订阅-发布机制 

1.工具库: PubSubJS
2.下载: npm install pubsub-js --save
3.使用: 
        1)import PubSub from 'pubsub-js' //引入
        2)PubSub.subscribe('delete', function(data){ }); //订阅
        3)PubSub.publish('delete', data) //发布消息

7.4扩展:Fetch

7.React路由

7.1. 相关理解

7.1.1. SPA的理解

1.单页Web应用(single page web application,SPA)。
2.整个应用只有一个完整的页面。
3.点击页面中的链接不会刷新页面,只会做页面的局部更新。
4.数据都需要通过ajax请求获取, 并在前端异步展现。

7.1.2. 路由的理解 

1.什么是路由?

1.一个路由就是一个映射关系(key:value)
2.key为路径, value可能是function或component

2.路由分类

后端路由: 

1)理解: value是function, 用来处理客户端提交的请求。
2)注册路由: router.get(path, function(req, res))
3)工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由: 

1)浏览器端路由,value是component,用于展示页面内容。
2)注册路由: <Route path="/test" component={Test}>
3)工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

7.1.3. react-router-dom的理解 

注意安装的是npm add react-router-dom@5.2.0

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

7.2. react-router-dom相关API 

7.2.1. 内置组件

1.<BrowserRouter>
2.<HashRouter>
3.<Route>
4.<Redirect>
5.<Link>
6.<NavLink>
7.<Switch>

7.2.2. 其它(路由组件才有下面的的对象)

1.history对象
2.match对象
3.withRouter函数

7.3. 基本路由使用

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

7.4路由组件与一般组件 

1.写法不同:
          一般组件:<Demo/>
          路由组件:<Route path="/demo" component={Demo}/>
2.存放位置不同:
        一般组件:components
        路由组件:pages
3.接收到的props不同:
        一般组件:写组件标签时传递了什么,就能收到什么
         路由组件:接收到三个固定的属性
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"

7.5路由的严格匹配与模糊匹配 

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
 2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

{/* 注册路由 */}

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

 7.6Navigate的使用     

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

 <Routes> 

          <Route path="/about" element={<About/>}/>

          <Route path="/home" element={<Home/>}/>

           {/* 首次进入页面是重定向到/home */}

            <Route path="*" element={<Navigate to="/home"/>}></Route>

</Routes>

7.7Switch的使用 

1.通常情况下,path和component是一一对应的关系。

 2.Switch可以提高路由匹配效率(单一匹配)。

7.8Redirect的使用    

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

7.9路由的严格匹配与模糊匹配 

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
 2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
 3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

7.10嵌套路由 

1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的

7.11向路由组件传递参数数据

第一种:params传参

路由链接(携带参数):

<Link to='/demo/test/tom/18'}>详情</Link>

 注册路由(声明接收):

<Route path="/demo/test/:name/:age" component={Test}/>

 接收参数:this.props.match.params

<Link to={`/home/message/detail/${id}/${title}`}>Detail</Link>
<Route path="/home/message/detail/:id/:title" component={Detail}/>

第二种search参数 

路由链接(携带参数):

<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):

<Route path="/demo/test" component={Test}/>


接收参数:

this.props.location.search

第三种state参数 

路由链接(携带参数):

<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):

<Route path="/demo/test" component={Test}/>


 接收参数:

this.props.location.state

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

7.12编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退
                            -this.prosp.history.push()
                            -this.prosp.history.replace()
                            -this.prosp.history.goBack()
                            -this.prosp.history.goForward()
                            -this.prosp.history.go()

7.13withRouter的使用(把一般组件变成路由组件才有的api)

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件

8.React UI组件库 

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

8.1.1. material-ui(国外)

1.官网: http://www.material-ui.com/#/
2.github: https://github.com/callemall/material-ui

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

1.官网: https://ant.design/index-cn
2.Github: https://github.com/ant-design/ant-design/

9.redux

9.1. redux理解

9.1.1. 学习文档

1.英文文档: https://redux.js.org/
2.中文文档: http://www.redux.org.cn/
3.Github: https://github.com/reactjs/redux

9.1.2. redux是什么 

1.redux是一个专门用于做状态管理的JS库(不是react插件库)。
2.它可以用在react, angular, vue等项目中, 但基本与react配合使用。
3.作用: 集中式管理react应用中多个组件共享的状态。

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

1.某个组件的状态,需要让其他组件可以随时拿到(共享)。
2.一个组件需要改变另一个组件的状态(通信)。
3.总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

9.1.4. redux工作流程 

9.2redux的三个核心概念

9.2.1action

1.动作的对象
2.包含2个属性
     type:标识属性, 值为字符串, 唯一, 必要属性
     data:数据属性, 值类型任意, 可选属性

3.例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

9.2.2 reducer

1.用于初始化状态、加工状态。
2.加工时,根据旧的state和action, 产生新的state的纯函数。

9.2.3store

1.将state、action、reducer联系在一起的对象
2.如何得到此对象?
        1)import {createStore} from 'redux'
        2)import reducer from './reducers'
        3)const store = createStore(reducer)
3.此对象的功能?
        1)getState(): 得到state
        2)dispatch(action): 分发action, 触发reducer调用, 产生新的state
        3)subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

9.3redux的核心API

9.3.1 createstore()

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

9.3.2store对象

1.作用: redux库最核心的管理对象
2.它内部维护着:
   1)state
   2)reducer
3.核心方法:
   1)getState()
   2)dispatch(action)
   3)subscribe(listener)

4.具体编码:

   1)store.getState()
   2)store.dispatch({type:'INCREMENT', number})
   3)store.subscribe(render)

9.3.3 applyMiddleware()

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

9.3.4 combineReducers

作用:合并多个reducer函数

9.4. 使用redux编写应用 

## 1.求和案例_redux精简版
        (1).去除Count组件自身的状态
        (2).src下建立:
                        -redux
                            -store.js
                            -count_reducer.js

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

        (4).count_reducer.js:
                    1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
                    2).reducer有两个作用:初始化状态,加工状态
                    3).reducer被第一次调用时,是store自动触发的,
                                    传递的preState是undefined,
                                    传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

        (5).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>
                备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。


## 2.求和案例_redux完整版
        新增文件:
            1.count_action.js 专门用于创建action对象
            2.constant.js 放置容易写错的type值

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

## 4.求和案例_react-redux基本使用
            (1).明确两个概念:
                        1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
                        2).容器组件:负责和redux通信,将结果交给UI组件。
            (2).如何创建一个容器组件————靠react-redux 的 connect函数
                            connect(mapStateToProps,mapDispatchToProps)(UI组件)
                                -mapStateToProps:映射状态,返回值是一个对象
                                -mapDispatchToProps:映射操作状态的方法,返回值是一个对象
            (3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
            (4).备注2:mapDispatchToProps,也可以是一个对象


## 5.求和案例_react-redux优化
            (1).容器组件和UI组件整合一个文件
            (2).无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
            (3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
            (4).mapDispatchToProps也可以简单的写成一个对象
            (5).一个组件要和redux“打交道”要经过哪几步?
                            (1).定义好UI组件---不暴露
                            (2).引入connect生成一个容器组件,并暴露,写法如下:
                                    connect(
                                        state => ({key:value}), //映射状态
                                        {key:xxxxxAction} //映射操作状态的方法
                                    )(UI组件)
                            (4).在UI组件中通过this.props.xxxxxxx读取和操作状态

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

## 7.求和案例_react-redux开发者工具的使用
            (1).yarn add redux-devtools-extension
            (2).store中进行配置
                    import {composeWithDevTools} from 'redux-devtools-extension'
                    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

## 8.求和案例_react-redux最终版
            (1).所有变量名字要规范,尽量触发对象的简写形式。
            (2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

9.5redux异步编程 

9.5.1理解:

1.redux默认是不能进行异步处理的, 
2.某些时候应用中需要在redux中执行异步任务(ajax, 定时器)

9.5.2. 使用异步中间件

npm install  redux-thunk 

9.6. react-redux 

9.6.1. 理解

1.一个react插件库
2.专门用来简化react应用中使用redux

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

1.UI组件

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

2.容器组件 

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

9.6.3. 相关API 

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

2.connect:用于包装 UI 组件生成容器组件

3.mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性

4.mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

9.7. 使用上redux调试工具

9.7.1. 安装chrome浏览器插件

9.7.2. 下载工具依赖包

npm install  redux-devtools-extension 

9.8. 纯函数和高阶函数

9.8.1. 纯函数

1.一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
2.必须遵守以下一些约束  
   1)不得改写参数数据
   2)不会产生任何副作用,例如网络请求,输入和输出设备
   3)不能调用Date.now()或者Math.random()等不纯的方法  
3.redux的reducer函数必须是一个纯函数
 

9.8.2. 高阶函数

1.理解: 一类特别的函数
    1)情况1: 参数是函数
    2)情况2: 返回是函数
2.常见的高阶函数: 
   1)定时器设置函数
   2)数组的forEach()/map()/filter()/reduce()/find()/bind()
   3)promise
   4)react-redux中的connect函数
作用: 能实现更加动态, 更加可扩展的功能
 

10.扩展知识 

10.1setState(两种写法)

setState(stateChange, [callback])------对象式的setState
   1.stateChange为状态改变对象(该对象可以体现出状态的更改)
   2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用

(2). setState(updater, [callback])------函数式的setState
            1.updater为返回stateChange对象的函数。
            2.updater可以接收到state和props。
            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。 

总结:
        1.对象式的setState是函数式的setState的简写方式(语法糖)
        2.使用原则:
            (1).如果新状态不依赖于原状态 ===> 使用对象方式
            (2).如果新状态依赖于原状态 ===> 使用函数方式
            (3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取 

10.2 lazyLoad

路由组件的lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
  

// import Home from './Home'
// import About from './About'

import Loading from './Loading'
const Home = lazy(()=> import('./Home') )
const About = lazy(()=> import('./About'))


 //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
  <Suspense fallback={<Loading/>}>
      {/* 注册路由 */}
       <Route path="/about" component={About}/>
      <Route path="/home" component={Home}/>
   </Suspense>

10.3. Hooks

10.3.1React Hook/Hooks是什么?

(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性

10.3.2. 三个常用的Hook

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

10.3.2.1State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值 

10.3.2.2Effect Hook

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
(3). 语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
(4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
        componentWillUnmount()

10.3.2.3Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

10.4. Fragment

 <Fragment><Fragment>
<></>

10.5. Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

1) 创建Context容器对象:
    const XxxContext = React.createContext()  
    
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
    <xxxContext.Provider value={数据}>
        子组件
    </xxxContext.Provider>
    
3) 后代组件读取数据:

    //第一种方式:仅适用于类组件 
      static contextType = xxxContext  // 声明接收context
      this.context // 读取context中的value数据
      
    //第二种方式: 函数组件与类组件都可以
      <xxxContext.Consumer>
        {
          value => ( // value就是context中的value数据
            要显示的内容
          )
        }
      </xxxContext.Consumer>

10.6. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()  

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
    重写shouldComponentUpdate()方法
    比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
    使用PureComponent
    PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
    注意: 
        只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
        不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

10.7. render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
    使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
    使用children props: 通过组件标签体传入结构
    使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

10.8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

10.9. 组件通信方式总结

组件间的关系:

  • 父子组件

  • 兄弟组件(非嵌套组件)

  • 祖孙组件(跨级组件)

几种通信方式:

1.props:
        (1).children props
        (2).render props
    2.消息订阅-发布:
        pubs-sub、event等等
    3.集中式管理:
        redux、dva等等
    4.conText:
        生产者-消费者模式

比较好的搭配方式:

父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多) 

React注意事项

  1. 所有组件首字母必须大写
  2. 所有组件样式类名是className而不是class
  3. 所有行内样式属性名统一用小驼峰且写在双大括号中 style={{ }}
  4. 所有数组类型的元素在循环输出时必须设置不同的key
  5. 所有return后面的元素必须且只有一个根元素

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值