
说明:这个文档是依据尚硅谷官方文档复制下来的,可能排版不是很好,因为是从word直接粘贴的,我在官方文档的基础上增加了里面的实例,应该说内容会丰富一些。
注意:我下面的代码中,有一些在jsx中的render(){}里面使用js注释的方法,其实是错误的,jsx中的注释必须放在{}中,是这样的{/* 注释内容 */},可能会有没有改到的地方,需要注意。
1.1.1. 官网
1.1.2. 介绍描述

1.1.3. React的特点
原生JS的痛点:

react的设计目标就是解决上面的问题,由此得出了react的优点:

1.1.4. React高效的原因

一直以来都有一个疑问:为什么diff算法可以减少重绘重排?最终都需要转换为DOM给浏览器渲染的,不管是原生JS还是react/vue,重新生成的DOM应该是一样的啊,哪里减少了呢?
要搞清楚这个问题,首先要搞清楚什么是“重绘重排”。重绘重排是浏览器的行为,浏览器的重绘(repaint)和重排(reflow)是指浏览器对网页进行重新渲染的过程。重排是指重新计算网页布局的过程,而重绘则是根据新的布局信息重新绘制网页的过程。它们的区别在于,重排会导致元素的尺寸、位置、内容等属性的变化,因此需要重新计算布局信息;而重绘则是在元素的位置和尺寸等属性不变的情况下,重新绘制元素的样式。(原文链接:https://blog.csdn.net/tyxjolin/article/details/129865766)
然后要搞清楚浏览器渲染网页的原理(参考我的《浏览器渲染原理》文件),简单讲就是网页会被浏览器渲染引擎转换为DOM树结构,而重绘重排就是改变了DOM树结构导致的结果。
那么为什么diff算法可以减少重绘重排呢?因为diff算法有一个重要操作:给DOM树打补丁。这个就是重点,diff算法将DOM节点转换为JS对象,通过算法比对,将差异的地方标记出来(记为patch),然后将patch打补丁到DOM树上(操作原生JS时,虽然更改的是同样的地方,但实际上是更改了一大块DOM结构。这是由DOM本身造成的,一个普通DOM身上的属性太多太多了,而diff算法可以极大的精细化这个差异点。),这样就会引起浏览器重新渲染,而这个重新渲染的过程,浏览器其实也知道该怎么渲染,浏览器不是重新渲染全部DOM,而是重绘重排,将差异化的内容渲染到页面上。
打补丁的过程,参考vue设计原理,这里有一个简单实现:https://juejin.cn/post/7151916642652389389
xxxxxxxxxx3112<html lang="en">34 <head>5 <meta charset="UTF-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>Document</title>9 </head>1011 <body>12 <!-- 准备一个容器 -->13 <div id="test"></div>14 <!-- 引入react核心库,核心库必须最先引入 -->15 <script src="../js/react.development.js"></script>16 <!-- 引入react-dom,用于支持react操作DOM -->17 <script src="../js/react-dom.development.js"></script>18 <!-- 引入babel,用于将jsx转为js -->19 <script src="../js/babel.min.js"></script>2021 <!-- 注意:此处type的值必须为text/babel,用于标识出此处是jsx代码,需要babel来转译 -->22 <script type="text/babel">23 // 1、创建虚拟DOM24 // 此处一定不要写引号,因为这里是虚拟DOM,不是字符串25 const VDOM = <h1>Hello,React</h1>26 // 2、渲染虚拟DOM到容器27 ReactDOM.render(VDOM,document.getElementById("test"))28 </script>29 </body>3031</html>说明:
因为markdown里面的代码对于
<script type="text/babel"></script>里面的渲染不是很好,都是白色的代码,不管是html还是jsx的显示效果都不好,所以后面的代码中,只要文件类型是html的,我都只保留了jsx的部分,另外加上了部分html的代码,所以要看清楚。

1、纯JS方式(一般不用)
xxxxxxxxxx2812<html lang="en">34 <head>5 <meta charset="UTF-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>使用js创建虚拟DOM</title>9 </head>1011 <body>12 <div id="test"></div>13 <script src="../js/react.development.js"></script>14 <!-- 引入react-dom,用于支持react操作DOM -->15 <script src="../js/react-dom.development.js"></script>1617 <!-- 由于不用babel进行转义,所以这里的type就是最普通的text/javescript -->18 <script type="text/javascript">19 // 1、创建虚拟DOM,使用的是React的createElement方法,React.createElement(标签名,标签属性,标签内容)20 const VDOM = React.createElement('h1', {21 id: 'title'22 }, 'Hello,React')23 // 2、渲染虚拟DOM到页面24 ReactDOM.render(VDOM, document.getElementById("test"))25 </script>26 </body>2728</html>可以看到,使用js来创建虚拟DOM,不需要引入babel.js,使用的是React.createElement方法。事实上,使用jsx来写的内容,都会被babel转义为js的这些内容,但是这个转义的过程不需要管,属于react工作原理的范畴,编写的时候,要用jsx的方式来编写,这样才达到了发明jsx的目的,简化编写过程。
2、JSX方式
xxxxxxxxxx3312<html lang="en">34 <head>5 <meta charset="UTF-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>使用jsx创建虚拟DOM</title>9 </head>1011 <body>12 <div id="test"></div>13 <script src="../js/react.development.js"></script>14 <!-- 引入react-dom,用于支持react操作DOM -->15 <script src="../js/react-dom.development.js"></script>16 <!-- 引入babel,用于将jsx转为js -->17 <script src="../js/babel.min.js"></script>1819 <!-- 注意:此处必须写text/babel,用于标识出此处是jsx代码,需要babel来转译 -->20 <script type="text/babel">21 // 1、创建虚拟DOM22 // 此处一定不要写引号,因为这里是虚拟DOM,不是字符串。可以使用小括号包裹起来,然后里面可以换行,写成层次分明的结构。23 const VDOM = (24 <h1 id="title">25 Hello,React26 </h1>27 )28 // 2、渲染虚拟DOM到页面29 ReactDOM.render(VDOM,document.getElementById("test"))30 </script>31 </body>3233</html>使用jsx的方式来创建虚拟DOM,可以非常方便的添加元素,同时嵌套信息也很清楚。
const VDOM = React.createElement('',{id:''},'')
上面创建的就是一个简单的虚拟DOM对象。

React.createElement(component,props, ...children)方法的语法糖①写法:var ele = <h1>Hello JSX!</h1>
②注意1:它不是字符串, 也不是HTML/XML标签
③注意2:它最终产生的就是一个JS对象
①遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
②遇到以 {开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
①浏览器不能直接解析JSX代码,需要babel转译为纯JS的代码才能运行
②只要用了JSX,都要加上type="text/babel",声明需要babel来处理
xxxxxxxxxx5512<html lang="en">34 <head>5 <meta charset="UTF-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>jsx语法规则</title>9 <style>10 .title {11 background-color: orange;12 width: 200px;13 }14 </style>15 </head>1617 <body>18 <div id="test"></div>19 <!-- 引入react核心库 -->20 <script src="../js/react.development.js"></script>21 <!-- 引入react-dom,用于支持react操作DOM -->22 <script src="../js/react-dom.development.js"></script>23 <!-- 引入babel,用于将jsx转为js -->24 <script src="../js/babel.min.js"></script>2526 <!-- 注意:此处必须写text/babel,用于标识出此处是jsx代码,需要babel来转译 -->27 <script type="text/babel">28 const myId = 'aTguiGu'29 const myData = 'HeLlo,ReacT'3031 //1.创建虚拟DOM32 const VDOM = (33 <h2 className="title" id={myId.toLowerCase()}>34 <span style={{color:'white',fontSize:'30px'}}>{myData.toLowerCase()}</span>35 </h2>36 )37 //2.渲染虚拟DOM到页面38 ReactDOM.render(VDOM,document.getElementById("test"))3940 /*41 jsx语法规则:42 1、定义虚拟DOM时,不能在最外层写引号。43 2、标签中混入js表达式时,要用{} 。44 3、样式的类名不要用class,要用className 。45 4、内联样式,要用style={{key:value}}的形式去写。样式名要使用驼峰写法。(说明:这里的{{}}不是插值表达式,而是表示:最外层还是按照第2条规则来操作,即 用{}来混入js表达式,里面一层的{}表示一个JS对象,要理解这一点。)(样式名使用驼峰写法不是那么难,为什么要使用驼峰写法,因为js无法正确解析 background-color 这里的 - ,如果真的想使用 background-color 这种写法,可以加上英文引号"",这是JS对象里面的基础内容。)46 5、虚拟DOM必须只有一个根标签。(那react复杂项目应该怎么组合起来呢?难道像vue那样的单页面应用吗?后面学习了之后再来回答。------2023.04.07,确实是这样的,app应用只有一个根标签,与vue一样,是用来做SPA应用的。但是通过配置,可以做MPA项目。不要小看了SPA项目,我目前所能想象到的复杂的项目不一定非要使用MPA来做,SPA完全可以胜任。)47 6、标签必须闭合,单标签必须注意要写 / 来闭合。48 7、标签首字母49 1)若标签以小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。50 2)若标签以大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。(所以自定义的组件,使用的时候,首字母必须大写,这是一种规范)51 */52 </script>53 </body>5455</html>ReactDOM.render(virtualDOM,containerDOM)①参数一: 纯js或jsx创建的虚拟dom对象
②参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
需求: 动态展示如下列表


语句和表达式是不同的,这一点一定要弄清楚。那么在jsx的
{}里面,数据为空时显示数据空提示图片,有数据时显示表格界面,应该怎么做呢?这时候不能使用if或switch来判断,因为这是js语句,而应该使用三元表达式,三元表达式总会返回一个值。
xxxxxxxxxx31{2hasData ? <Empty /> : <Table />3}如果有嵌套的判断条件,就嵌套使用三元表达式,这是我目前看到的解决方案,如果有更好的方法,就记录下来。
xxxxxxxxxx4812<html lang="en">34 <head>5 <meta charset="UTF-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1.0">8 <title>jsx语法规则</title>9 <style>10 .title {11 background-color: orange;12 width: 200px;13 }14 </style>15 </head>1617 <body>18 <div id="test"></div>19 <script src="../js/react.development.js"></script>20 <!-- 引入react-dom,用于支持react操作DOM -->21 <script src="../js/react-dom.development.js"></script>22 <!-- 引入babel,用于将jsx转为js -->23 <script src="../js/babel.min.js"></script>24 <script type="text/babel">25 const title = '前端js框架列表'26 const data = ['Angular','React','Vue']2728 //1.创建虚拟DOM29 const VDOM = (30 <div>31 <h1>32 {title}33 </h1>34 <ul>35 // 注意,为什么这里不能写for循环,因为{}里面是可以写js表达式的,但没有说{}里面可以写js语句,而表达式与语句是不同的,上面的图片有总结。36 {37 data.map((item,index)=>{38 return <li key={index}>{item}</li>39 })40 }41 </ul>42 </div>43 )44 //2.渲染虚拟DOM到页面45 ReactDOM.render(VDOM,document.getElementById("test"))46 </script>47 </body>48</html>当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用





函数式组件:
xxxxxxxxxx191<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/babel.min.js"></script>5678//1、创建函数式组件,函数式组件名首字母必须大写。9function MyComponent(){10 console.log(this);//此处的this是undefined,因为babel编译后开启了严格模式11 return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>12}13//2、渲染组件到页面14ReactDOM.render(<MyComponent/>,document.getElementById("test"))15/*16 执行了ReactDOM.render(<MyComponent/>,document.getElementById("test"))之后发生了什么?17 1、React解析组件标签,找到了MyComponent组件。18 2、发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。19*/
类式组件:

xxxxxxxxxx301<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/babel.min.js"></script>56789//1、创建类式组件。类式组件必须继承React.Component这个类,这是固定的写法。10class MyComponent extends React.Component{11 render(){12 console.log(this);13 //render是放在哪里的? 是放在类的原型对象上,供实例使用。(这里有一个疑问,这个实例是怎么出现的?一般类的实例都是 new 类 出现的,可是下面的代码中没有出现new啊。原因是:下面使用到了MyComponent的标签,所以类的实例化操作由react来完成了。)14 //render指向的this是什么? 类的实例对象。也可以说成是MyComponent组件实例对象。15 return (16 <h2>17 我是用类定义的组件(适用于【复杂组件】的定义)18 </h2>19 )20 }21}2223//2、渲染组件到页面24ReactDOM.render(<MyComponent/>,document.getElementById("test"))25/*26执行了ReactDOM.render(<MyComponent/>,document.getElementById("test"))之后发生了什么?271、React解析组件标签,找到了MyComponent组件。282、发现组件是使用类定义的,随后new一个该类的实例,并通过该实例调用到原型上的render方法。293、将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。30*/复杂组件和简单组件可以根据有没有state(状态)来区分,有state的就是复杂组件,没有就是简单组件。(当然,这是老师为了让教学更简单做出的定义,其实函数式组件可以使用hooks来定义state,函数式组件是现在react主推的编写方式,具体看项目要求)

三大核心属性的前提是用class定义的组件,而function定义的组件是不会有这三大属性的(在hooks之前)。这里的标题“组件三大核心属性之一”其实是“组件实例的三大核心属性之一”,这里简写了。
但是最新版的react提供了hooks方法,也可以让function定义的组件有三大属性,那是后面的内容,到时候可以仔细比较。
需求: 定义一个展示天气信息的组件
1. 默认展示天气炎热 或 凉爽
2. 点击文字切换天气

a) 强制绑定this: 通过函数对象的bind()
b) 箭头函数
例子:
xxxxxxxxxx421<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/babel.min.js"></script>5678//1、创建组件9class Weather extends React.Component{10 // 构造器调用了1次11 constructor(props){12 super(props)13 //初始化状态14 this.state = {isHot:false}15 //解决了changeWeather中this指向问题16 this.changeWeather = this.changeWeather.bind(this)17 }18 // render调用几次?--- 调用1+n次,1是初始化的那次,n是状态更新的次数19 render(){20 // react专门把原生html标签上的事件重新写了一份,比如说这里的onclick,原生html标签上是写成onclick,但是react上必须写成onClick(采用驼峰命名方式),照着写就行了。至于到底有哪些事件,可以查看原生html的标签的事件。这里的事件方法(比如此处的this.changeWeather)后面不要加小括号,直接写事件方法名称即可。那如果函数需要传参应该怎么做呢?往后面看你就知道了。21 return <h2 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h2>22 }23 // changeWeather调用几次?--- 点击几次就调用几次24 changeWeather(){25 /*26 1、changeWeather放在哪里? 放在了Weather的原型对象上,供实例使用。27 2、由于changeWeather是作为onClick的回调,所以不是通过实例调用的,而是直接调用的。28 3、类中的方法默认开启了局部的严格模式(这一点我还不知道,老师讲的比较好,可以仔细听一下),所以changeWeather中的this为undefined。这是一个难以理解的点,因为constructor和render方法都默认被react处理了,所以这两个里面的this指向没有问题,但是自定义的方法就需要在constuctor里面显式的绑定this。29 */30 // console.log(this)31 // state里面的数据不能直接更改,下面确实更改了this.state.isHot的值,但是页面不会刷新。因为react不会对此做出反应。32 // this.state.isHot = !this.state.isHot3334 let isHot = this.state.isHot35 this.setState({36 isHot:!isHot37 })38 }39}4041//2、将组件渲染到页面42ReactDOM.render(<Weather/>,document.getElementById('test'))可以将上面的代码精简一下:
xxxxxxxxxx281<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/babel.min.js"></script>5678//1、创建组件9class Weather extends React.Component{10 // 初始化状态。这里之所以可以这样写,是因为类中可以直接写赋值语句,表示给类的实例都添加这个属性和属性值。11 // 那props呢?不需要显式的声明继承吗?不需要,原因是react帮助我们处理了。12 state = {isHot:false}13 render(){14 // 要经常使用ES6的解构赋值15 const { isHot } = this.state16 return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h2>17 }18 // 自定义方法--要用赋值语句的形式,并且用箭头函数19 changeWeather = () => {20 let { isHot } = this.state21 this.setState({22 isHot:!isHot23 })24 }25}2627//2、将组件渲染到页面28ReactDOM.render(<Weather/>,document.getElementById('test'))需求: 自定义用来显示一个人员信息的组件
1. 姓名必须指定,且为字符串类型;
2. 性别为字符串类型,如果性别没有指定,默认为男
3. 年龄为数字类型,默认值为18

xxxxxxxxxx631<div id="test1"></div>2<div id="test2"></div>3<div id="test3"></div>4<div id="test4"></div>5<script src="../js/react.development.js"></script>6<script src="../js/react-dom.development.js"></script>7<!--用于对组件标签属性进行限制-->8<script src="../js/prop-types.js"></script>9<script src="../js/babel.min.js"></script>1011121314//1、创建组件15class Person extends React.Component{16 render(){17 // props是只读的18 const {name,age,sex} = this.props19 return (20 <ul>21 <li>姓名:{name}</li>22 <li>性别:{sex}</li>23 <li>年龄:{age}</li>24 </ul>25 )26 }27}2829// 类型、必要性的限制,如果不符合限制条件,则会报错,但不会终止运行。30Person.propTypes = {31 name:PropTypes.string.isRequired,32 age:PropTypes.number,33 sex:PropTypes.string,34 // 值得注意的是,如果限制一个属性必须为函数类型,则应该写成下面这样,而不能用PropTypes.function35 speak:PropTypes.func,36}3738// 默认属性值39Person.defaultProps = {40 age:18,41 sex:'男'42}4344function speak(){45 console.log('speak')46}4748//2、将组件渲染到页面49// 上面规定了age的数据类型为number,但是这样的传递方式 age="19" 传递过去的是string类型,可以这样写 age={19},这样传递过去的age就是number类型了。50ReactDOM.render(<Person name="tom" age="19" sex="男" speak={speak}/>,document.getElementById('test1'))51ReactDOM.render(<Person name="jerry" age="18" sex="男"/>,document.getElementById('test2'))52ReactDOM.render(<Person name="lily" age="17" sex="女"/>,document.getElementById('test3'))5354// 如果数据结构复杂,可以使用扩展运算符来简写,前提是key的名称要和“类型必要性限制”里面规定的一致。55/*56 注意:原生JS的扩展运算符不能直接展开对象,即...p是会报错的。但是可以在外面包裹一层{}(就是表示对象的括号),相当于是复制一个对象,没有问题。57 但是在React这里的{...p},外面一层{}不是对象的括号,而是相当于插值表达式的符号,那为什么不报错呢?按照老师的说法是因为58 有了React.js和babel.js,所以能这样用,但仅仅适用于标签属性的传递,别的地方是不能这样使用的。59*/6061// 下面的用法叫作“批量传递标签属性”或者叫作“批量传递props”62const p = {name:'hiky',age:20,sex:'女'}63ReactDOM.render(<Person {p}/>,document.getElementById("test4"))props简写方式:
xxxxxxxxxx461<div id="test1"></div>2<div id="test2"></div>3<div id="test3"></div>4<div id="test4"></div>5<script src="../js/react.development.js"></script>6<script src="../js/react-dom.development.js"></script>7<script src="../js/prop-types.js"></script>8<script src="../js/babel.min.js"></script>910111213//1、创建组件14class Person extends React.Component{15 // static的这种写法,本身就是JS类中定义类属性的方法,react中可以直接使用的。16 // 这里的属性名 propTypes 和 defaultProps 是固定的,因为需要给react来进行处理。17 // 类型限制18 static propTypes = {19 name:PropTypes.string.isRequired,20 age:PropTypes.number,21 sex:PropTypes.string22 }23 // 默认属性值24 static defaultProps = {25 age:18,26 sex:'男'27 }28 render(){29 const {name,age,sex} = this.props30 return (31 <ul>32 <li>姓名:{name}</li>33 <li>性别:{sex}</li>34 <li>年龄:{age}</li>35 </ul>36 )37 }38}3940//2、将组件渲染到页面41ReactDOM.render(<Person name="tom" age="19" sex="男"/>,document.getElementById('test1'))42ReactDOM.render(<Person name="jerry" age="18" sex="男"/>,document.getElementById('test2'))43ReactDOM.render(<Person name="lily" age="17" sex="女"/>,document.getElementById('test3'))4445const p = {name:'hiky',age:20,sex:'女'}46ReactDOM.render(<Person {p}/>,document.getElementById("test4"))xxxxxxxxxx11this.props.name第一种方式(React v15.5 开始已弃用):
xxxxxxxxxx41Person.propTypes = {2 name: React.PropTypes.string.isRequired,3 age: React.PropTypes.number4}第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
xxxxxxxxxx51Person.propTypes = {2 name: PropTypes.string.isRequired,3 age: PropTypes.number,4 speak: PropTypes.func // 这个很特殊,限制为函数的话,要写成 func5}xxxxxxxxxx11<Person {person}/>xxxxxxxxxx41 Person.defaultProps = {2 age: 18,3 sex:'男'4 }
xxxxxxxxxx71// react类定义组件时,constructor能不写就不写,这里只是为了说明一种特殊的情况。23constructor(props){4 // 构造器中是否接收props,是否传递给super,取决于:是否希望在 构造器中 通过this访问props。否则不需要在构造器中使用super来接收props5 super(props)6 console.log(this.props)//打印所有属性7}因为函数可以接收参数,所以组件实例的三大属性里面,函数式组件可以使用props。
xxxxxxxxxx361<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11function Person(props){12 const {name,age,sex} = props13 return (14 <ul>15 <li>姓名:{name}</li>16 <li>性别:{sex}</li>17 <li>年龄:{age}</li>18 </ul>19 )20}2122// 类型限制,如果不符合限制条件,则会报错,但不会终止运行。23Person.propTypes = {24 name:PropTypes.string.isRequired,25 age:PropTypes.number,26 sex:PropTypes.string27}2829// 默认属性值30Person.defaultProps = {31 age:18,32 sex:'男'33}3435//2、将组件渲染到页面36ReactDOM.render(<Person name="tom" age="19" sex="男"/>,document.getElementById('test'))需求: 自定义组件, 功能说明如下:
1. 点击按钮, 提示第一个输入框中的值
2. 当第2个输入框失去焦点时, 提示这个输入框中的值
效果如下:

组件内的标签可以定义ref属性来标识自己。
xxxxxxxxxx11<input ref="input1"/>xxxxxxxxxx331<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Demo extends React.Component{12 render(){13 return (14 <div>15 <input ref="input1" type="text" placeholder="点击按钮提示数据" />16 <button onClick={this.showData}>点我提示左侧的数据</button>17 <input ref="input2" onBlur={this.showData1} type="text" placeholder="失去焦点提示数据" />18 </div>19 )20 }21 22 showData = ()=>{23 alert(this.refs.input1.value)24 }25 26 showData1 = ()=>{27 const {input2} = this.refs28 alert(input2.value)29 }30}3132//2、将组件渲染到页面33ReactDOM.render(<Demo/>,document.getElementById('test'))字符串形式的ref存在效率问题,所以官方现在不推荐使用,但是react16.8版本之前的项目都在大量使用,所以还是有必要了解并使用的,react16.8之后的版本推荐下面两种使用方式。
而且字符串式的ref,使用起来也不是那么方便,完全可以用
useRef()来代替。
xxxxxxxxxx11<input ref={(c)=>{this.input1 = c}} // 这里的 c 可以输出看一下,是当前ref所在的dom节点。这里的参数 c 是谁提供的呢?是react提供的,那么就需要一个变量来接收这个参数,所以直接使用this.input1 = c 来接收参数(在类里面可以不预先定义属性,直接使用 this.属性名 来赋值,然后使用)。react设计了这种写法,所以直接用即可。xxxxxxxxxx331<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Demo extends React.Component{12 // 回调函数里面的参数可以简写为c,并且可以将箭头函数简写。但是要注意这个c是什么,c是表示当前标签的dom节点。13 render(){14 return (15 <div>16 <input ref={(currentNode)=>{this.input1 = currentNode}} type="text" placeholder="点击按钮提示数据" />17 <button onClick={this.showData}>点我提示左侧的数据</button>18 <input ref={c=>this.input2=c} onBlur={this.showData1} type="text" placeholder="失去焦点提示数据" />19 </div>20 )21 }22 showData = ()=>{23 // 注意:ref的回调用法,取值就不在this.refs上取值了,而是直接在this上取值。24 alert(this.input1.value)25 }26 showData1 = ()=>{27 const {input2} = this28 alert(input2.value)29 }30}3132//2、将组件渲染到页面33ReactDOM.render(<Demo/>,document.getElementById('test'))关于回调 refs 的说明(摘自官方文档)
如果
ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
虽然写成内联形式会有这种问题,但还是推荐写成内联形式,因为没什么影响。如果非要较真,那么就写成绑定函数的形式。
xxxxxxxxxx331<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>67//1、创建组件8class Demo extends React.Component{9 showInfo = ()=>{10 const {input} = this11 alert(input.value)12 }13 state = {isHot:false}14 changeWeather = ()=>{15 const {isHot} = this.state16 this.setState({17 isHot:!isHot18 })19 }20 render(){21 const {isHot} = this.state22 return (23 <div>24 <h2 onClick={this.changeWeather}>今天天气很{isHot ? '寒冷' : '炎热'}</h2>25 <input type="text" ref={c=>{this.input=c;console.log("@",c)}} /> 26 <button onClick={this.showInfo}>点我提示数据</button>27 </div>28 )29 }30}3132//2、将组件渲染到页面33ReactDOM.render(<Demo/>,document.getElementById('test'))
可以使用绑定函数的形式避免这种问题:
xxxxxxxxxx421<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Demo extends React.Component{12 showInfo = ()=>{13 const {input} = this14 alert(input.value)15 }16 state = {isHot:false}17 changeWeather = ()=>{18 const {isHot} = this.state19 this.setState({20 isHot:!isHot21 })22 }23 saveInput = (c)=>{24 this.input1 = c25 console.log("@",c);26 }27 render(){28 const {isHot} = this.state29 return (30 <div>31 <h2 onClick={this.changeWeather}>今天天气很{isHot ? '寒冷' : '炎热'}</h2>32 {/* 注意:jsx里面的注释就是使用这种方式 <input type="text" ref={c=>{this.input=c;console.log("@",c)}} />*/} 33 {/* 注意这种写法,ref={this.saveInput},看上去就像绑定了一个事件,不是的,是绑定了一个元素。很容易搞混啊,特别是我还看不懂这个js写法的深刻含义时,特别要注意。 */}34 <input type="text" ref={this.saveInput} />35 <button onClick={this.showInfo}>点我提示数据</button>36 </div>37 )38 }39}4041//2、将组件渲染到页面42ReactDOM.render(<Demo/>,document.getElementById('test'))
这种方式是目前React官方最推荐的写法。
xxxxxxxxxx21myRef = React.createRef() 2<input ref={this.myRef} />xxxxxxxxxx331<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Demo extends React.Component{12 // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的,也就意味着必须用一个就创建一个,不能混着用。13 myRef = React.createRef()14 myRef1 = React.createRef()15 render(){16 return (17 <div>18 <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />19 <button onClick={this.showData}>点我提示左侧的数据</button>20 <input ref={this.myRef1} onBlur={this.showData1} type="text" placeholder="失去焦点提示数据" />21 </div>22 )23 }24 showData = ()=>{25 alert(this.myRef.current.value)26 }27 showData1 = ()=>{28 alert(this.myRef1.current.value)29 }30}3132//2、将组件渲染到页面33ReactDOM.render(<Demo/>,document.getElementById('test'))通过onXxx属性指定事件处理函数(注意大小写)
1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件(为了更好兼容性)
2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素),目的是为了高效。
通过event.target得到发生事件的DOM元素对象(不要过度使用ref,如果发生事件的元素是你要操作的元素,那么直接用event.target)
官方提示不要过度使用ref,那么就可以使用event.target得到发生事件的DOM元素对象。
xxxxxxxxxx261//1、创建组件2class Demo extends React.Component{3myRef = React.createRef()4myRef1 = React.createRef()5render(){6return (7<div>8<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />9<button onClick={this.showData}>点我提示左侧的数据</button>10{/*<input ref={this.myRef1} onBlur={this.showData1} type="text" placeholder="失去焦点提示数据" /> 就可以改为下面的样子*/}11<input onBlur={this.showData1} type="text" placeholder="失去焦点提示数据" />12</div>13)14}15showData = ()=>{16alert(this.myRef.current.value)17}18showData1 = (event)=>{19//alert(this.myRef1.current.value)20// 参数event是html标签自带的,如果发生事件的元素是你要操作的元素,那么直接用这种方法21alert(event.target.value)22}23}2425//2、将组件渲染到页面26ReactDOM.render(<Demo/>,document.getElementById('test'))
注意:
上面对使用event.target的使用似乎限定了范围,“如果发生事件的元素是你要操作的元素,那么直接用这种方法”,那点击按钮提示左侧输入框数据只能使用ref了吗?
当然不是,左侧的input框也可以绑定onBlur、onChange等事件,使用event.target,然后取出值保存在state中,等点击按钮的时候从state中拿出来使用。所以要借助state的能力。
需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息

包含表单的组件分类
输入类组件的内容需要用的时候就取出来,就是非受控组件。用到了表单本身的一个属性event.preventDefault(),阻止表单提交。
xxxxxxxxxx291<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Login extends React.Component{12 handleSubmit = (event)=>{13 event.preventDefault()//阻止表单提交。表单提交是一个默认事件,form上不写action或者action为空,都不能阻止提交事件,所以需要使用event来阻止。14 const {username,password} = this15 alert(`用户名是:${username.value},密码是:${password.value}`)16 }17 render(){18 return(19 <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>20 用户名:<input ref={c=>this.username=c} type="text" name="username" />21 密码:<input ref={c=>this.password=c} type="password" name="password" />22 <button>登录</button>23 </form>24 )25 }26}2728//2、将组件渲染到页面29ReactDOM.render(<Login/>,document.getElementById('test'))受控组件:所有输入类的DOM,随着输入,及时将输入内容保存到状态中,要使用的时候就从状态里面拿出来,就叫受控组件。推荐使用受控组件,因为受控组件里面几乎没有用到ref,这是官方文档所推荐的(并不是说不能使用ref,如果一些情况下非写ref不可,那就直接写,不要有心理负担)。
xxxxxxxxxx511<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Login extends React.Component{12 //初始化状态13 state = {14 username:'',15 password:''16 }1718 //保存用户名到状态中19 saveUsername = (e)=>{20 this.setState({21 username:e.target.value22 })23 }2425 //保存密码到状态中26 savePassword = (e)=>{27 this.setState({28 password:e.target.value29 })30 }3132 //表单提交的回调33 handleSubmit = (event)=>{34 event.preventDefault()//阻止表单提交35 const {username,password} = this.state36 alert(`用户名是:${username},密码是:${password}`)37 }3839 render(){40 return(41 <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>42 用户名:<input onChange={this.saveUsername} type="text" name="username" />43 密码:<input onChange={this.savePassword} type="password" name="password" />44 <button>登录</button>45 </form>46 )47 }48}4950//2、将组件渲染到页面51ReactDOM.render(<Login/>,document.getElementById('test'))大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由react组件负责处理。如果使用非受控组件的话,控制能力较弱,表单数据由dom本身处理,但更加方便快捷,代码量少。
应用场景如下图:

既然非受控组件的应用场景这么少,那么就都用受控组件就行了。
在讲组件的生命周期之前,老师讲了一下高阶函数和函数柯里化,下面这个概念非常重要,可以帮助我减轻很大的负担,注意这个柯里化的写法,我想了很久都没有写出来,我之前写vue和小程序的时候,早就想这么写了,但就是写不出来:
xxxxxxxxxx571<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910/*11高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数121、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。132、若A函数,调用后的返回值是一个函数,那么A就可以称之为高阶函数。14常见的高阶函数:Promise、settimeout、数组的一些方法,如:map()等等。1516函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式。17*/18//1、创建组件19class Login extends React.Component{20//初始化状态21state = {22username:'',23password:''24}2526//保存表单数据到状态中27saveFormData = (dataType)=>{28// 刚开始可能不知道这里的event是怎么来的?是这样的:onChange事件本身要绑定一个事件,是一个函数。那么我构造一个函数,它返回的就是个函数,就可以放在onChange属性上了。onChange事件真正绑定的是这个返回的函数,所以这里的event就可以传递过来了。29// 另外一点就是,高阶函数为我们传递了一个参数,根据闭包原则,这个参数是可以在返回的函数里面使用的,没有问题。30return (event)=>{31this.setState({32// 注意这个写法 [dataType] ,很重要33[dataType]:event.target.value34})35}36}3738//表单提交的回调39handleSubmit = (event)=>{40event.preventDefault()//阻止表单提交41const {username,password} = this.state42alert(`用户名是:${username},密码是:${password}`)43}4445render(){46return(47<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>48用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />49密码:<input onChange={this.saveFormData('password')} type="password" name="password" />50<button>登录</button>51</form>52)53}54}5556//2、将组件渲染到页面57ReactDOM.render(<Login/>,document.getElementById('test'))不用柯里化的写法:
xxxxxxxxxx441<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>678910//1、创建组件11class Login extends React.Component{12//初始化状态13state = {14username:'',15password:''16}1718//保存表单数据到状态中19saveFormData = (dataType,value)=>{20this.setState({21[dataType]:value22})23}2425//表单提交的回调26handleSubmit = (event)=>{27event.preventDefault()//阻止表单提交28const {username,password} = this.state29alert(`用户名是:${username},密码是:${password}`)30}3132render(){33return(34<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>35用户名:<input onChange={event=>this.saveFormData('username',event.target.value)} type="text" name="username" />36密码:<input onChange={event=>this.saveFormData('password',event.target.value)} type="password" name="password" />37<button>登录</button>38</form>39)40}41}4243//2、将组件渲染到页面44ReactDOM.render(<Login/>,document.getElementById('test'))不用柯里化的写法为什么可以这样写?因为onChange后面直接写了一个函数,而这个函数本身就有一个event的参数,可以直接提供给别的函数。
高阶组件能够提高代码的复用性和灵活性,在实际应用中,常常用于与核心业务无关但又在多个模块使用的功能,如权限控制、日志记录、数据校验、异常处理、统计上报等。
需求:定义组件实现以下功能:
1. 让指定的文本做显示 / 隐藏的渐变动画
2. 从完全可见,到彻底消失,耗时2S
3. 点击“不活了”按钮从界面中卸载组件

xxxxxxxxxx6512<html lang="en">3 <head>4 <meta charset="UTF-8" />5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />7 <title>引出生命周期</title>8 </head>9 <body>10 <div id="test"></div>1112 <script src="../old version react/react.development.js" type="text/javascript"></script>13 <script src="../old version react/react-dom.development.js" type="text/javascript"></script>14 <script src="../old version react/babel.min.js" type="text/javascript"></script>15 <script src="../old version react/prop-types.js" type="text/javascript"></script>1617 <script type="text/babel">18 class Life extends React.Component {19 state = {20 opacity: 1,21 };2223 death = () => {24 // 卸载组件25 ReactDOM.unmountComponentAtNode(document.getElementById("test"));26 };2728 // 组件挂载完毕29 componentDidMount() {30 // 这里为什么能够用this.timer,timer在之前并没有进行定义啊?其实这是对类的用法不熟悉,上面的state也没有预先定义啊,其实类里面是可以使用赋值语句的,变量不需要预先定义。31 this.timer = setInterval(() => {32 let { opacity } = this.state;33 opacity = opacity - 0.1;34 // 这里为什么要用 <= 0,而不是直接 === 0 呢?因为很多语言都有小数的计算问题,所以这里用 <= 0 是必须的。35 if (opacity <= 0) {36 opacity = 1;37 }38 this.setState({39 opacity,40 });41 }, 200);42 }4344 // 组件将要卸载45 componentWillUnmount() {46 // 卸载之前清除定时器47 clearInterval(this.timer);48 }4950 render() {51 const { opacity } = this.state;52 return (53 <div>54 <h1 style={{ opacity: opacity }}>我会慢慢变淡</h1>55 <button onClick={this.death}>消失了</button>56 </div>57 );58 }59 }6061 ReactDOM.render(<Life />, document.getElementById("test"));62 </script>63 </body>64</html>65从例子中可以看出,componentDidMount、componentWillUnmount与render函数一样,都是react提供的函数,所以写法都可以写成函数的形式。

生命周期的三个阶段(旧)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1)constructor()
2)componentWillMount()
3)render(),必须使用的一个钩子函数。
4)componentDidMount(),此钩子函数很常用,一般在这里做初始化的事情,比如说:开启定时器、发送网络请求、订阅消息。
2. 更新阶段: 由组件内部this.setState()或父组件重新render触发
1)shouldComponentUpdate()
2)componentWillUpdate()
3)render()
4)componentDidUpdate()
更新的过程有三种情况:
注意:看这张图时,并不是“挂载时”和“父组件render”组成了两部分,这个并不是标题,而是一个触发机制名称,应该是这样看:“挂载时”、“父组件render”、“setState()”、“forceUpdate()”是类似的东西,即一种触发机制名称。

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1)componentWillUnmount(),此钩子函数常用,一般在这里做一些收尾的事情,例如:关闭定时器、取消订阅消息。
xxxxxxxxxx7912<div id="test"></div>3<script src="../js/react.development.js"></script>4<script src="../js/react-dom.development.js"></script>5<script src="../js/prop-types.js"></script>6<script src="../js/babel.min.js"></script>78//1、创建组件9class Count extends React.Component{10 //构造器11 constructor(props){12 console.log('Count---constructor');13 super(props)14 //初始化状态15 this.state = {count:0}16 }1718 // 组件将要挂载的钩子函数19 componentWillMount(){20 console.log('Count---componentWillMount');21 }2223 // 组件挂载完毕的钩子函数24 componentDidMount(){25 console.log('Count---componentDidMount');26 }2728 // 可以当作控制组件更新的“阀门”。可以不写shouldComponentUpdate函数,如果写了,就必须显式地返回一个boolean值。29 shouldComponentUpdate(){30 console.log('Count---shouldComponentUpdate');31 return true32 }3334 // 组件将要更新的钩子函数35 componentWillUpdate(){36 console.log('Count---componentWillUpdate');37 }3839 // 组件更新完毕的钩子函数40 componentDidUpdate(){41 console.log('Count---componentDidUpdate');42 }4344 // 组件卸载前的钩子函数45 componentWillUnmount(){46 console.log('Count---componentWillUnmount');47 }4849 add = ()=>{50 let {count} = this.state51 count += 152 this.setState({53 count:count54 })55 }5657 delete = ()=>{58 ReactDOM.unmountComponentAtNode(document.getElementById('test'))59 }6061 // 强制更新62 force = ()=>{63 // 这个方法是哪里来的?下面的图是官网的解释,forceUpdate()方法是React组件的方法,所以这里用this来使用。64 this.forceUpdate()65 }6667 render(){68 console.log('Count---render');69 const {count} = this.state70 return (71 <div>72 <h2>当前求和为:{count}</h2>73 <button onClick={this.add}>点我+1</button>74 <button onClick={this.delete}>卸载组件</button>75 <button onClick={this.force}>不修改任何东西,强制更新</button>76 </div>77 )78 }79}
xxxxxxxxxx451<div id="test"></div>2<script src="../js/react.development.js"></script>3<script src="../js/react-dom.development.js"></script>4<script src="../js/prop-types.js"></script>5<script src="../js/babel.min.js"></script>67<script type="text/babel">8 // 这里给出了一个使用子组件的例子,是写在同一个script标签中,然后在父组件中直接使用子组件的标签就行了。9 class Father extends React.Component{10 state = {carName:'奔驰'}1112 changeCar = ()=>{13 this.setState({14 carName:'宝马'15 })16 }1718 render(){19 return (20 <div>21 <h2>我是Father组件</h2>22 <button onClick={this.changeCar}>换车</button>23 <Son carName={this.state.carName}/>24 </div>25 )26 }27 }2829 class Son extends React.Component{30 render(){31 return (32 <div>33 <h2>我是Son组件,接收到的车是{this.props.carName}</h2>34 </div>35 )36 }3738 // 组件将要接收属性的钩子函数,有一个参数。这个生命周期函数有一个坑,就是第一次接收时不会执行这个钩子函数,但是子组件确实接收到props,因此在render中的this.props可以正常使用,是会在第一次展示出来的,但这个函数componentWillReceiveProps确实在第一次不会执行。39 componentWillReceiveProps(props){40 console.log('Son---componentWillReceiveProps',props);41 }42 }43 //2、将组件渲染到页面44 ReactDOM.render(<Father/>,document.getElementById('test'))45</script>
xxxxxxxxxx851<div id="test"></div>2<script src="../js/17.0.1/react.development.js"></script>3<script src="../js/17.0.1/react-dom.development.js"></script>4<script src="../js/17.0.1/prop-types.js"></script>5<script src="../js/17.0.1/babel.min.js"></script>678910//1、创建组件11class Count extends React.Component{12 //构造器13 constructor(props){14 console.log('Count---constructor');15 super(props)16 //初始化状态17 this.state = {count:0}18 }1920 // 若state的值在任何时候都取决于props,那么可以使用此钩子函数。否则不需要使用。21 static getDerivedStateFromProps(props,state){22 console.log('Count---getDerivedStateFromProps',props,state);23 return null24 }2526 // 获取更新前的快照27 getSnapshotBeforeUpdate(){28 console.log('Count---getSnapshotBeforeUpdate');29 // 此处必须返回一个值,可以是null或其它值,这个值会传递给componentDidUpdate()的第三个参数30 return 'baidu'31 }3233 // 组件挂载完毕的钩子函数34 componentDidMount(){35 console.log('Count---componentDidMount');36 }3738 // 控制组件更新的“阀门”39 shouldComponentUpdate(){40 console.log('Count---shouldComponentUpdate');41 return true42 }4344 // 组件更新完毕的钩子函数(注意,旧生命周期里面的componentDidUpdate函数是没有接收参数的)45 componentDidUpdate(prevProps,prevState,snapshotValue){46 console.log('Count---componentDidUpdate',prevProps,prevState,snapshotValue);47 }4849 // 组件卸载前的钩子函数50 componentWillUnmount(){51 console.log('Count---componentWillUnmount');52 }5354 add = ()=>{55 let {count} = this.state56 count += 157 this.setState({58 count:count59 })60 }6162 delete = ()=>{63 ReactDOM.unmountComponentAtNode(document.getElementById('test'))64 }6566 force = ()=>{67 this.forceUpdate()68 }6970 render(){71 console.log('Count---render');72 const {count} = this.state73 return (74 <div>75 <h2>当前求和为:{count}</h2>76 <button onClick={this.add}>点我+1</button>77 <button onClick={this.delete}>卸载组件</button>78 <button onClick={this.force}>不修改任何东西,强制更新</button>79 </div>80 )81 }82}8384//2、将组件渲染到页面85ReactDOM.render(<Count/>,document.getElementById('test'))

老师用了一个例子来说明getSnapshotBeforeUpdate()函数有什么作用:
一个新闻列表,当新闻内容多了之后,产生滚动条,用户在滚动到某个位置的时候,需要固定在这个位置,让用户能够好好地看,由于新闻不断增加,实际上的位置都会回到最新的位置。需要解决这个问题。
xxxxxxxxxx7112<html lang="en">3 <head>4 <meta charset="UTF-8" />5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />7 <title>newsList案例</title>8 <style>9 .news-list {10 width: 200px;11 height: 150px;12 background-color: paleturquoise;13 overflow: auto;14 }15 .news {16 height: 30px;17 }18 </style>19 </head>20 <body>21 <div id="test"></div>2223 <script src="../new version react/react.development.js" type="text/javascript"></script>24 <script src="../new version react/react-dom.development.js" type="text/javascript"></script>25 <script src="../new version react/babel.min.js" type="text/javascript"></script>26 <script src="../new version react/prop-types.js" type="text/javascript"></script>2728 <script type="text/babel">29 class NewsList extends React.Component {30 state = {31 news: [],32 };3334 componentDidMount() {35 // 添加定时器,不断添加新闻36 this.timer = setInterval(() => {37 const { news } = this.state;38 let newsItem = "新闻" + (news.length + 1);39 this.setState({40 news: [newsItem, ...news],41 });42 }, 1000);43 }4445 getSnapshotBeforeUpdate() {46 // 返回 newslist更新之前的高度47 const { newslist } = this;48 return newslist.scrollHeight;49 }5051 componentDidUpdate(prevProps, prevState, snapshotValue) {52 const { newslist } = this;53 // scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。这里是累加操作,目的就是为了保持当前的位置。54 newslist.scrollTop += newslist.scrollHeight - snapshotValue;55 }56 render() {57 return (58 <div class="news-list" ref={(currentNode) => (this.newslist = currentNode)}>59 {this.state.news.map((item) => {60 return <div class="news">{item}</div>;61 })}62 </div>63 );64 }65 }6667 ReactDOM.render(<NewsList />, document.getElementById("test"));68 </script>69 </body>70</html>71
这个案例实现的就是:滚动条滚动到哪里,就可以固定在哪里,这在微信里面可以看到,比如说关注了很多公众号,公众号不断推送消息,我想看哪一个消息就滚动到哪里,不会因为推送的消息而产生滚动。这个功能还是蛮实用的。
总结:生命周期的三个阶段(新)
初始化阶段: 由ReactDOM.render()触发---初次渲染
1)constructor()
2)getDerivedStateFromProps
3)render()
4)componentDidMount()
更新阶段: 由组件内部this.setState()或父组件重新render触发
1)getDerivedStateFromProps
2)shouldComponentUpdate()
3)render()
4)getSnapshotBeforeUpdate
5)componentDidUpdate()
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1)componentWillUnmount()
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
1、getDerivedStateFromProps
2、getSnapshotBeforeUpdate
面试题:react的新、旧生命周期有什么区别?
react新生命周期废弃了3个生命周期钩子函数:componentWillMount、componentWillReceiveProps、componentWillUpdate,新增了2个生命周期钩子函数:getDerivedStateFromProps、getSnapshotBeforeUpdate,再说一说新钩子函数的作用即可。
可以在这个网站找到很多前端开源项目CDN地址 https://www.bootcdn.cn/ 。
需求:验证虚拟DOM Diffing算法的存在

xxxxxxxxxx321<div id="test"></div>2<script src="../js/17.0.1/react.development.js"></script>3<script src="../js/17.0.1/react-dom.development.js"></script>4<script src="../js/17.0.1/prop-types.js"></script>5<script src="../js/17.0.1/babel.min.js"></script>678910//1、创建组件11class Time extends React.Component{12 state = {date:new Date()}13 componentDidMount(){14 setInterval(()=>{15 this.setState({16 date:new Date()17 })18 },1000)19 }20 render(){21 return (22 <div>23 <h1>Hello</h1>24 <input type="text" />25 <span>现在是:{this.state.date.toTimeString()}</span>26 </div>27 )28 }29}3031//2、将组件渲染到页面32ReactDOM.render(<Time/>,document.getElementById('test'))从上面可以看出,diffing算法的最小粒度是标签,span标签里面的“现在是:”这几个文字虽然没有变化,但是react没有办法识别出这些文字没有变化,只能识别出标签变化没有。同时标签是有层级的,所以diffing还是可以识别出不同层级标签的diffing的,举个例子:一个顶级标签,里面一个内容变化了,里面的其余标签是不是会跟着变化呢?不会跟着变化,因为层级不一样,diffing是可以识别的。
为什么需要Key?
xxxxxxxxxx7412<html lang="en">3 <head>4 <meta charset="UTF-8" />5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />7 <title>key的作用</title>8 </head>9 <body>10 <div id="test"></div>1112 <script src="../new version react/react.development.js" type="text/javascript"></script>13 <script src="../new version react/react-dom.development.js" type="text/javascript"></script>14 <script src="../new version react/babel.min.js" type="text/javascript"></script>15 <script src="../new version react/prop-types.js" type="text/javascript"></script>1617 <script type="text/babel">18 class Demo extends React.Component {19 state = {20 persons: [21 { id: 1, name: "tom", age: 18 },22 { id: 2, name: "jerry", age: 19 },23 ],24 };25 add = () => {26 const { persons } = this.state;27 const p = {28 id: persons.length + 1,29 name: "cherry",30 age: 20,31 };32 this.setState({33 persons: [p, ...persons],34 });35 };36 render() {37 return (38 <div>39 <h1>点击按钮添加人员</h1>40 <button onClick={this.add}>添加</button>41 <h3>使用index索引值作为key</h3>42 <ul>43 {this.state.persons.map((item, index) => {44 return (45 <li key={index}>46 {item.name}---{item.age}47 <input type="text" />48 </li>49 );50 })}51 </ul>52 <hr />53 <hr />54 <hr />55 <h3>使用id(唯一标识符)作为key</h3>56 <ul>57 {this.state.persons.map((item) => {58 return (59 <li key={item.id}>60 {item.name}---{item.age}61 <input type="text" />62 </li>63 );64 })}65 </ul>66 </div>67 );68 }69 }7071 ReactDOM.render(<Demo />, document.getElementById("test"));72 </script>73 </body>74</html>input里面的值是自己输入进去的,供查看不同的key对应在添加人员后的情况。


经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
- 虚拟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,随后渲染到到页面
- 用index作为key可能会引发的问题:
1、若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2、如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3、注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 开发中如何选择key?:
1、最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2、如果确定只是简单的展示数据,用index也是可以的。

提示:
在写react项目时,标签写起来很麻烦,不能像写html标签那样,写出标签然后按tab键就自动生成了。现在按tab键是没有反应的,需要进行配置。
打开vscode的settings.json文件,然后加上这段代码:
xxxxxxxxxx71就写在JSON的里面最外层2{3"emmet.triggerExpansionOnTab":true,4"emmet.includeLanguages": {5"javascript":"javascriptreact"6}7}然后写双标签的就可以这样做:先写标签名,然后按tab键,就自动生成了。写单标签可以这样做:先写
标签名/,然后按tab键,就自动生成了。注意在js和jsx中写标签时,前面应该有个空格,否则按tab键会将前面的部分都当成标签名。示例:
① 包含了所有需要的配置(语法检查、jsx编译、devServer…)
② 下载好了所有相关的依赖
③ 可以直接运行一个简单效果
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
项目生成之后,有一个
yarn eject或者npm run eject命令,这个命令是把create-react-app脚手架封装好的webpack文件全部显示出来,让用户来配置。默认create-react-app是隐藏webpack的文件的,而且没有必要完全可以不管这个webpack是怎么运行的。除非你非常牛,原理什么的都搞清楚了,可以来配置,否则没有必要来配置。
老师说到了一个很重要的点,我以前没有搞懂的,就是脚手架是怎么做出来的。其实vue-cli、create-react-app都是基于webpack做出来的,最初学习vue的时候,我也学过webpack,只不过后来学习了脚手架之后,就没有webpack来配置项目了。不过有时间还是可以将webpack搞清楚的,因为有时候真的需要对项目进行配置。
xxxxxxxxxx131 public ---- 静态资源文件夹23 favicon.icon ------ 网站页签图标45 index.html -------- 主页面。里面的`%PUBLIC_URL%`表示public文件夹的路径。67 logo192.png ------- logo图89 logo512.png ------- logo图10 11 manifest.json ----- 应用加壳的配置文件1213 robots.txt -------- 爬虫协议文件/public/index.html文件说明:
xxxxxxxxxx2612<html lang="en">3 <head>4 <meta charset="utf-8" />5 <!-- %PUBLIC_URL表示public文件夹的路径% -->6 <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />7 <!-- 开启理想视口,用于做移动端网页的适配 -->8 <meta name="viewport" content="width=device-width, initial-scale=1" />9 <!-- 用于配置浏览器页签和地址栏的颜色(仅支持安卓手机浏览器,且兼容性很差,基本上用不着) -->10 <meta name="theme-color" content="#000000" />11 <!-- 网站的说明信息,用于给搜索引擎识别, -->12 <meta name="description" content="Web site created using create-react-app" />13 <!-- 用于指定网页添加到手机主屏幕后的图标 -->14 <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />15 <!-- 应用加壳时的配置文件。如果不将这个react项目加壳为ios或android应用,就不需要管这个文件。 -->16 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />17 <title>React App</title>18 </head>19 <body>20 <!-- 若浏览器不支持JavaScript的运行,则显示标签中的内容 -->21 <noscript>You need to enable JavaScript to run this app.</noscript>22 <!-- 项目的根节点 -->23 <div id="root"></div>24 </body>25</html>26
xxxxxxxxxx211src ---- 源码文件夹23 App.css -------- App组件的样式45 App.js --------- App组件67 App.test.js ---- 用于给App做测试89 index.css ------ 通用的样式1011 index.js -------入口文件1213 logo.svg ------- logo图1415 reportWebVitals.js1617 --- 页面性能分析文件(需要web-vitals库的支持)1819 setupTests.js2021 ---- 组件单元测试的文件(需要jest-dom库的支持)/src/index.js文件:
xxxxxxxxxx181import React from "react";2import ReactDOM from "react-dom/client";3import "./index.css";4import App from "./App";56// 页面性能分析文件,reportWebVitals.js文件需要很多配置才能能到想要的结果。了解即可。7import reportWebVitals from "./reportWebVitals";89const root = ReactDOM.createRoot(document.getElementById("root"));10root.render(11 // React.StrictMode标签的含义是:帮助检查写的react代码有没有不合理的地方,比如说字符串的ref等,会出现报警提示。建议写React.StrictMode标签,因为react框架的api变化很快,说不定哪个时候原来学的api就不推荐使用了。12 <React.StrictMode>13 <App />14 </React.StrictMode>15);1617reportWebVitals();18react项目主要的文件:public/index.html、src/app.js、src/index.js。
有一点需要澄清:我记得刚开始学习vue的时候,老师说到了vue是用来做SPA项目的,但是我一直有一个误解,就是如果不是SPA,是更复杂的项目,可以用react来写,叫作MPA(多页面应用程序)。可是我看了create-react-app创建的项目,主文件还是一个root根节点,天禹老师也明确说了react是用来做SPA项目的,这里要明确一点,react主要是用来做SPA项目的,但是可以用来做MPA项目,参考:https://blog.csdn.net/weixin_28710515/article/details/107709288。关于SPA和MPA项目的区别,可以参考:https://www.jianshu.com/p/a8b5eb1f0f5d。
看了老师的讲解,发现React的项目结构还是蛮熟悉的,因为小程序是模仿react做的,所以结构是类似的。小程序规定的详细一些。
重点:import react, {Component} from "react"这里的{Component}不是解构赋值,而是分别暴露的对象的引入操作。

在之前的学习React的时候,类定义的组件都是extends React.Component,说明React身上确实有Component这个属性,但是react库里面同时定义了Component这个类,可以直接引入。就像上面的例子那样。
react中使用import引入文件时,js和jsx文件可以不写后缀名。


老师说样式的模块化用的不多,用的多的还是less或sass,但必须要知道可以这样用。
但是这里还没有讲到怎么给项目里面添加less或sass,因为在vue项目中,加入了less或sass是需要进行配置的,这里还没有讲怎么使用。

先记住下面几个快捷键,别的再说。
| Prefix | Method |
|---|---|
imr→ | import React from 'react' |
imrd→ | import ReactDOM from 'react-dom' |
imrc→ | import React, { Component } from 'react' |
imrpc→ | import React, { PureComponent } from 'react' |
imrm→ | import React, { memo } from 'react' |
imrr→ | import { BrowserRouter as Router, Route, NavLink} from 'react-router-dom' |
imbr→ | import { BrowserRouter as Router} from 'react-router-dom' |
imbrc→ | import { Route, Switch, NavLink, Link } from react-router-dom' |
imbrr→ | import { Route } from 'react-router-dom' |
imbrs→ | import { Switch } from 'react-router-dom' |
imbrl→ | import { Link } from 'react-router-dom' |
imbrnl→ | import { NavLink } from 'react-router-dom' |
imrs→ | import React, { useState } from 'react' |
imrse→ | import React, { useState, useEffect } from 'react' |
redux→ | import { connect } from 'react-redux' |
est→ | this.state = { } |
cdm→ | componentDidMount = () => { } |
scu→ | shouldComponentUpdate = (nextProps, nextState) => { } |
cdup→ | componentDidUpdate = (prevProps, prevState) => { } |
cwun→ | componentWillUnmount = () => { } |
gdsfp→ | static getDerivedStateFromProps(nextProps, prevState) { } |
gsbu→ | getSnapshotBeforeUpdate = (prevProps, prevState) => { } |
sst→ | this.setState({ }) |
ssf→ | this.setState((state, props) => return { }) |
props→ | this.props.propName |
state→ | this.state.stateName |
rcontext→ | const $1 = React.createContext() |
cref→ | this.$1Ref = React.createRef() |
fref→ | const ref = React.createRef() |
bnd→ | this.methodName = this.methodName.bind(this) |
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.3 保存在哪个组件?
3.2 交互(从绑定事件监听开始)