一名中/高级前端工程师的自检清单-React 篇
共 9000字,需浏览 18分钟
·
2021-12-14 01:50
你真的了解 React 吗?我们在面试中往往涉及 React 时,第一个问题就是“解释 React 是什么”。解释一种技术是什么,在面试中也是非常常见的引起 话题的题目。本篇文章我就带你掌握这一类概念题的解答技巧。
一. 说说对 React 的理解,有哪些特性
官方的解释:React 是一个 UI 库,它的核心思想是UI=F(data)
, 即界面的呈现是由函数传入的参数决定的
开发者不再需要关心界面时如何渲染的,只要关心数据的生成和传递,这大大提高的开发者的开发效率,节省了开发时间
其次 React 设计的
使用类似 HTML 的 JSX
语法来描述视图通过 虚拟DOM
修改真实DOM
通过 setState
修改数据在不同的 生命周期
阶段做不同的事源码底层对真实 DOM 事件进行封装,使用 事件委托
的方式来捕获 DOM 事件....
等特性进一步简化的 真实 DOM 操作的复杂性
二. 说说真实 DOM 与虚拟 DOM 的区别,优缺点
2.1 虚拟 DOM 是什么
真实DOM
就是我们在浏览器开发者工具中看到的DOM
结构虚拟DOM
简单来说就是 JS 对象,此对象中的字段包含了对真实DOM
的描述:type
:是什么标签/元素props
:标签/元素有哪些属性children
:是否有子元素
2.2 虚拟 DOM 大概是如何工作的
当 DOM
操作(渲染更新)比较频繁时,
React 底层会先将前后两次的虚拟DOM
树进行对比,
定位出具体需要更新的部分,生成一个补丁集
,
最后只把“补丁”打在需要更新的那部分真实DOM
上,实现精准的“差量更新”。
2.3 虚拟 DOM 的优点
解决了频繁操作真实 DOM 的低效率工作-不直接操作 DOM,数据驱动视图,也在一定程度上提升了性能 解决了扩平台开发的问题,因为虚拟 DOM 描述的东西可以是真实 DOM,也可以是安卓界面。IOS 界面等等,这就可以对接不同平台的渲染逻辑。从而实现"一次编码,多端运行"(如 React,React Native)
2.4 虚拟 DOM 的缺点
如果当虚拟 DOM 的构建和diff
的过程相对复杂(比如很多递归遍历等操作),那么虚拟 DOM 的 JS 计算是比较耗时的
三. 说说 Diff 算法
一般的原始 diff 思路算法复杂度是O(n^3)
,即循环递归进行树节点的一一对比
但 React 的 diff 算法
是 O (n)
复杂度的思路
当对比两棵虚拟 DOM 树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态
当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。
当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树,这大大减少了 Diff
过程中冗余的递归操作当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性 列表形式的子元素比较:React 引入了 key 属性
。当子元素拥有key
时,React 使用key
来匹配原有树上的子元素以及最新树上的子元素,如果key
不同 不同则会拆卸原有的key
节点并且建立起新的key
节点
详细内容请参考React 官方文档- Diffing 算法[2]
四. 说说 React 声明周期有哪些不同阶段,每个阶段对应的方法是什么
4.1 创建阶段
constructor()
:组件的构造函数,组件更新到界面上之前会先调用用于初始化内部状态,很少使用 唯一可以直接修改 state
的地方static getDerivedStateFromProps(nextProps, prevState)
:用于从外部的属性去初始化一些内部的状态当 state
需要从props
初始化时使用尽量不要使用,维护 state/props
状态一致性会增加复杂度每次 render
都会调用典型场景: 表单控件获取默认值 render()
::组件必须定义的一个生命周期方法,用来描述 虚拟 DOM 结构componentDidMount()
: 用于数据请求,定义一些外部资源等等副作用UI 渲染完成后调用 只执行一次 典型场景:获取外部资源
4.2 更新阶段
static getDerivedStateFromProps(nextProps, prevState)
shouldComponentUpdate(nextProps, nextState)
:告诉组件是否需要重新渲染,用于性能优化,比如判定指定 props 发生改变,组件才进行重新渲染决定虚拟 DOM 是否需要重绘 一般可以由 PureComponent
自动实现典型场景:性能优化 render()
getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次渲染输出(提交到 DOM 节点)之前调用,state 已更新 与 componentDidUpdate
搭配使用典型场景:捕获 render
之前的 DOM 状态componentDidUpdate(prevProps, prevState)
每次 UI 更新时被调用 典型场景:页面需要根据 props
变化重新获取数据
4.3 卸载阶段
componentWillUnmount()
: 做些资源释放,卸载副作用的事情
此方法中可以执行必要的清理操作,例如,清除 timer,取消网络请求或清除在
详细内容请参考React 知识体系之生命周期及使用场景[3]
五. 说说对 State 和 Props 的理解,有什么区别
state
用于组件内部数据传递,state
数据可以通过this.setSate
或者useState
进行修改props
用于组件外部组件数据传递,props
不能直接修改.主要使用场景是:兄弟组件通信 父子组件通信 "爷孙组件"
组件通信props
的使用范围虽然更加广泛,但也有其局限性:对于嵌套层次较深的组件,如果使用props
传递数据,会导致代码冗余,增加数据传递的复杂度
六. super 和 super(props)有什么区别
在 JavaScript 中,super 指的是父类构造函数。(在我们的示例中,它指向 React.Component 实现。)
在调用父类的构造函数之前,你是不能在 constructor
中使用 this
关键字的。JavaScript 不允许这个行为
class Checkbox extends React.Component {
constructor(props) {
// 🔴 还不能使用 `this`
super(props);
// ✅ 现在可以了
this.state = { isOn: true };
}
// ...
}
复制代码
为什么一定要传递 props
呢?为了让 React.Component
构造函数能够初始化 this.props
React 内部代码:
// React 內部
class Component {
constructor(props) {
this.props = props;
// ...
}
}
复制代码
实例代码:
class Button extends React.Component {
constructor(props) {
super(); // ? We forgot to pass props
console.log(props); // ✅ {}
console.log(this.props); // ? undefined
}
// ...
}
复制代码
class Button extends React.Component {
constructor(props) {
super(props); // ✅ We passed props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
复制代码
有了 Hooks 以后,我们几乎就不需要 super
和 this
了
详细内容请参考为什么我们要写 super(props) ?[4]
七. 说说 React 中的 setState 机制
7.1 合成事件、钩子函数中的 setState
在钩子函数中 setSate
拿不到最新值在合成事件中执行多个同样的 setSate
,最终只会执行一次,并且也拿不到最新值
原因:
一次 setState
就会触发一次re-render
(重渲染)为了避免频繁的 re-ernder
,setState
被设计成异步的形式每来一个 setState
,就把它塞进一个队列里“攒起来”。等时机成熟,再把“攒起来”的state
结果做合并(对于相同属性的设置,React 只会为其保留最后一次的更新),最后只针对最新的state
值走一次更新流程。这个过程,叫作**批量更新
**
7.2 setTimeout/setInterval
、原生 DOM
中的 setState
在 setTimeout/setInterval
中设置setState
,可以拿到最新的值在 原生 DOM
事件中设置setState
,可以拿到最新的值
原因:
setState
的“异步”并不是说内部由异步代码实现,其实源码本身执行的过程和代码都是同步的,
只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”
setState
的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和 setTimeout
中不会批量更新
详细内容请参考setState 到底是同步的,还是异步的?[5]
八. 说说对 React 事件机制的理解
8.1 React 中的事件是什么
React 中的事件叫合成事件
:React 底层使用事件委托
的方式对真实 DOM 事件进行了封装,使合成事件具有更好的浏览器兼容性和性能
8.2 合成事件的大致原理
当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例
8.3 React 为什么要重新设计出一个合成事件
合成事件是 React 自定义的事件对象,它符合 W3C 规范,在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。
开发者们由此便不必再关注烦琐的兼容性问题,可以专注于业务逻辑的开发。
虽然合成事件并不是原生 DOM 事件,但它保存了原生 DOM 事件的引用。当你需要访问原生 DOM 事件对象时,可以通过合成事件对象的 e.nativeEvent
属性获取到它合成事件无法获取到真实 DOM,但可以通过 React 提供 ref
API 进行获取
详细内容请参考React 事件与 DOM 事件有何不同?[6]
九. React 事件绑定的方式有哪些
9.1 类组件
9.1.1 render 方法中使用 bind
这种方式在组件每次 render 渲染的时候,都会重新进行 bind 的操作,影响性能
class App extends React.Component {
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={this.handleClick.bind(this)}>test</div>;
}
}
复制代码
9.1.2 render 方法中使用箭头函数
通过 ES6 的上下文来将 this 的指向绑定给当前组件,同样在每一次 render 的时候都会生成新的方法,影响性能
class App extends React.Component {
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={(e) => this.handleClick(e)}>test</div>;
}
}
复制代码
9.1.3 constructor 中 bind
在 constructor 中预先 bind 当前组件,可以避免在 render 操作中重复绑定
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("this > ", this);
}
render() {
return <div onClick={this.handleClick}>test</div>;
}
}
复制代码
9.1.4 定义阶段使用箭头函数绑定
能够避免在 render 操作中重复绑定
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log("this > ", this);
};
render() {
return <div onClick={this.handleClick}>test</div>;
}
}
复制代码
9.2 函数式组件
箭头函数
函数组件没有实例,因此没有this
const App = () => {
handleClick = (e) => {
console.log(e);
};
return <div onClick={this.handleClick}>test</div>;
};
复制代码
详细内容请参考React 构建组件的方式有哪些[7]
十. React 构建组件的方式有哪些
类组件 高阶组件 render props
纯函数组件 Hooks
组件自定义 Hooks
详细内容请参考React 构建组件的方式有哪些[8]
十一. React 中组件通信的方式有哪些
单个组件内部数据传递 state
父组件向子组件传递 props
子组件向父组件传递 props
兄弟组件之间的通信 props
父组件向后代组件传递 props
Context API
Redux
发布-订阅模式
EventBus
非关系组件传递 Context API
Redux
发布-订阅模式
EventBus
十二. React 的 key 有什么作用
说到 React 的 key,就要说到 React 的 Diff 算法
详细内容请参考React 列表循环为什么需要 key[9]
参考资料
https://juejin.cn/post/6978685539985653767: https://juejin.cn/post/6978685539985653767
[2]https://zh-hans.reactjs.org/docs/reconciliation.html#the-diffing-algorithm: https://link.juejin.cn?target=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Freconciliation.html%23the-diffing-algorithm
[3]https://juejin.cn/post/6981739846461030408: https://juejin.cn/post/6981739846461030408
[4]https://overreacted.io/zh-hans/why-do-we-write-super-props/: https://link.juejin.cn?target=https%3A%2F%2Foverreacted.io%2Fzh-hans%2Fwhy-do-we-write-super-props%2F
[5]https://www.yuque.com/u221766/xgl0mb/oxl3ik: https://link.juejin.cn?target=https%3A%2F%2Fwww.yuque.com%2Fu221766%2Fxgl0mb%2Foxl3ik
[6]https://www.yuque.com/u221766/xgl0mb/iu68y0: https://link.juejin.cn?target=https%3A%2F%2Fwww.yuque.com%2Fu221766%2Fxgl0mb%2Fiu68y0
[7]https://juejin.cn/post/6952907248393781284#heading-2: https://juejin.cn/post/6952907248393781284#heading-2
[8]https://juejin.cn/post/6952907248393781284: https://juejin.cn/post/6952907248393781284
[9]https://juejin.cn/post/6940974776441634823: https://juejin.cn/post/6940974776441634823
来源:望道同学
https://juejin.cn/post/6981831831112908831