react -【JSX】-【组件】-【state、props、ref】-【事件】-【收集表单数据】-【生命周期】-【DIFF】-【脚手架】-【配置代理服务器】-【消息订阅与发布】

本文详细介绍了React的基础知识,包括如何创建虚拟DOM、JSX语法、组件与模块、状态管理和事件处理。特别强调了组件化编程,通过函数式组件和类式组件展示了组件的定义与使用。此外,还讲解了如何使用ref进行DOM操作,以及如何通过propType和defaultProps控制组件更新。最后,讨论了React的生命周期函数,包括新旧版本的差异,并演示了fetch发送请求的基本用法。
摘要由CSDN通过智能技术生成

react入门

hello react

HTML代码如下

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

	<!-- 2. 引入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 */
		// 3.创建虚拟DOM
		const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
		// JSX可以将HTML和JS混用
		// 4.渲染虚拟DOM到页面 
		// ReactDOM.render(虚拟DOM,容器) 你把哪个虚拟DOM渲染到哪个容器中 ReactDOM操作虚拟DOM
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>
</html>

创建虚拟DOM的两种方式

为什么要用JSX:JSX可以让程序员更简单的创建虚拟DOM 假如我们要实现下面的结构
在这里插入图片描述

  1. JS创建虚拟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 React.createElement(标签名,标签属性,标签内容)创建虚拟DOM document.createElement创建真实DOM
		const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>
</html>
  1. JSX创建虚拟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" > /* 此处一定要写babel */
		// Babel就是将你这里面的代码翻译成了原生JS的代码:
		// const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))

		//1.创建虚拟DOM
		const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
			<h1 id="title">
				<span>Hello,React</span>
			</h1>
		)
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>
</html>
  1. 关于虚拟DOM:
    (1)本质是Object类型的对象(一般对象)
    (2)虚拟DOM身上的属性比较少,真实DOM身上的属性比较多,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
    (3)虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

JSX语法

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

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

组件与模块

  1. 模块
    (1)理解:向外提供特定功能的js程序, 一般就是一个js文件
    (2)为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
    (3)作用:复用js, 简化js的编写, 提高js运行效率
  2. 组件
    (1)理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
    (2)为什么要用组件: 一个界面的功能更复杂
    (3)作用:复用编码, 简化项目编码, 提高运行效率
  3. 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
  4. 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

React面向组件编程

定义组件的两种方式

函数式组件

<!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(){
			// 此处的this是undefined,因为babel编译后开启了严格模式 
			// 严格模式下禁止自定义函数里的this指向window
			console.log(this); 
			// 2. 函数必须有返回值。返回值就是组件内容,也就是你要展示的东西
			return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
		}
		// 3.渲染组件到页面 ReactDOM.render(组件的标签(必须闭合),要渲染进的容器)
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
	</script>
</body>
</html>

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

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

类式组件

复习类的基本知识
  1. 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
  3. 类中所定义的方法,都放在了类的原型对象上,供实例去使用。
class Person {//创建一个Person类
	constructor(name,age){//构造器方法
		//构造器中的this是谁?—— 类的实例对象
		this.name = name
		this.age = age
	}
			
	speak(){//一般方法
		//speak方法放在了哪里?——类的原型对象上,供实例使用
		//通过Person实例调用speak时,speak中的this就是Person实例
		console.log(`我叫${this.name},我年龄是${this.age}`);
	}
}

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

class Car {
	constructor(name,price){
		this.name = name
		this.price = price
		// this.wheel = 4
	}
	//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1
	a = 1
	wheel = 4
	// 在变量名前面加static,表示这是个静态变量,静态变量是在类Car身上的,不是实例对象,是这个对象本身有的
	static demo = 100
}
const c1 = new Car('奔驰c63',199)
console.log(c1);
console.log(Car.demo);

类式组件定义方式:

<!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.创建类式组件
		// 必须继承React.Component 必须定义render()函数 render()函数中必须有返回值
		class MyComponent extends React.Component {
			render(){
				//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
				//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
				console.log('render中的this:',this);
				return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
			}
		}
		//2.渲染组件到页面 这里的render和上面的render不是同一个东西 只是重名了而已
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
	</script>
</body>
</html>

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

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

state

  1. 类中的this指向问题
class Person {
	constructor(name,age){
		this.name = name
		this.age = age
	}
	study(){
		//study方法放在了哪里?——类的原型对象上,供实例使用
		//通过Person实例调用study时,study中的this就是Person实例
		console.log(this);
	}
}
const p1 = new Person('tom',18)
p1.study() //通过实例调用study方法
const x = p1.study
x()// 类中你定义的所有方法都自动开启了严格模式(局部) 所以这里打印undefined
  1. 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{
			//构造器调用几次? ———— 1次
			constructor(props){// 借助构造器初始化state
				console.log('constructor');
				super(props)// 只要你写了构造器 这个一定要写
				
				// react官方要求在下面两种情况下得使用构造器:
				// 1. 通过给this.state赋值对象来初始化内部state
				// 2. 为事件处理函数绑定实例 这两种情况 分别对应下面两行代码
				// 所以在后面state简写方式中 通过别的方式实现了下面两行代码 就可以不用构造器 一般都不写
				//初始化状态 state必须是个对象
				this.state = {isHot:false,wind:'微风'}
				//解决changeWeather中this指向问题
				this.changeWeather = this.changeWeather.bind(this)
				//构造函数里的this都指向实例对象
				// this.changeWeather.bind(this)拿到了实例对象上的changeWeather方法 然后通过bind函数将该方法的this改为了实例对象
				// bind做了两件事:1. 给你生成一个新的函数 2. 改变函数里的this指向
				// this.changeWeather.bind(this)执行完了你得到了一个新的changeWeather函数 这个函数的this指向实例对象
				// 然后你把你得到的这个函数赋值给了实例对象的changeWeather属性
				// this.changeWeather是给实例对象赋值了一个changeWeather属性
				// 以后 你调用changeWeather就是调用changeWeather函数
			}

			//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
			render(){
				console.log('render');
				//读取状态
				const {isHot,wind} = this.state
				// 注意这里是this.changeWeather 而不是this.changeWeather()
				// 后者是调用changeWeather()函数 将其返回值赋值给onClick
				// 前者是将changeWeather()函数赋值给onClick 当你点击的时候 会执行onClick 就会执行changeWeather()函数
				// 由于类给类中所有自定义方法都自动开启了严格模式(局部) 这里的this是undefined
				// 所以如果你不写this.changeWeather = this.changeWeather.bind(this) 会报错
				// 写了之后 this.changeWeather指的就是实例对象的属性 这个属性中保存了一个方法
				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>
  1. 简写:
<!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
			//初始化状态
			state = {isHot:false,wind:'微风'}

			render(){
				const {isHot,wind} = this.state
				return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
			}

			//自定义方法————要用赋值语句的形式+箭头函数
			/*
			changeWeather = function() {
				// 这也相当于 给实例对象赋值了一个属性changeWeather 该属性指向一个函数
				// 但是这样写的话 你还是要在构造函数中修改this的指向
				// 但是如果你把changeWeather写成箭头函数 就可以不修改this指向了
				// 因为构造函数的this在这里就指向实例对象
				const isHot = this.state.isHot
				this.setState({isHot:!isHot})
			}
			 */
			changeWeather = ()=>{
				// 这里的this指向实例对象
				const isHot = this.state.isHot
				this.setState({isHot:!isHot})
			}
		}
		//2.渲染组件到页面
		ReactDOM.render(<Weather/>,document.getElementById('test'))
	</script>
</body>
</html>

props

  1. 通过标签属性从组件外向组件内传递变化的数据,这些数据保存在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'))
		 上面那种写法传入的19是个字符串 你如果写age=19会报错 数据类型是JS才有的 JSX没有
		 所以你可以写age={19}来告诉它是个Number类型
		 */
		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'))
		//	Babel+react确实可以让...展开一个对象 但是只能用于标签属性的传递
		//	传递props和传递标签属性一个意思
	</script>
</body>
</html>

展开运算符:

let arr1 = [1,3,5,7,9]
let arr2 = [2,4,6,8,10]
console.log(...arr1); //展开一个数组
let arr3 = [...arr1,...arr2]//连接数组

function sum(...numbers){//在函数中使用
	return numbers.reduce((preValue,currentValue)=>{
		return preValue + currentValue
	})
}
console.log(sum(1,2,3,4));

//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
let person2 = {...person}// 在外面包一个{}就能展开对象了 其实是深拷贝了对象赋值给person2
//console.log(...person); //报错,展开运算符不能展开对象
person.name = 'jerry'
console.log(person2);
console.log(person);
//合并
let person3 = {...person,name:'jack',address:"地球"}
console.log(person3);
  1. props是只读的 你如果想改props中的值 在组件渲染时进行传值时就应该改 其他时候改不了
  2. 引入prop-types包 为Person类添加propTypes属性 在该属性中对props中的数据进行限制 通过键值对的形式进行限制 键是props中的数据 值是你的限制 通过PropTypes.限制.限制进行限制
  3. 为Person类添加defaultProps属性 为该属性中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是只读的 你如果想改props中的值 在下面渲染时进行传值时就应该改
				// 在这里面props是不能更改的 他是只读的
				//this.props.name = 'jack' //此行代码会报错,因为props是只读的
				return (
					<ul>
						<li>姓名:{name}</li>
						<li>性别:{sex}</li>
						<li>年龄:{age+1}</li>
					</ul>
				)
			}
		}
		//对标签属性进行类型、必要性的限制 你只要给类添加propTypes属性我就认为你在加规则
		Person.propTypes = {
			name:PropTypes.string.isRequired, //限制name必传,且为字符串 通过在后面.来加限制
			sex:PropTypes.string,//限制sex为字符串
			age:PropTypes.number,//限制age为数值
			speak:PropTypes.func,//限制speak为函数
		}
		//指定默认标签属性值 通过defaultProps指定默认值
		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>
  1. 对标签属性进行类型、必要性的限制以及设置默认值 只要给组件本身添加了propTypes、defaultProps属性即可 所以可以使用static关键字为类添加这两个属性 在类中使用static关键字添加的属性在类自身身上
    简写:
<!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
// 官网:在react组件挂载之前 会调用他的构造函数 在为React.Component子类实现构造函数时
// 应在其他语句之前调用super(props) 否则this.props在构造函数中可能会出现未定义的bug
// 你在构造函数中使用this.props时 一定要给构造函数传props 并且第一句代码就是super(props)
// 要把props传给super 但一般在构造函数中都不用this.props
				// console.log(props);
				super(props)
				// this.props拿的并不是传给constructor的参数 而是实例对象上的props
				console.log('constructor',this.props);
			}
			// 对标签属性进行类型、必要性的限制 只要给类本身添加了propTypes属性就可以做限制
			// 不管你是在类外面用.添加的 还是在类里面用static添加的 下同
			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>
  1. 对标签属性进行类型、必要性的限制以及设置默认值 只要给组件本身添加了propTypes、defaultProps属性即可 所以针对函数式组件 我们也可以对他的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>
				)
		}
		// 只要给组件添加了propTypes属性 就可以限制 下同
		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>

ref

  1. 组件可以定义ref属性来标识自己
  2. ref:通过键值对的方式存储 键是ref的值 值是ref当前所处的节点 是虚拟DOM转成真实DOM后的节点
  3. 通过this.refs.ref名字 可以获取ref名字所在组件
  4. 字符串形式的ref
class Demo extends React.Component{//创建组件
	showData = ()=>{//展示左侧输入框的数据
		// const input1 = this.refs.input1;
		const {input1} = this.refs// 等价于上面那行代码 只不过这里用解构赋值
		alert(input1.value)
	}
	showData2 = ()=>{//展示右侧输入框的数据
		const {input2} = this.refs
		alert(input2.value)
	}
	//下面使用的是字符串形式的ref 效率不高 不推荐使用
	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/>,document.getElementById('test'))
  1. 回调函数形式的ref 框架同上
class Demo extends React.Component{//创建组件
	showData = ()=>{//展示左侧输入框的数据
		// 你下面this.input1 = c 将节点赋值给了组件实例对象的input1属性 所以这里通过实例获得节点
		const {input1} = this
		alert(input1.value)
	}
	showData2 = ()=>{//展示右侧输入框的数据
		const {input2} = this
		alert(input2.value)
	}
	render(){
		return(
			<div>
				/*
				ref中的函数是个回调函数 回调函数:你定义的 你没调用 它执行了
				react会new一个Demo实例 然后调用实例的render方法 执行render会返回jsx 执行jsx时发现ref是个回调函数
				就会帮你调用这个回调函数 且在调用时会把当前你处于的节点作为实参传进去
				this.input1 = c就是把你处在的节点赋值给实例对象的属性 c就是你当前处于的节点input
	*/
				<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/>,document.getElementById('test'))
  1. 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);
			}
/*
下面注释了的那行代码是以内联函数的方式定义的 在组件更新过程中他会被执行两次 第一次传入参数null 第二次传入参数的DOM元素
这是因为在每次渲染时会创建一个新的函数实例 所以react清空旧的ref并设置新的
通过将ref的回调函数定义成class的绑定函数(被注释的代码下一行的写法) 可以避免以上问题 但大多数时候 他是无关紧要的
 */
			render(){
				const {isHot} = this.state
				return(
					<div>
						<h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
						{/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
						{/* 把函数放在了实例自身 就算更新组件时重新调用render函数 也不会重新执行ref里的代码 */}
						<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>
  1. createRef创建ref容器
<!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所标识的节点,该容器是“专人专用”的
			下面用了多少个ref 这里就要用React.createRef()创建几个ref
			 */
			myRef = React.createRef()// 创建一个容器 这个容器挂载到了实例自身的myRef属性上
			myRef2 = React.createRef()
			
			showData = ()=>{//展示左侧输入框的数据
				// alert(this.myRef);// 输出一个对象current: 节点类型/标签名
				// this.myRef.current就可以拿到ref={this.myRef}所在的节点
				alert(this.myRef.current.value);// 取节点的值
			}
			
			showData2 = ()=>{//展示右侧输入框的数据
				alert(this.myRef2.current.value);
			}
			// 执行到ref={this.myRef}时 react发现你是用createRef()创建的ref 会将ref所在节点存储在容器里
			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>

事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    (1)为了更好的兼容性,React使用的是自定义(合成)事件,而不是使用的原生DOM事件
    (2)为了的高效,React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  2. 通过event.target得到发生事件的DOM元素对象(注意:不要过度使用ref)
class Demo extends React.Component{//创建组件
	//创建ref容器
	myRef = React.createRef()
	myRef2 = React.createRef()
	
	showData = (event)=>{//展示左侧输入框的数据
		console.log(event.target);
		alert(this.myRef.current.value);
	}
	// 失去焦点时react会帮你调用showData2 并把发生事件的对象作为实参传递给了showData2
	// event.target就可以获得发生事件的事件源
	showData2 = (event)=>{//展示右侧输入框的数据
		alert(event.target.value);
	}
	render(){
		return(
			<div>
				<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
				<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
				{/* 下面这个输入框失去焦点时会alert alert中提示的是下面这个输入框中的数据
				发生事件的DOM元素和你要操作的DOM元素是一个 这种情况就可以省略ref */}
				<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
			</div>
		)
	}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))

收集表单数据

  1. 非受控组件:页面上所有输入类DOM(input checkbox radio等)的值都是现用现取。在下面的例子中,你点击了登录按钮,然后走了handleSubmit这个回调,在回调中取了节点,然后取了节点的value值
<!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)=>{
				/*
				表单提交是一个默认的动作 就算你没有写action他也会提交 他的提交会提交数据 刷新页面
				但我们希望我们能获得表单的数据 然后用Ajax来发送请求 因为Ajax不会刷新页面 用户体验感更好
				也就是说 我现在只想获得表单中的数据 不提交表单
				 */
				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>
  1. 受控组件:页面上所有输入类DOM(input checkbox radio等)的值,随着你的输入,都会被维护到状态state里,等需要用时,直接从状态中取出来,类似于vue的双向数据绑定,但react没有实现双向数据绑定
<!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}`)
			}
			// 所有输入类DOM都有一个onchange回调函数 输入类DOM发生改变时会调用这个回调
			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. 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
    (1)若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
    (2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
  2. 常见的高阶函数有:Promise、setTimeout、arr.map()等等
  3. 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
	return(b)=>{
		return (c)=>{
			return a+b+c
		}
	}
}

上一节的代码使用函数柯里化:

<!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:'' //密码
			}
			/*
			我们在受控组件里写的代码如下:
			saveUsername = (event)=>{//保存用户名到状态中
				this.setState({username:event.target.value})
			}

			savePassword = (event)=>{//保存密码到状态中
				this.setState({password:event.target.value})
			}
			我们现在想统一收集数据到状态中 可以通过传参的方式区分数据 但是在render函数中
			react会自己帮你执行方法saveFormData('username')并将username传进去
			但我们希望输入框中的内容改变时 onChange被触发时 再收集数据 所以在saveFormData方法中 我们可以返回一个函数
			这个函数被赋给了onChange 当输入框中的内容改变时 react会执行onChange方法 将event作为参数传进去
			 */
			saveFormData = (dataType)=>{//保存表单数据到状态中
				return (event)=>{
					// [dataType]获得对象的属性
					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>
  1. 不使用柯里化
<!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}`)
			}
			/*
			onChange是个回调函数 保存在这里面的应该是个函数 当输入框的内容改变了 会执行onChange函数
			那么我们给他个函数呗 当输入框的内容改变了 会执行onChange指向的箭头函数 并把event作为实参传进去
			这个箭头函数调用了收集数据的方法
			 */
			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>

生命周期函数

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
  4. 生命周期流程图(旧)
    在这里插入图片描述
    生命周期的三个阶段:
    (1)初始化阶段: 由ReactDOM.render()触发初次渲染
    constructor()
    componentWillMount()
    render()
    componentDidMount():常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
    (2)更新阶段: 由组件内部this.setSate()或父组件render触发
    shouldComponentUpdate()
    componentWillUpdate()
    render():必须使用的一个
    componentDidUpdate()
    (3)卸载组件: 由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">
		class Count extends React.Component{//创建组件
			constructor(props){//构造器
				console.log('Count---constructor');
				super(props)
				this.state = {count:0}//初始化状态
			}

			add = ()=>{//加1按钮的回调
				const {count} = this.state//获取原状态
				this.setState({count:count+1})//更新状态
			}

			death = ()=>{//卸载组件按钮的回调
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			force = ()=>{//强制更新按钮的回调
				//forceUpdate不会调用shouldComponentUpdate询问是否能更新页面 而是直接更新页面
				this.forceUpdate()
			}

			componentWillMount(){//组件将要挂载的钩子
				console.log('Count---componentWillMount');
			}

			componentDidMount(){//组件挂载完毕的钩子
				console.log('Count---componentDidMount');
			}

			componentWillUnmount(){//组件将要卸载的钩子
				console.log('Count---componentWillUnmount');
			}

			// 控制组件更新的“阀门”
			// 你可以不写这个钩子函数 不写react会自动帮你创建一个 且返回值为true
			// 但是如果你自己写了这个钩子函数 必须返回一个布尔值 返回true表示可以更新组件 就会执行后面的钩子函数
			// 返回false表示不准更新组件 后面的钩子函数也就不会执行
			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>
				)
			}
		}
		
		class A extends React.Component{//父组件A
			state = {carName:'奔驰'}//初始化状态

			changeCar = ()=>{
				this.setState({carName:'奥拓'})
			}

			render(){
				return(
					<div>
						<div>我是A组件</div>
						<button onClick={this.changeCar}>换车</button>
						<B carName={this.state.carName}/>
					</div>
				)
			}
		}
		
		class B extends React.Component{//子组件B
			//组件将要接收新的props的钩子
			// 第一次不算 也就是说初始不算 更新才算 可以接受props作为参数 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. 生命周期流程图(新)
    在这里插入图片描述
    生命周期的三个阶段:
    (1)初始化阶段: 由ReactDOM.render()触发初次渲染
    constructor()
    getDerivedStateFromProps
    render()
    componentDidMount():常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
    (2)更新阶段: 由组件内部this.setSate()或父组件重新render触发
    getDerivedStateFromProps
    shouldComponentUpdate()
    render()
    getSnapshotBeforeUpdate
    componentDidUpdate()
    (3)卸载组件: 由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{//创建组件
			constructor(props){//构造器
				console.log('Count---constructor');
				super(props)
				//若state的值在任何时候都取决于props 你也可以在构造函数中 将取得的props用来初始化state
				this.state = {count:0}//初始化状态
			}

			add = ()=>{//加1按钮的回调
				const {count} = this.state//获取原状态
				this.setState({count:count+1})//更新状态
			}

			death = ()=>{//卸载组件按钮的回调
				ReactDOM.unmountComponentAtNode(document.getElementById('test'))
			}

			force = ()=>{//强制更新按钮的回调
				this.forceUpdate()
			}
			
			//若state的值在任何时候都取决于props,你的修改 你的初始化都没有用 完全依赖props 那么可以使用getDerivedStateFromProps
			//也可以在这里做一些判断:你传过来的props的某个属性的值是多少 初始化中某个属性的值是多少
			// 然后进行对比 奇数以谁为主 偶数以谁为主
			//只要有getDerivedStateFromProps 你所有的状态都以他的返回值为主 getDerivedStateFromProps在挂载和更新阶段都是必经之路
			static getDerivedStateFromProps(props,state){// 他是类自身的方法 state是初始化状态的值
				console.log('getDerivedStateFromProps',props,state);
				return null// 必须返回一个状态对象或null 状态对象就是state中某个键的值 如return {count: 180}
			// 只要返回一个对象 如果对象里包含的键在初始state中也有 该键的值就以我们这里返回的为主 不以初始化为主
			//	而且以后这个键的值都改不了了
			}

			getSnapshotBeforeUpdate(){//在更新之前获取快照
				console.log('getSnapshotBeforeUpdate');
				return 'atguigu'// 必须有返回值 可以返回null
			}

			componentDidMount(){//组件挂载完毕的钩子
				console.log('Count---componentDidMount');
			}

			componentWillUnmount(){//组件将要卸载的钩子
				console.log('Count---componentWillUnmount');
			}

			shouldComponentUpdate(){//控制组件更新的“阀门”
				console.log('Count---shouldComponentUpdate');
				return true
			}

			//组件更新完毕的钩子 preProps是组件更新前的props preState是组件更新前的state snapshotValue是getSnapshotBeforeUpdate的返回值
			//getSnapshotBeforeUpdate会将返回值传给componentDidUpdate的snapshotValue
			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. 新旧生命周期函数的区别:新的废弃了3个:componentWillMount() componentWillUpdate() componentWillReceiveProps 新增了2个钩子函数:getDerivedStateFromProps getSnapshotBeforeUpdate
  2. 重要的勾子:
    (1)render:初始化渲染或更新渲染调用
    (2)componentDidMount:开启监听, 发送ajax请求
    (3)componentWillUnmount:做一些收尾工作, 如: 清理定时器
  3. 即将废弃的勾子:
    (1)componentWillMount
    (2)componentWillReceiveProps
    (3)componentWillUpdate
    现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

getSnapShotBeforeUpdate的使用场景

  1. 我们现在想实现一个新闻展示区 新闻一条条的返回 将新的新闻展示在顶部 新闻越来越多 但是展示区高度有限 所以会产生滚动条 因为每隔1s就会返回一条数据 假如现在数据展示到200条了 但是我想看第4条数据 我滑到第4条 但是因为每1s会返回条新数据展示在顶部 所以我的第4条数据会慢慢滚下去 但是我希望它不动
  2. 实现思路:在组件更新前 也就是getSnapshotBeforeUpdate中获取当前列表的高度并传给componentDidUpdate,组件更新后 新的数据添加到了展示区顶部 我们再去获取当前列表的高度 两个高度相减就得到多出来的那部分高度 此时组件的位置就应该是原来的位置加上多出来的高度 设置给组件即可
<!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>

虚拟DOM与DIFF算法

  1. 两个虚拟DOM进行比较 比较的是标签
  2. 下面代码中 每秒中会获取当前时间 时间保存在state中 state中的内容每秒都变 变了就会来调render函数 会获得一个新的虚拟DOM 新旧虚拟DOM进行比较 他们的<h1>hello</h1><input type="text"/>没变 所以真实DOM也不会改变 但是span标签中的{this.state.date.toTimeString()}每次都在改变 所以他会更新span标签的内容现在是:{this.state.date.toTimeString()}不能说“现在是”这三个字没变就不更新了哦 DIFF算法比较的是标签 其次 他不会更新<input type="text"/> 因为两个虚拟DOM进行比较 比较的标签,这也是个标签 还是会进行新旧虚拟DOM比较 发现这个标签没变 就不会改变真实虚拟DOM
<!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的作用

react/vue中的key有什么作用?(key的内部原理是什么?)为什么遍历列表时,key最好不要用index?

  1. 虚拟DOM中key的作用:
    (1)简单的说:key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
    (2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
    a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:若虚拟DOM中内容没变, 直接使用之前的真实DOM 若虚拟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也是可以的。
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'))
	慢动作回放----使用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>

比较初始的虚拟DOM和更新数据后的的虚拟DOM 发现key=0的两个标签内容不一样
所以将更新数据后的虚拟DOM:<li key=0>小王---20<input type="text"/></li>
转成真实DOM挂载到页面 同理 key=1 key=2的两个标签都会转换成真实DOM挂载到页面
可是key=1 key=2这两个标签明明就可以复用 因为你设置的不正确的key导致没有必要的真实DOM更新 性能变差

-----------------------------------------------------------------

慢动作回放----使用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>

比较初始的虚拟DOM和更新数据后的的虚拟DOM 将key=3的标签转换成真实DOM挂载到页面 其余标签不动 复用之前的真实DOM

React应用(基于脚手架)

  1. React的脚手架是用webpack搭建的
  2. 创建项目:
    (1)全局安装:cmd输入:npm i -g create-react-app
    (2)在cmd中进入想创建项目的目录 然后在该目录下输入:create-react-app 项目名

yarn start:开启开发者的服务器 每次写完代码想看效果都要执行该命令
yarn build:把你写完的项目最终进行一次打包

  1. package.json的script中可以查看短命令
  2. react是单页面应用 整个页面只有一个HTML文件
  3. 项目启动后 首先来到了src下的index.js(入口文件) 引入了一堆库 然后触发渲染App 然后他会去public中找index.html 在index.html中找相应id的容器(本文是root) 然后App组件就出现在了页面 并设置了样式 其中index.js index.html不能改名
  4. 在react中 引入js文件和jsx文件都可以省略后缀
  5. 项目中文件名.jsx只是用来区分这个文件是个组件 其实你写文件名.js也行

脚手架配置代理服务器

在终端输入:npm i axios

配置单个代理服务器

客户端:http://localhost:3000
服务器:http://localhost:5000
代理服务器:http://localhost:3000
两台服务器传数据不用ajax,用的http请求,没有同源限制
  1. 浏览器向服务器发送请求 服务器收到请求 然后返回数据 但是由于浏览器同源协议的限制 返回的数据被浏览器的Ajax引擎拦截了
  2. 使用代理服务器解决跨域:客户端给代理服务器发请求 代理服务器将浏览器的请求转发给服务器 服务器给代理服务器返回数据 代理服务器将服务器返回的数据返回给浏览器 代理服务器可以收到服务器发回来的数据 因为代理服务器没有Ajax引擎
  3. 懂了上面的逻辑 那么浏览器就应该给代理服务器发送请求:axios.get('http://localhost:3000/students')……
    然后在package.json中追加如下配置:"proxy":"http://localhost:5000" 这是开启代理服务器"proxy":"服务器地址"
  4. 以后 浏览器请求的资源 若3000(代理服务器)没有 则把请求转发给5000(服务器)
    否则3000(代理服务器)会直接返回自己这里的资源给浏览器 而不转发给5000(服务器)

配置多个代理服务器

  1. 创建代理配置文件 在src下创建配置文件:src/setupProxy.js 在setupProxy.js中使用conmonJS写代码 react脚手架会找到这个文件 把该文件加到webpack的配置中 webpack中用的都是node中的语法 写的都是conmonJS。setupProxy.js代码如下:
const proxy = require('http-proxy-middleware')

module.exports = function(app){
	app.use(
		proxy('/api1',{ 
			//遇见/api1前缀的请求,就会触发该代理配置 只要你的请求中有api1 就会转发该请求
			target:'http://localhost:5000', //请求转发给谁
			changeOrigin:true,//控制服务器收到的请求头中Host的值
			// 若不写上面这行 则默认取值为false 服务器会知道这个请求是从哪里过来的(从浏览器3000端口过来的 不是代理服务器)
			// 若取值为true 服务器会错误的认为这个请求是从我自己同源的来的 相当于欺骗服务器
			pathRewrite:{'^/api1':''} //重写请求路径(必须) 去除/api1
			// 浏览器想请求服务器(http://localhost:5000)的/students这个资源
			// 你正常发请求 你的URL(http://localhost:5000/students)中不应该出现api1的 但是你配置了代理服务器
			//	如果URL中没有api1 代理服务器不会给你转发
			//	但是你加了api1 路径又不对了 所以我们在这里把/api1去除掉
		}),
		proxy('/api2',{
			target:'http://localhost:5001',
			changeOrigin:true,
			pathRewrite:{'^/api2':''}
		}),
	)
}
  1. 然后浏览器发送的请求记得加上/api1:axios.get('http://localhost:3000/api1/students') /api1必须写在端口号后面
  2. 以后 浏览器请求的资源 若3000(代理服务器)没有 则把请求转发给5000(服务器)
    否则3000(代理服务器)会直接返回自己这里的资源给浏览器 而不转发给5000(服务器)(?)

消息订阅与发布

  1. 在终端下载:npm install pubsub-js --save
  2. 使用:
    (1)引入:import PubSub from 'pubsub-js'
    (2)订阅消息:在componentDidMount中订阅消息 即组件一挂载就订阅消息:let token = PubSub.subscribe('delete', function(msg, data){ });delete是消息名 你要订阅的消息 如果有人发布了delete这个消息 你就会收到通知 收到之后会调用回调函数 回调函数中的msg就是消息名 也就是delete data是别人想交给你的数据 你每次订阅消息都会有一个订阅的token 等以后你不想订阅这个消息了(如组件被卸载了) 可以通过token取消订阅:PubSub.unsubscribe(token);
    (3)发布消息:PubSub.publish('delete', data)delete是消息名 第二个参数data是携带的数据
    (4)取消订阅:在componentWillUnmount中取消订阅 即订阅消息的组件将被卸载了 取消订阅消息:PubSub.unsubscribe(token);
  3. 任意组件进行通信都可以使用消息订阅与发布

fetch发送请求

  1. fetch:原生函数,不再使用XmlHttpRequest对象提交ajax请求
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值