);
};
上面代码中,输入框和 checkbox 的变化,均是经过了 React 来操作的,在数据变更时,React 是能够知道的。
#### 高阶组件(HOC)?
#### 高阶组件?
高阶组件通过包裹(wrapped)被传入的 React 组件,经过一系列处理,最终返回一个相对增强(enhanced)的 React 组件,供其他组件调用。
作用:
1. 复用逻辑:高阶组件更像是一个加工 react 组件的工厂,批量对原有组件进行加工,包装处理。我们可以根据业务需求定制化专属的 HOC,这样可以解决复用逻辑。
2. 强化 props:这个是 HOC 最常用的用法之一,高阶组件返回的组件,可以劫持上一层传过来的 props,然后混入新的 props,来增强组件的功能。代表作 react-router 中的 withRouter。
3. 赋能组件:HOC 有一项独特的特性,就是可以给被 HOC 包裹的业务组件,提供一些拓展功能,比如说额外的生命周期,额外的事件,但是这种 HOC,可能需要和业务组件紧密结合。典型案例 react-keepalive-router 中的 keepaliveLifeCycle 就是通过 HOC 方式,给业务组件增加了额外的生命周期。
4. 控制渲染:劫持渲染是 hoc 一个特性,在 wrapComponent 包装组件中,可以对原来的组件,进行条件渲染,节流渲染,懒加载等功能,后面会详细讲解,典型代表做 react-redux 中 connect 和 dva 中 dynamic 组件懒加载。
参考:[react 进阶」一文吃透 React 高阶组件(HOC)]( )")
#### React 中为什么要使用 Hook?
官方网站有介绍该原因:[使用 Hook 的动机]( )。
这里我们简要的提炼下:
1. 在组件之间复用状态逻辑很难:在类组件中,可能需要 render props 和 高阶组件等方式,但会形成“嵌套地域”;而使用 Hook,则可以从组件中提取状态逻辑,是的这些逻辑可以单独测试并复用;
2. 复杂组件变得难以理解:在类组件中,每个生命周期常常包含一些不相关的逻辑。如不同的执行逻辑,都要放在`componentDidMount`中执行和获取数据,而之后需在 `componentWillUnmount` 中清除;但在函数组件中,不同的逻辑可以放在不同的 Hook 中执行,互不干扰;
3. 难以理解的 class:类组件中,充斥着各种对 `this` 的使用,如 `this.onClick.bind(this)`,`this.state`,`this.setState()` 等,同时,class 不能很好的压缩,并且会使热重载出现不稳定的情况;Hook 使你在非 class 的情况下可以使用更多的 React 特性;
#### useCallback 和 useMemo 的使用场景
useCallback 和 useMemo 可以用来缓存函数和变量,提高性能,减少资源浪费。但并不是所有的函数和变量都需要用这两者来实现,他也有对应的使用场景。
我们知道 useCallback 可以缓存函数体,在依赖项没有变化时,前后两次渲染时,使用的函数体是一样的。它的使用场景是:
* 函数作为其他 hook 的依赖项时(如在 useEffect()中);
* 函数作为 React.memo()(或 shouldComponentUpdate )中的组件的 props;
主要是为了避免重新生成的函数,会导致其他 hook 或组件的不必要刷新。
useMemo 用来缓存函数执行的结果。如每次渲染时都要执行一段很复杂的运算,或者一个变量需要依赖另一个变量的运算结果,就都可以使用 useMemo()。
参考文章:[React18 源码解析之 useCallback 和 useMemo]( )。
#### useState 的传参方式,有什么区别?
useState()的传参有两种方式:纯数据和回调函数。这两者在初始化时,除了传入方式不同,没啥区别。但在调用时,不同的调用方式和所在环境,输出的结果也是不一样的。
如:
const App = () => {
const [count, setCount] = useState(0);
const handleParamClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
const handleCbClick = () => {
setCount(count => count + 1);
setCount(count => count + 1);
setCount(count => count + 1);
};
};
上面的两种传入方式,最后得到的 count 结果是不一样的。为什么呢?因为在以数据的格式传参时,这 3 个使用的是同一个 count 变量,数值是一样的。相当于`setCount(0 + 1)`,调用了 3 次;但以回调函数的传参方式,React 则一般地会直接该回调函数,然后得到最新结果并存储到 React 内部,下次使用时就是最新的了。注意:这个最新值是保存在 React 内部的,外部的 count 并不会马上更新,只有在下次渲染后才会更新。
还有,在定时器中,两者得到的结果也是不一样的:
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 500);
return () => clearInterval(timer);
}, []);
useEffect(() => {
const timer = setInterval(() => {
setCount(count => count + 1);
}, 500);
return () => clearInterval(timer);
}, []);
};
#### 为什么在本地开发时,组件会渲染两次?
[issues#2]( )
在 React.StrictMode 模式下,如果用了 useState,usesMemo,useReducer 之类的 Hook,React 会故意渲染两次,为的就是将一些不容易发现的错误容易暴露出来,同时 React.StrictMode 在正式环境中不会重复渲染。
也就是在测试环境的严格模式下,才会渲染两次。
#### 如何实现组件的懒加载
从 16.6.0 开始,React 提供了 lazy 和 Suspense 来实现懒加载。
import React, { lazy, Suspense } from ‘react’;
const OtherComponent = lazy(() => import(‘./OtherComponent’));
function MyComponent() { const App = () => { useEffect(() => { return const App = () => { // 监听 count 的变化,不过这里将定时器改成了 setTimeout // 以回调的方式 return // https://overreacted.io/zh-hans/making-setinterval-declarative-with-react-hooks/ useEffect(() => { useEffect(() => { // 运行第一个 effect // 产生更新时 // 产生更新时 // 组件卸载时
return (
);
}
属性`fallback`表示在加载组件前,渲染的内容。
#### 如何实现一个定时器的 hook
若在定时器内直接使用 React 的代码,可能会收到意想不到的结果。如我们想实现一个每 1 秒加 1 的定时器:
const [count, setCount] = useState(0);
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
};
可以看到,coun 从 0 变成 1 以后,就再也不变了。为什么会这样?
![](https://img-blog.csdnimg.cn/img_convert/5a2dd7772499063db0b3919c13fadc91.webp?x-oss-process=image/format,png)
尽管由于定时器的存在,组件始终会一直重新渲染,但定时器的回调函数是挂载期间定义的,所以它的闭包永远是对挂载时 Counter 作用域的引用,故 count 永远不会超过 1。
针对这个单一的 hook 调用,还比较好解决,例如可以监听 count 的变化,或者通过 useState 的 callback 传参方式。
const [count, setCount] = useState(0);
// 即使不修改,setInterval()的timer也会在每次渲染时被清除掉,
// 然后重新启动一个新的定时器
useEffect(() => {
const timer = setTimeout(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, [count]);
// 回调的方式,会计算回调的结果,然后作为下次更新的初始值
// 详情可见: https://www.xiabingbao.com/post/react/react-usestate-rn5bc0.html#5.+updateReducer
useEffect(() => {
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
};
当然还有别的方式也可以实现 count 的更新。那要是调用更多的 hook,或者更复杂的代码,该怎么办呢?这里我们可以封装一个新的 hook 来使用:
const useInterval = (callback: () => void, delay: number | null): void => {
const savedCallback = useRef(callback);
savedCallback.current = callback;
});
function tick() {
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
#### useEffect()的清除机制是什么?在什么时候执行?
useEffect(callback)的回调函数里,若有返回的函数,这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。
React 何时清除 effect? React 会在组件卸载的时候执行清除操作。同时,若组件产生了更新,会先执行上一个的清除函数,然后再运行下一个 effect。如
// 清除上一个 effect
// 运行下一个 effect
// 清除上一个 effect
// 运行下一个 effect
// 清除最后一个 effect
参考:[为什么每次更新的时候都要运行 Effect]( )
### 2. 源码层面上
这部分考察的就更有深度一些了,多多少少得了解一些源码,才能明白其中的缘由,比如 React 的 diff 对比,循环中 key 的作用等。
#### 虚拟 dom 有什么优点?真实 dom 和虚拟 dom,谁快?
Virtual DOM 是以对象的方式来描述真实 dom 对象的,那么在做一些 update 的时候,可以在内存中进行数据比对,减少对真实 dom 的操作减少浏览器重排重绘的次数,减少浏览器的压力,提高程序的性能,并且因为 diff 算法的差异比较,记录了差异部分,那么在开发中就会帮助程序员减少对差异部分心智负担,提高了开发效率。
虚拟 dom 好多这么多,渲染速度上是不是比直接操作真实 dom 快呢?并不是。虚拟 dom 增加了一层内存运算,然后才操作真实 dom,将数据渲染到页面上。渲染上肯定会慢上一些。虽然虚拟 dom 的缺点在初始化时增加了内存运算,增加了首页的渲染时间,但是运算时间是以毫秒级别或微秒级别算出的,对用户体验影响并不是很大。
#### 什么是合成事件,与原生事件有什么区别?
React 中所有触发的事件,都是自己在其内部封装了一套事件机制。目的是为了实现全浏览器的一致性,抹平不同浏览器之间的差异性。
在 React17 之前,React 是把事件委托在 document 上的,React17 及以后版本不再把事件委托在 document 上,而是委托在挂载的容器上。React 合成事件采用的是事件冒泡机制,当在某具体元素上触发事件时,等冒泡到顶部被挂载事件的那个元素时,才会真正地执行事件。
而原生事件,当某具体元素触发事件时,会立刻执行该事件。因此若要比较事件触发的先后时机时,原生事件会先执行,React 合成事件会后执行。
#### key 的作用是什么?
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
当组件刷新时,React 内部会根据 key 和元素的 type,来对比元素是否发生了变化。若选做 key 的数据有问题,可能会在更新的过程中产生异常。
### 总结
秋招即将开始,校招的朋友普遍是缺少项目经历的,所以**底层逻辑,基础知识要掌握好!**
而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。
这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 **HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法**等高频考点**238道(含答案)**!
资料截图 :
![](https://img-blog.csdnimg.cn/img_convert/edeaa71cbc745698bdedcfe65be40387.png)
![](https://img-blog.csdnimg.cn/img_convert/981487b285a8b90918f35c830c299df8.png)
![](https://img-blog.csdnimg.cn/img_convert/9866daffbebcf87b44d9530a59427834.png)
**高级前端工程师必备资料包**
![](https://img-blog.csdnimg.cn/img_convert/ba803e1b69e399b6739e3d33554d42a7.png)