React基础笔记

React基础学习笔记

Author: 哇哇小仔
Date: 2021-03-19
Version: V 1.1.0
Description: 根据尚硅谷React视频教程总结的笔记

第1章 React简介

  1. React是什么?
    1. 用于构建用户界面的Javascript库
      • 发送请求获取数据
      • 处理数据(过滤、整理格式)
      • 操作DOM呈现页面(这个是React的作用)
    2. React是一个将数据渲染为HTML视图的开源Javascript库
  2. 谁开发的?
    • 由Facebook开发,且开源
  3. 为什么要学?
    1. 原生Javascript操作DOM繁琐,效率低(DOM-API操作UI)
    2. 使用Javascript直接操作DOM,浏览器会进行大量的重绘重排
    3. 原生Javascript没有组件化编码方案,代码复用率低
  4. React的特点
    1. 采用组件化模式,声明式编程,提高开发效率及组建复用率
    2. 在React Native中可以使用React语法进行移动端开发
    3. 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
  5. React包
    1. babel.min.js: 使用babel是将jsx==>js
    2. react.development.js: React的核心库
    3. react-dom.development.js: React的扩展库
      注:必须先引入核心库,再引入扩展库

第2章 React Basic

2.1 Hello React

  • 代码例子
    <body>
      	<!-- 准备好一个“容器” -->
      	<div id="test"></div>
    
      	<!-- 引入react核心库,引入了React对象 -->
      	<script type='text/javascript' src='./js/react.development.js'></script>
      	<!-- 引入react-dom,用于支持react操作DOM,引入了ReactDOM对象 -->
      	<script type='text/javascript' src='./js/react-dom.development.js'></script>
      	<!-- 引入babel,用于将jsx转为js -->
      	<script type='text/javascript' src='./js/babel.min.js'></script>
    
      	<!-- 此处一定要写babel,就是告诉浏览器,写的是jsx,需要babel转换成js -->
      	<script type='text/babel'>
        		// 1. 创建虚拟DOM
 				/* 此处一定不要写引号,因为不是字符串,这是JSX,
				如果想清除一些使用缩进直接在最边加上一个括号 */
        		const VDOM = ( 
    			  	<h1 id='title'>
    			    	<span>Hello React!</span>
    			  	</h1>
    		     ); 
    
        		// 2. 渲染虚拟DOM到页面
        		ReactDOM.render(VDOM, document.getElementById('test'));
      	</script>
     </body>
  • 注意点:
    • 必须先引入react核心库,再引入扩展库,这个顺序不能颠倒
    • script标签中写react代码时,type必须是’text/babel’
    • 创建虚拟DOM时,一定不能加引号

2.2 创建虚拟DOM的两种方式

  1. 使用JSX创建
    const VDOM = <h1>Hello React</h1>
  2. 使用JS创建
    const VDOM = React.createElement('h1', {id: 'title'}, 'Hello React')
  3. 关于虚拟DOM
    • 本质是Object类型的对象(一般对象)
    • 虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性
    • 虚拟DOM最终会被React转化为真实DOM呈现在页面上

2.3 JSX语法规则

实际上,JSX 仅仅只是 React.createElement(component, props, …children) 函数的语法糖。

  1. 定义虚拟DOM时,不要写引号
  2. 标签中混入JS表达式时,要用{}
  3. 样式的类名指定不要用class,要用className
  4. 内联样式,要用style={{key: value}}的形式去写,因为右侧是相当于混入了JS表达式,外侧{}表示{}里边是JS表达式,里侧{}表示一个键值对对象
  5. 虚拟DOM必须只有一个根标签
  6. 标签必须闭合 (比如那个input自结束标签,结尾可以加上一个’/’,或者</input>)
  7. 标签首字母
    (1) 若小写字母开头,则将该标签转为html中同名元素,若html中没有对应的同名标签,则报错
    (2) 若大写字母开头,react则去渲染对应的组件,若组件没有定义,则报错
  8. JSX注释的方式 {/* */}
  9. 补充:
    一定注意区分:JS语句(代码)JS表达式,上边第2条中是针对JS表达式
  • 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。下边这些都是表达式:
    (1)a
    (2)a+b
    (3)demo(1)
    (4)arr.map()
    (5)function test () { }
  • 语句(代码):下边这些都是语句(代码)
    (1)if () { }
    (2)for () { }
    (3)switch () {case: xxx}

2.4 模块与组件、模块化与组件化的理解

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

第3章 React面向组件编程

3.1 基本理解和使用

3.1.1 函数式组件
  • 函数式组件,函数名就是组件名
// 创建函数式组件
function MyComponent(){
	console.log(this);  // 此处this是undefined,因为babel编译后开启了严格模式
	return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
  • 执行了 ReactDOM.render(…) 之后,发生了什么?
    1. React 解析组件标签,找到 MyComponent 组件
    2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面上
3.1.2 类式组件
// 创建类式组件
class MyComponent extends React.Component {
  render(){
    // rende是放在哪里的?—— MyComponet的原型对象上的
    // render中的this是谁?—— MyComponent组件的实例对象
    console.log('render中的this是:', this);
    return <h2>我是用类定义的组件(用于复杂组件的定义)</h2>
  }
}
// 渲染组件到页面
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
  • 执行了ReactDOM.render(…)之后,发生了什么?
    1. React解析组件标签,找到 MyComponent 组件
    2. 发现组件是使用类定义的,随后 new 出该类的实例,并通过该实例调用到原型上的 render 方法
    3. 将 render 返回的虚拟 DOM 转为真实的 DOM,随后呈现在页面中

3.2 组件实例的三大核心属性1:state

  1. 理解
    • state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
    • 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件,也就是重新调用组件的render方法)
    • 状态驱动页面显示
  2. 强烈注意
    • 组件中render方法中的this为组件的实例对象
    • 组件自定义方法中的this为undefined,如何解决?
      • 方法一:强制绑定this:通过函数对象的bind()
      • 方法二:赋值语句+箭头函数
    • 状态数据(也就是state里边的属性),不能直接修改或更新,需要使用setState()方法
  3. 代码示例
  • state修改的方法一
<script type='text/babel'>
	// 1. 创建组件
	class Weather extends React.Component {
	
		// 构造器调用几次? —— 1次
		constructor(props){
      		super(props);
      		// 初始化状态
      		this.state = {isHot: true, wind: '微风'}
      		// 等号左边的changeWeather是在实例自身创建一个changeWeather
      		// 等号右边的changeWeather是找到了原型对象上的changeWeather,也就是下边定义的
      		// bind方法改变了等号左边的函数对象的this,也就是传递的this(也就是Weather的实例对象)
      		/* 
         		bind() 方法会创建一个新函数,当这个新函数被调用时,
         		它的 this 值是传递给 bind() 的第一个参数, 
         		它的参数是 bind() 的其他参数和其原本的参数。 
      		*/
     	 	this.changeWeather = this.changeWeather.bind(this)
      		// this.demo = this.changeWeather.bind(this)
    	}
    	// render 调用了几次? —— n+1次,1是初始化的那次,n是状态更新的次数
    	render(){
      		// 对象的解构赋值,寻找同名属性进行解构
      		// 读取状态
      		let {isHot} = this.state;
      		return (<h2 onClick={this.changeWeather}>
						今天天气很{isHot?'炎热':'凉爽'}, {this.state.wind}
		  			</h2>)
      		// return <h2 onClick={this.demo}>今天天气很{isHot?'炎热':'凉爽'}</h2>
    	}
    	// changeWeather 调用了几次? —— 点几次调用几次
    	changeWeather(){
      		// changeWeather 放在了 Weather 的原型对象上,供实例使用
      		// 由于changeWeather是作为onClick的回调,不是通过实例调用的,是直接调用的
      		// 类中的方法默认开启了局部的严格模式,严格模式下,禁止this关键字指向全局对象。 
			// 所以changeWeather中的this为undefined
      		// 获取原来的isHot值
      		const isHot = this.state.isHot;
      		// 严重注意:状态state不能直接更改,下面这行就是直接更改
      		// this.state.isHot = !isHot;  // 这是错误的写法
      		// 严重注意:状态state只能通过setState修改,且更新是一种合并,不是替换
      		// 不是替换,因此state里边的wind属性还在
      		this.setState({isHot: !isHot})
    	}
	}
	// 渲染组件到页面
	ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>
  • state修改的简写方法二
<script type='text/babel'>
	class Weather extends React.Componenent{
    	// 初始化状态
    	state = {isHot: true, wind: '微风'}
    	render(){
      		let {isHot} = this.state;
      		return (<h2 onClick={this.changeWeather}>
					今天天气很{isHot?'炎热':'凉爽'}, {this.state.wind}
		  			</h2>)
    	}
    	// 自定义方法 —— 要用赋值语句的形式+箭头函数
    	changeWeather = () => {
      		// 箭头函数this是静态的,this始终指向函数声明时所在作用域下的 this 的值
      		// 因此这里的this是Weather的实例对象
      		const isHot = this.state.isHot;
      		this.setState({isHot: !isHot})
    	}
	}
	// 渲染组件到页面
	ReactDOM.render(<Weather/>, document.getElementById('test'))
</script>

3.3 组件实例的三大核心属性2:props

  • 理解
    • 每个组件对象都会有props属性
    • 组件标签的所有属性都保存在props中
  • 作用
    • 通过标签属性从组件外向组件内传递变化的数据
    • 注意:组件内部不要修改props数据
  • 编码操作
  1. 内部读取某个属性值:this.props.name
  2. 对props中的属性值进行类型限制和必要性限制
  • 第一种方式 (React v15.5 开始已经弃用)
	Person.propTypes = {
		name: React.PropTypes.string.isRequired,  
		gender: React.PropTypes.string,  
		age: React.PropTypes.number,  
		sayName: React.PropTypes.func  
	}
  • 第二种方式(新)
    使用prop-types库进行限制(需要引入prop-types库)
	//这种方式是写在类的外部
	Person.propTypes = {
		name: PropTypes.string.isRequired,
		age: PropTypes.number
	}
	// 简写方式,写在类的内部
	static propTypes = {
		name: PropTypes.string.isRequired,
		age: PropTypes.number
	} 
  1. 扩展属性:将对象的所有属性通过props传递
	const person = {name:'Danny', age:10}
	<Person {...person}>
  1. 默认属性值
	Person.defaultProps = {
		age = 18,
		gender = '未知'
	}  

5)组件类的构造函数
* 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
* 如果构造器没有接收props,没有传递给super,则constructor中访问this.props为undefined

		constructor(props){
			super(props)
			console.log(props) // 打印所有属性
		}
  1. 类式组件使用props(简写方法)
<div id='test1'><div>
<div id='test2'><div>
<!-- 引入prop-types 用于对组件标签属性进行限制,全局会多了一个PropTypes对象 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type='text/babel'>
	// 创建虚拟DOM
	class Person extends React.Component{
		/* constructor(props){  // 一般构造器都不需要写
			super(props)  
		}*/
		static propTypes = { // 设置类型和必须性限制
			name: PropTypes.string.isRequired,
			age: PropTypes.number,
			gender: PropTypes.string
		}
		static defaultProps = { // 设置默认值
			age: 18,
			gender: '未知'
		}
		render(){
			return (<ul>
						<li>名字:{name}</li>
						<li>性别:{gender}</li>
						<li>年龄:{age}</li>
					</ul>)
		}
	}
	const p = {name: 'Danny', age: 18, gender: 'Male'}
	// 将虚拟DOM渲染到页面
	ReactDOM.render(<Person {...p}/>, document.getElementById('test1'))
	ReactDOM.render(<Person name='Jenny'/>, document.getElementById('test2'))
</script>
  1. 函数式组件使用props
<script type='text/babel'>
	function Person (props) {
		const {name, age, gender} = props;
		return (
			<ul>
				<li>姓名:{name}</li>
				<li>年龄:{age}</li>
				<li>性别:{gender}</li>
			</ul>
		)
	}
	Person.propTypes = {
		name: PropTypes.string.isRequired,
		age: PropTypes.number,
		gender: PropTypes.string
	}
	Person.defaultProps = {
		name: '',
		age: 18,
		gender: '未知'
	}
	ReactDOM.render(<Person name='Danny' age={22} gender='Male'/>, document.getElementById('test1'));
	ReactDOM.render(<Person name='Jenny' age={12} />, document.getElementById('test2'));
</script>

3.4 组件实例的三大核心属性3:refs与事件处理

3.4.1 标识 refs
  1. 字符串形式的ref: ref='input1'
	<script>
		class Demo extends React.Component{
			showData = () => {
				// ref的键值对key-value保存在 this.refs这个对象中
				// key就是'input1', value就是input那个真实DOM
				const {input1} = this.refs;  // 解构赋值					
				alert(input1.value);
			}
			render(){
				// ref 直接是一个字符串,不推荐使用,今后可能会被React弃用
				return (
					<div>
						<input ref='input1' type='text' />
						<button onClick={this.showData}>点我显示输入内容</button>
					</div>
				)
			}
		}
	</script>
  1. 回调函数形式的ref: ref = {c => this.input1 = c}
	<script>
		class Demo extends React.Component{
			showData = () => {
				const {input1} = this;  // 解构赋值
				alert(input1.value)
			}
			render(){
				// 回调函数 接受一个参数 c (currentNode)
				// 这个 c 实际就是这个真实的 DOM 节点 input
        		// ref 的回调中的 this 是 Demo 的实例对象
				return (
					<div>
						<input ref={c => this.input1 = c} type='text' />
						<button onClick={this.showData}>点我显示输入内容</button>
					</div>
				)
			}
		}
	</script>
  • 关于回调 refs 的说明
    • 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。
    • 这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
    • 通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题。
    • 但是大多数情况下它是无关紧要的。
		saveInput = (currentNode) => {
			this.input1 = currentNode
		}
		// 将input的ref那里写成一个Demo的绑定函数(例如:saveInput)
		<input ref={this.saveInput} type='text' />
  1. createRef: myRef = React.createRef(); ref={this.myRef}
<script>
	class Demo extends React.Component{
		// React.createRef 调用后可以返回一个容器,该容器可以存储被ref所标识的节点
		// 该容器是专“人”专用的,只能存一个节点,
		// 需要标记几个节点就要创建几个React.createRef()
		myRef = React.createRef()
		myRef2 = React.createRef(
		showData = () => {
			// console.log(this.myRef);  // {current: input}
			// 这里的 current 是固定的,不能更改
			alert(this.myRef.current.value)
		}
		render(){
			return (
			  <div>
				<input ref={this.myRef} type='text' />
				<input ref={this.myRef2} type='text' />
				<button onClick={this.showData}>点我显示输入内容</button>
			  </div>
			)
		}
	}
</script>
3.4.2 事件处理
  1. 通过 onXxx 属性指定事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —— 为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —— 为了高效
  2. 通过event.target得到发生事件的DOM元素对象 —— 不要过度使用ref
<div id='test'></div>
<script>
	// 创建虚拟DOM
	class Demo extends React.Component{
		// 发生事件的元素就是我们要操作的元素,此时ref可以使省略
		// 下边input失去焦点,显示的input的值,此时ref就可以省略
		showData = (event) => {
	  		alert(event.target.value)
		}
    	render(){
	  		return (
				<input onBlur={this.showData} type='text' placeholder='失去焦点显示数据'/>
			)
	  	}
 	}
	ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>

3.5 收集表单数据

3.5.1 非受控组件
  • 表单中输入类的DOM的值,是现用现取,这种叫做非受控组件
<div id='test'></div>
<script>
  class Login extends React.Component{
	// 点击提示输入的内容
	handleSubmit = (event) => {
	  event.preventDefault(); // 阻止表单提交的默认行为
	  const {username, password} = this;
	  alert(`输入的用户名是${username.value},密码是${password.value}`);
	}
	render(){
	  return (
	    // 表单绑定提交事件(form有onSubmit事件)
		<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>
3.5.2 受控组件
  • 表单中输入类的DOM,随着输入我们将内容维护到组件的状态state中,等待使用时从state中取出来,我们叫做受控组件。
  • 受控组件不需要使用ref,更加推荐。
<div id='test'></div>
<script>
	class Login extends React.Component{
		// 状态state初始化
		state = {username:'', password: ''}
		// 将username存入状态中
		saveUsername = (event) => {
			this.setState({username: event.target.value});
		}
		// 将password存入状态中
		savePassword = (event) => {
			this.setState({password: event.target.value});
		}
		handleSubmit = (event) => {
	  	event.preventDefault();  // 阻止表单提交的默认行为
	  	const {username, password} = this.state;
	  	alert(`您输入的用户名是${username},密码是${password}`);
		}
		render(){
			// 为 form 绑定 onSubmit 事件
			// 为 input 绑定 onChange 事件,发生改变就将改变保存到state
			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>
3.5.3 高阶函数和函数柯里化
  • 高阶函数
    • 定义:如果一个函数符合下面2个规范中任意一个,那么该函数就是高阶函数
      • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
      • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
    • 常见的高阶函数:
      • Promise:new Promise((reolsve, reject)=>{})
      • setInterval, setTimeOut:setTimeOut(function(){}, 1000)
      • arr.map() 等等的数组上的方法
  • 函数柯里化
    • 通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式
  • 函数柯里化的例子
<div id='test'></div>
<script>
  	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(){
			// 内联函数写了括号就是这个函数的返回值作为回调函数
    		// 必须要把一个函数交个 onChange 作为回调
	  		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>
  • 不用函数柯里化也可以实现上述目的
<div id='test'></div>
<script>
  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(){
    	// 此时 onChange 的回调函数是一个箭头函数
	  return (
		<form onSubmit={this.handleSubmit}>
		  <input onChange={e => this.saveFormData('username', e)} type='text' name='username'/>
		  <input onChange={e => this.saveFormData('password', e)} type='password' name='password'/>
		  <button>提交</button>
		</form>
	  )
	}
  }
  ReactDOM.render(<Login/>, document.getElementById('test'))
</script>

3.6 组件生命周期

3.6.1 组件的生命周期(旧)
3.6.1.1 旧的生命周期3个阶段
  1. 挂载阶段:由 ReactDOM.render() 触发
    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount() ===> 常用
      • 一般在这个钩子中做一些初始化的事情,例如开启定时器、发送网络请求、订阅消息
  2. 更新阶段:由 this.setState() 或者父组件重新更新 render() 触发
    1. componentWillReceivedProps()
    2. shouldComponentUpdate()
      • 组件是否要更新的钩子 如果不写这个函数默认返回true。
      • 如果写了这个函数,必须要写返回值,返回值必须是布尔值
    3. componentWillUpdate()
    4. render()
    5. componentDidUpdate()
  3. 卸载阶段:由 ReactDOM.unmountComponentAtNode() 触发
    1. componentWillUnmount() ===> 常用
      • 一般做一些收尾的事情,例如:关闭定时器、取消订阅消息
3.6.1.2 旧的生命周期图例

在这里插入图片描述

  1. 挂载时的生命周期的流程:constructor => componentWillMount => render => componentDidMount => componentWillUnmount
  2. setState() 正常更新的生命周期流程:setState() => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate => componentWillUnmount
  3. forceUpdate() 的生命周期流程:forceUpdate() => componentWillUpdate => render => componentDidUpdate => componentWillUnmount.
           - 强制更新:组件的状态不做任何更改,强制更新组件
           - 阀门(shouldComponentUpdate)即使关了,强制更新也可以使用
  4. 父组件重新render的生命周期流程:
    componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
3.6.2 组件的生命周期(新)
3.6.2.1 新的生命周期3个阶段
  1. 挂载阶段:由 ReactDOM.render() 触发
    1. constructor()
    2. getReceivedStateFromProps(props, state)
      • 前边必须加上static关键字
      • 此方法适用于罕见的用例(几乎不用)
      • 如果state的值都取决于props,可以使用这个函数
      • 必须要有返回值,null或者state对象
    3. render()
    4. componentDidMount() ==> 常用
      • 一般在这个钩子中做一些初始化的事情,例如开启定时器、发送网络请求、订阅消息
  2. 更新阶段:由 this.setState() 或者父组件重新 render() 触发
    1. getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. getSnapshotBeforeUpdate()
      • 如果写这个钩子,必须同时写componentDidUpdate()这个钩子
      • 在更新之前获取快照,此用法并不常见
    4. render()
    5. componentDidUpdate()
      • 可以接收三个参数:prevProps, prevState, snapshotValue
  3. 卸载阶段:由 ReactDOM.unmountComponentAtNode()触发
    1. componentWillUnmount ==> 常用
      • 一般做一些收尾的事情,例如:关闭定时器、取消订阅消息
3.6.2.2 新的生命周期的图例

在这里插入图片描述

3.6.2.3 getSnapshotBeforeUpdate()应用例子
<!DOCTYPE html>
<html lang="en">
<head>
<title>getSnapshotBeforeUpdate应用</title>
<style>
  	.list{
    		width: 200px;
    		height: 150px;
    		background-color: skyblue;
    		overflow: auto
  	}
  	.news{ height: 30px; }
</style>
</head>
<body>
<div id='test'><div>
<script>
  	// 需求:div中不断接收到新的新闻,滚动条
  	// 但是点击某一条新闻时,固定页面在那个新闻处
  	class NewsList extends React.Component{
		state = {newsArr: []};  // 初始化状态,一个新闻的数组
		// 开启定时器,每隔1s添加一个新闻
		componentDidMount(){
			setInterval(()=>{
				const {newsArr} = this.state;
				const news = '新闻' + (newsArr.length + 1)
				this.setState({newsArr: [news, ...newsArr]})
			}, 1000)
		}
		
		// 每次更新前,需要确定当前的scrollHeight
		getSnapshotBeforeUpdate(){
			return this.myRef.scrollHeight;
		}
		// 在每次新加入新闻后,需要确定div的scrollTop的值使其位置不发生变化
		componentDidUpdate(prevProps, prevState, height){
			this.myRef.scrollTop += this.myRef.scrollHeight - height
		}
		render(){
			return (
			  <div ref={c=>this.myRef=c} className='list'>
				{
					this.state.newsArr.map((value, index)=>{
						return <li key={index} className='news'>{value}</li>
					})
				}
			  </div>
			)
		}	
  	}
	ReactDOM.render(</NewsList>, document.getElementById('test'))
</script>
</body>
3.6.3 新、旧生命周期的对比和小结
  1. 重要的钩子
    • render():初始化渲染或更新渲染调用
    • componentDidMount():开启监听, 发送ajax请求
    • componentWillUnmount():做一些收尾工作, 如: 清理定时器
  2. 新的生命周期中添加的钩子
    • getRetrivedStateFromProps()
    • getSnapshotBeforeUpdate()
  3. 从旧的生命周期中删除的钩子
    • componentWillMount()
    • componentWillUpdate()
    • componentWillReceiveProps()
    • 注意:现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

3.7 虚拟DOM与DOM Diff算法

3.7.1 经典问题
  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也是可以的。
3.7.2 算法原理图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值