React学习总结
一、邂逅React开发
1. React开发的三个依赖包是什么?分别有什么作用?
- react:包含react所有必须的核心代码
- react-dom:react 渲染在不同平台所需要的核心代码
- babel:将jsx转换为 React 代码的工具
2. React如何封装组件,组件里面包含哪些内容?
类的方式封装组件
定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
.实现当前组件的render函数
1
2
3
4
5
6
7
8
9class App extends React.Component{
render(){
return <h2> Hello React<h2/>
}
}
#渲染根组件
const root =ReactDOM.createRoot(document.querySelector("#r oot"))
#使用组件
root.render(<App/>)
3. 在进行函数绑定时,如何进行this关键字的绑定?
方式一:传入函数使用显示绑定(bind、call、apply)
在传入函数时直接绑定this
1
2
3
4
5
6
7
8render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick.bind(this)}>点击</button>
</div>
)
}
方式二:
在类中提前绑定需要使用的函数的this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class App extends React.Component {
// 组件数据
constructor() {
super()
this.state = {
message: "Hello World",
}
// 对需要绑定的方法, 提前绑定好this
this.btnClick = this.btnClick.bind(this)
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick}>点击</button>
</div>
)
}
}
4. React如何进行列表数据的展示?回顾数组的常见高阶函数。
方式一:直接使用for循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17render() {
// 1.对movies进行for循环
const liEls = []
for (let i = 0; i < this.state.movies.length; i++) {
const movie = this.state.movies[i]
const liEl = <li>{movie}</li>
liEls.push(liEl)
}
return (
<div>
<h2>电影列表</h2>
<ul>
<li> {liEls}</li>
</ul>
</div>
)
}方式二:map 高阶函数
1
2
3
4
5
6
7
8
9
10
11
12
13render()
#返回一个新的数组
const liEls = this.state.movies.map(movie => <li>{movie}</li>)
return (
<div>
<h2>电影列表</h2>
<ul>
<li> {liEls}</li>
</ul>
</div>
)
}方式三: map 高阶函数 表达式
1
2
3<ul>
{this.state.movies.map(movie => <li>{movie}</li>)}
</ul>
5. JSX如何绑定数据?如何绑定内容、属性,有哪些规则?
绑定数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Home extends React.Component {
constructor() {
super();
this.state = {
message: 'BNTang'
}
}
render() {
return (
<div>
<p id="box">{this.state.message}</p>
<p title={this.state.message}>{this.state.message}</p>
</div>
)
}
}
绑定class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Home extends React.Component {
constructor() {
super();
this.state = {
isActive: true,
objStyle: {color: "red", fontSize: "30px"}
}
}
render() {
const { title, imgURL, href, isActive, objStyle } = this.state
#1.class绑定的写法一: 字符串的拼接
const className = `abc cba ${isActive ? 'active': ''}`
# 2.class绑定的写法二: 将所有的class放到数组中
const classList = ["abc", "cba"]
if (isActive) classList.push("active")
# 3.class绑定的写法三: 第三方库classnames -> npm install classnames
return (
<div>
<h2 className={className}>哈哈哈哈</h2>
<h2 className={classList.join(" ")}>哈哈哈哈</h2>
</div>
)
}
}
绑定style
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Home extends React.Component {
constructor() {
super();
this.state = {
message: 'BNTang'
}
}
render() {
return (
<div>
<p style={{color: 'red', fontSize: '100px'}}>{this.state.message}</p>
</div>
)
}
}绑定属性
1
2
3<p title="我是标题">我是段落</p>
<p title={message}>我是段落</p>书写规范
- 顶层只有一个根元素
- jsx 的外层需要包裹一个 () 方便阅读,可以进行换行书写
- jsx的标签可以是单标签,也可以是双标签
- 单标签必须以/>结尾
- jsx中的注释 { /* */}
- JSX嵌入变量作为子元素
- 情况一:当变量是Number、String、Array类型时,可以直接显示
- 情况二:当变量是null、undefined、Boolean类型时,内容为空;
如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式; - 情况三:Object对象类型不能作为子元素(not valid as a React child)
6. JSX如何绑定数据?如何绑定内容、属性,有哪些规则?
二、React的JSX语法解析
1. JSX绑定事件,this绑定有哪些规则?如何给函数传递参数?
this绑定规则
- 普通绑定 -
onClick={this.btnClick}- 在内部是独立函数调用,所以this为undefined - this绑定方式一: bind绑定 -
onClick={this.btnClick.bind(this)} - this绑定方式二: ES6 class fields -
onClick={this.btnClick}-btnClick = () => {} - this绑定方式三: 直接传入一个箭头函数 -
onClick={() => this.btnClick()}
- 普通绑定 -
给函数传递参
- event参数的传递 -
onClick={(event) => this.btn1Click(event)} - 额外参数的传递 -
onClick={(event) => this.btn2Click(event, "http", 18)}
1
2
3
4
5
6btn1Click(event) {
console.log(event);
}
btn2Click(event, name, age) {
console.log(event, name, age);
}- event参数的传递 -
2. JSX的代码是如何被编译为React代码的?它的本质是进行什么操作?
- jsx是通过babel工具转换编译成React代码的
- 本质
- jsx 仅仅只是 React.createElement(component, props, …children) 函数的语法糖
- 所有的jsx最终都会被转换成React.createElement的函数调用
3. 什么是虚拟DOM?虚拟DOM在React中起到什么作用?
- 什么是虚拟DOM?
- Virtual DOM 是一种编程概念,UI以一种理想化或者说虚拟化的方式保存在内存中
- Virtual DOM 本质上是 JavaScript 对象,是真实 DOM 的描述,⽤⼀个 JS 对象来描述⼀个 DOM 节点
- 我们知道jsx转成React代码的本质是 - 转换成React.createElement的函数调用
- 通过React.createElement的函数创建出来的
ReactElement对象 - React利用
ReactElement对象组成了一个JavaScript的对象树 - JavaScript的对象树就是虚拟DOM
- 虚拟DOM在React中的作用
- 虚拟DOM 通过diff算法 - 以最⼩的代价更新变化的视图
- 跨平台渲染
- 声明式编程 - 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
- 你只需要告诉React希望让UI是什么状态
- React来确保DOM和这些状态是匹配的
- 不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来
4. 分析脚手架创建的React项目目录结构,并且删除文件后自己编写代码
- public
- favicon.ico – 应用程序顶部icon图标
- index.html – 应用的index.html入口文件
- logo192.png – 在manifest.json中被使用
- logo512.png – 在manifest.json中被使用
- manifest.json – 与web app配置相关
- robots.text – 指定搜索引擎可以或者不可以爬取那些信息
- src
- App.css – App组件相关样式
- App.js – App组件代码文件
- App.test.js – App组件的测试代码文件
- index.css – 全局样式文件
- index.js – 整个应用程序的入口文件
- logo.svg – 启动项目时,所看到的React图标
- reportWebVitals.js – 默认帮我们写好的 注册pwa相关的代码
- setupTests.js – 测试初始文件
- pwa是什么? 全称Progressive Web App,即渐进式WEB应用
- 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用
- 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
- 这种Web存在的形式,我们也称之为是 Web App
- pwa可以将网页添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
- pwa实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
- pwa实现了消息推送
三、React组件化开发(一)
1. React组件可以如何进行划分?分别有哪些不同的概念?
根据定义方式
- 函数组件
- 类组件
根据组件内部有无状态需要维护
- 无状态组件
- 有状态组件
根据组件的不同职责
- 展示型组件
- 容器型组件
关注UI的展示
- 函数组件,无状态组件,展示型组件
关注数据逻辑的展示
- 类组件,有状态组件,容器型组件
2. React重要的组件生命周期有哪些?分别列出他们的作用。
- componentDidMount
- 会在组件挂载后立即调用
- 操作DOM
- 发送网络请求
- componentDidUpdate
- 会在组件更新后立即调用
- 首次渲染不会执行此方法
- 可以在组件更新后操作DOM
- conponentWillUnmount
- 会在组件卸载及销毁前调用
- 清除定时器
- 清除事件监听
3. React中如何实现组件间的通信?父传子?子传父?
- 父传子
- 在子组件通过属性传递内容
- 子组件通过props拿到内容
- 子传父
- 通过传递函数,调用回调函数
- 通过函数的参数传递要传递的数据
4. React中有插槽的概念吗?如果没有,如何实现插槽的效果呢?
- 没有插槽的概念
- 实现插槽效果的方式
- 直接在组件中插入children
- 在目标组件拿到插入的内容进行展示
- 通过props直接将要展示的内容传递给子组件
- 子组件通过props直接拿到要展示的内容
5. 非父子组件的通信有哪些方式?分别是什么作用?
- 事件总线
- 使用context
- 创建context
- 在要使用的组件,一般是根组件导入context
- 使用<context.Provider>包裹后代组件
- 在要使用的后代组件引入context
- xxxx.contextType = context
- 在render方法中可以通过this.context拿到传递过来的值
6. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?
四、React组件化开发(二)
1. 非父子组件的通信有哪些方式?分别是什么作用?
- Context
- 1.创建Context
- 2.使用<context.Provider>包裹后代组件
- 3.在要使用的后代xxxx组件引入context
- xxxx.contextType = context
- 在render方法中可以通过this.context拿到传递过来的值
- 事件总线EventBus
- Redux
2. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?
在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
React的setState是异步的 – 不要指望在调用
setState之后,this.state会立即映射为新的值在react18之前, 在setTimeout,Promise等中操作setState, 是同步操作
在react18之后, 在setTimeout,Promise等中操作setState,是异步操作(批处理)
- 如果需要同步的处理怎么办呢? 需要执行特殊的
flushSync操作
- 如果需要同步的处理怎么办呢? 需要执行特殊的
为什么要将setState设计成异步的
- 首先,若是将setState设计成同步的,在
componentDidMount中请求多个网络请求时,会堵塞后面的网络请求
1
2
3
4
5
6componentDidMount() {
// 网络请求一 : this.setState
// 网络请求二 : this.setState
// 网络请求三 : this.setState
// 如果this.setState设计成同步的,会堵塞后面的网络请求
}一. setState设计为异步,可以显著的提升性能
- 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
- 最好的办法应该是获取到多个更新,之后进行批量更新
1
2
3
4
5
6// 在一个函数中有多个setState时,
this.setState({}) --> 先不会更新,而是会加入到队列(queue)中 (先进先出)
this.setState({}) --> 也加入到队列中
this.setState({}) --> 也加入到队列中
// 这里的三个setState会被合并到队列中去
// 在源码内部是通过do...while从队列中取出依次执行的二: 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
- 首先,若是将setState设计成同步的,在
3. 什么是SCU优化?类组件和函数组件分别如何进行SCU的优化?
shouldComponentUpdate – SCU – React提供给我们的声明周期方法
SCU优化就是 一种巧妙的技术,用来减少DOM操作次数,具体为当React元素没有更新时,不会去调用render()方法
可以通过
shouldComponentUpdate来判断this.state中的值是否改变1
2
3
4
5
6
7shouldComponentUpdate(nextProps, nextState) {
const {message, counter} = this.state
if(message !== nextState.message || counter !== nextState.counter) {
return true
}
return false
}React已经帮我们提供好SCU优化的操作
- 类组件: 将class继承自
PureComponent - 函数组件: 使用一个高阶组件
memo
1
2
3
4
5
6
7
8
9
10import {mome} from 'react'
const HomeFunc = mome(function(props) {
console.log("函数式组件")
return (
<h4>函数式组件: {props.message}</h4>
)
})
export default HomeFunc- 类组件: 将class继承自
5. React为什么要强调不可变的力量?如何实现不可变的力量?
- 不可变的力量: 不要直接去修改this.state中的值(主要指对象),若是想修改的话,应该是将这整个值全部修改掉
- 注意: 值类型,在修改的时候,本身就全部替换掉了,所以不需要其他操作,直接改就可以
- 实现: 将对象浅拷贝赋值给一个新的变量,在将这个新的变量赋值给this.state中的值
6. React中获取DOM的方式有哪些?
ref获取DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14getDOM() {
// 方式一: 在react元素上绑定ref字符串 - 这种方式react已经不推荐了
// console.log(this.refs.http)
// 方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素(推荐)
// console.log(this.titleRef.current)
// 方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入(16.3之前的版本)
// console.log(this.titleEl)
}
<h3 ref="http">大大怪将军</h3>
<h3 ref={this.titleRef}>小小怪下士</h3>
<h3 ref={el => this.titleEl = el}>瑞克</h3>
<button onClick={() => this.getDOM()}>获取DOM</button>ref获取组件实例 –
createRef1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import React, { PureComponent, createRef } from 'react'
constructor() {
super()
this.state = {}
this.HWRef = createRef()
}
getComponent() {
console.log(this.HWRef.current)
this.HWRef.current.test()
}
<HelloWorld ref={this.HWRef} />
<button onClick={() => this.getComponent()}>获取组件实例</button>ref获取函数组件 – 函数式组件是没有实例的,所以无法通过ref获取他们的实例 –
React.forwardRef1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import React, { PureComponent, createRef, forwardRef } from 'react'
const HelloWorld = forwardRef(function(props, ref) {
return (
<div>
<h2 ref={ref}>函数组件</h2>
<h4>大大怪将军</h4>
</div>
)
})
constructor() {
super()
this.state = {}
this.HWRef = createRef()
}
getComponent() {
console.log(this.HWRef.current)
}
render() {
return (
<div>
<HelloWorld ref={this.HWRef} />
<button onClick={() => this.getComponent()}>获取DOM</button>
</div>
)
}
7. 性能优化:React的diff算法和key的作用
React的渲染流程
- 在render函数中返回jsx, jsx会创建出
ReactElement对象(通过React.createElement的函数创建出来的) ReactElement最终会形成一颗树结构, 这颗树结构就是vDOM- React会根据这样的vDOM渲染出真实DOM
- 在render函数中返回jsx, jsx会创建出
React更新流程
- props/state发生改变
- render函数重新执行
- 产生新的DOM树结构
- 新旧DOM树 进行diff算法
- 计算出差异进行更新
- 最后更新到真实DOM
1
2
3什么是diff算法?
diff算法并非React独家首创,但是React针对diff算法做了自己的优化,使得时间复杂度优化成了O(n)
对比两颗树结构,然后帮助我们计算出vDOM中真正变化的部分,并只针对该部分进行实际的DOM操作,而非渲染整个页面,从而保证了每次操作后页面的高效渲染。React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树
React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的**复杂程度为 O(n³)**,其中 n 是树中元素的数量
如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围
这个开销太过昂贵了,于是,React对这个算法进行了优化,将其优化成了O(n)
同层节点之间相互比较,不会垮节点比较(一旦某个节点不同,那么包括其后代节点都会被替换)

不同类型的节点,产生不同的树结构(当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树)
开发中,可以通过key属性标识哪些子元素在不同的渲染中可能是不变的
在遍历列表时,总是会提示一个警告,让我们加入一个key属性,当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。
- 在最后位置插入数据 – 这种情况,有无key意义并不大
- 在前面插入数据 – 这种做法,在没有key的情况下,所有的li都需要进行修改
- 在中间插入元素 – 新增2014, key为2016元素仅仅进行位移,不需要进行任何的修改
1
2
3
4
5
6
7
8
9
10<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2015">Duke</li>
<li key="2014">Connecticut</li>
<li key="2016">Villanova</li>
</ul>