面试复习题-react

266 阅读10分钟

✊不积跬步,无以至千里;不积小流,无以成江海

React 16/17/18

  1. React16 - 核心更改点:
  • Fiber架构:虽然不是表面上的新特性, React 16 内部重构为 Fiber 架构,提供更好的任务调度和异步渲染能力,提高了性能和灵活性。
  • Context API :官方规范化了上下文API,使状态管理和跨层级组件通信更为方便。
  1. React17 - 核心更改点:
  • 过渡版本,无重大更新
  • 主要更新在事件委托上
  1. React18 - 核心更改点:
  • 并发渲染( Concurrent Mode) :正式推出了并发渲染模式(但仍处于试验阶段),允许React在多个渲染之间切换,根据浏览器空闲时间进行渲染,提升了应用的响应性和交互性。

跨级组件的通信方式?

  1. Context API: React的Context API提供了一种在组件树中共享数据的方法,而无需手动通过每个级别的props传递。你可以创建一个Context对象,并使用<MyContext.Provider>在组件树中的某个位置提供数据。然后,任何子组件(无论多深)都可以使用<MyContext.Consumer>或useContext Hook来访问这些数据。
  2. Redux: Redux是一个流行的状态管理库,它允许你在应用程序的任何地方管理和访问状态。通过使用Redux,你可以创建一个全局可访问的store来存储应用程序的状态,并使用connect函数或useSelector和useDispatch Hooks将组件与store连接起来。
  3. props React ****Hooks (如useState和useReducer)与自定义Hooks: 对于较简单的跨级通信,你可以使用useState和useReducer Hooks在父组件中管理状态,并通过自定义Hooks或props将状态和方法传递给子组件。自定义Hooks允许你封装和重用状态逻辑。
  4. MobX: MobX是另一个状态管理库,它提供了一种更响应式的方法来管理和更新应用程序的状态。MobX鼓励使用可观察的对象和反应机制来自动更新UI。
  5. 事件总线(Event Bus)或发布-订阅模式: 你可以实现一个简单的事件总线或使用现有的库(如mitt、tiny-emitter等),允许组件订阅事件并在事件发生时接收通知。这种方法在需要解耦组件时特别有用。
  6. 全局状态容器(如window对象) : 虽然不推荐作为常规做法,但有时你可以将状态附加到全局对象(如window)上以实现跨级通信。这种方法应该谨慎使用,因为它可能导致难以追踪的状态更新和潜在的命名冲突。
  7. 父组件 回调: 通过父组件向子组件传递回调函数,子组件可以在需要时调用这些函数来通知父组件状态的变化。这种方法适用于较简单的场景和较浅的组件层次结构。
  8. 使用第三方库: 除了Redux和MobX之外,还有其他一些第三方库可以帮助实现跨级组件通信,如reactn、unstated、zustand等。这些库提供了不同的抽象和机制来处理状态管理。

Hook

  • 是什么?函数组件(纯函数)提供副作用能力的 React API
  • 怎么做?useXX来实现这个hook
  • 解决了什么问题? 1.耦合度高 2.没有生命周期钩子 3.复用困难 4,class学习成本高
  • 优点:1. 避免回调地狱 2.使组件存在状态 3.便于复用 4.容易上手
  • 缺点:1. 可能会遇见大量的代码重构问题 2.可能会有过多的嵌套
  • 如何解决缺点?1.仔细检查代码,理清依赖关系

用过什么hook

useState/useReducer(n每次变就全变)【useState是值,useReducer是函数+值】

useMemo/useCallback (m/fn变才变)【useMemo用于计算值,useCallback用来记忆函数】

useRef(永远不变)

useEffect =》副作用管理

生动例子解释hook

想象一下你正在建造一座神奇的城堡(React 应用)。在没有 Hook 之前,就好像城堡的建造方式非常传统和局限。

比如,你想要在城堡的不同房间(组件)里都记录时间的流逝。如果没有 Hook,你可能需要通过类组件的复杂方式,在每个需要记录时间的房间都创建一个类,然后在类的内部管理状态。这就像是在每个房间都放置一个独立的时钟,而且设置这些时钟的过程很繁琐,代码也会变得冗长和难以维护。

但是有了 Hook 之后,情况就大不一样了。Hook 就像是一种神奇的魔法,可以让你轻松地在任何房间里使用同一个“魔法时钟”。比如使用 useState Hook 来管理时间状态,你只需要在一个地方定义好如何获取和更新时间,然后在任何需要的房间里都可以轻松地调用这个“魔法时钟”,而不需要重复地在每个房间都设置一个独立的时钟。

再比如,你想在城堡的某些房间里根据特定的条件执行一些特殊的魔法效果(副作用,比如发送网络请求、订阅事件等)。没有 Hook 的时候,你可能需要在类组件的特定生命周期方法中处理这些副作用,这不仅容易让代码变得混乱,而且难以理解和维护。但是有了 useEffect Hook,你可以清晰地在需要的地方直接声明这些副作用,就像在房间里轻松地施展特定的魔法效果,而不需要在复杂的生命周期方法中费力地寻找和管理。

总之,Hook 让 React 的开发变得更加灵活、简洁和可维护,就像给开发者提供了一套强大的魔法工具,让建造神奇城堡的过程变得更加轻松和愉快。

虚拟DOM

是什么

虚拟dom就是虚拟节点,React用js对象来模拟dom节点,然后将其渲染成真实的节点

怎么做?

第一步是模拟,用jsx语法写出来的div其实是一个虚拟节点

<div id=”x">

<span class=”red">hi</span>

</div>

//这段代码实际上会得到这样一个对象:

{

tag: 'div',

props: {

id: 'x'

},

  


children: [

{

tag: 'span',

props: {

className: 'red'},

children: [

'hi'

]}]}

第二步是将虚拟节点渲染为真实节点

1.如果代码是字符串或者数字,那就创建一个文本的节点

if(typeof vdom == ‘string’ || typeof Odom = ’number’)return document.createTextNode(vdom)

  1. 创建一个真实的dom

const element = document.createElement(tag)

  1. 设置属性 

setProps(element, props)

4.遍历子节点,并获取到真实的dom插入到当前节点

children

.map(render)

.forEach(element.appendChild.bind(element))

  1. 在虚拟dom中缓存真实的dom节点

vdom.dom = element

  1. 返回dom节点

return element 如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过

diff 算法得到一个 patch 再更新到真实节点上。

解决了什么问题

  1. DOM 操作性能问题。通过虚拟 DOM 和 diff 算法减少不必要的 DOM 操作,保证

性能不太差

  1. DOM 操作不方便问题。以前各种 DOM API 要记,现在只有 setState

优点

  1. 为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲染为

其他东西。

  1. 让 DOM 操作的整体性能更好,能(通过 diff)减少不必要的 DOM 操作。

缺点

  1. 性能要求极高的地方,还是得用真实 DOM 操作(目前没遇到这种需求)
  2. React 为虚拟 DOM 创造了合成事件,跟原生 DOM 事件不太一样,工作中要额外

注意

a. 所有 React 事件都绑定到根元素,自动实现事件委托

b. 如果混用合成事件和原生 DOM 事件,有可能会出 bug

React 或 Vue 的 DOM diff 算法是怎样的?

是什么

DOM diff 就是对比两颗虚拟dom树的算法。当组件变化时,会render出一个新的虚拟dom,通过diff算法对比两颗虚拟树,会得到一个patch,然后react用patch来更新真实 DOM。

怎么做

首先,对比两棵树的根节点

  1. 如果根节点的类型改变了,比如 div 变成了 p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实 DOM 树,创建新的真实 DOM 树。

  2. 如果根节点的类型没变,就看看属性变了没有

    a. 如果没变,就保留对应的真实节点

    b. 如果变了,就只更新该节点的属性,不重新创建节点。

    c. 更新 style 时,如果多个 css 属性只有一个改变了,那么 React 只更新改

变的。

然后,同时遍历两棵树的子节点,每个节点的对比过程同上,不过存在如下两种情况。

  1. React 依次对比 A-A、B-B、空-C,发现 C 是新增的,最终会创建真实 C 节点插入页面。

  2. React 对比 B-A,会删除 B 文本新建 A 文本;对比 C-B,会删除 C 文本,新建 B 文本;(注意,并不是边对比边删除新建,而是把操作汇总到 patch 里再进行 DOM 操作。)对比空-C,会新建 C 文本。

  3. 其实只需要创建 A 文本,保留 B 和 C 即可,为什么 React 做不到呢?因为 React 需要你加 key 才能做到:React 先对比 key 发现 key 只新增了一个,于是保留 b 和 c,新建 a。

React 有哪些生命周期钩子函数?数据请求放在哪个钩子里?

截屏2024-03-04 23.24.56.png

有两个先直接mark出来,一个是卸载时候的unmounted,一个是开始加载时候的constructor。【constructor是类的构造函数,所以其实hook组件/函数组件是没有生命周期的】

之后看挂载的时候,已知黄色之后的dom就是真实的dom了。所以didmount时已经出现在真实的dom中了。在真实的dom之前, 有两个钩子,getStatus和render。所以挂载时一共有四个钩子。

其次看更新时,更新时分几种情况。其中前两个几乎是一样的,几乎包含了最后一种情况。通用的两个钩子:getStatus和render是已知的。更新之后的didUpdate钩子相当于一个事实(不然就没法完成更新这个动作了)。此时已经有三个钩子了,额外的两个钩子。一个是在render之前,相当于shouldupdate进行一下确认,一个是getSnaps在更新之前。

总结一下:

  1. 挂载时调用 constructor,更新时不调用

  2. 更新时调用 shouldComponentUpdate 和 getSnapshotBeforeUpdate,挂载时不调用

  3. should... 在 render 前调用,getSnapshot... 在 render 后调用

为什么ajax请求只能放在 componentDidMount里?

  1. construtor不能放ajax请求是因为,它在ssr的时候被调用。【相对于传统的CSR,SSR的关键区别在,页面的首次渲染不是在浏览器中进行,而是在服务器上。】而服务器端的数据不会被拿到。
  2. 更新时不会执行,是因为每一次更新所有的数据都会被调用,有可能会触发无限调用。
  3. unmounted调用是没有意义的。

所以只能在didmounted时调用。

React 如何实现组件间通信?你如何理解 Redux?

React 如何实现组件间通信?

  1. 父子组件通信:props + 函数

  2. 爷孙组件通信:两层父子通信或者使用 Context.Provider(提供) 和 Context.Consumer(获取)

  3. 任意组件通信:其实就变成了状态管理了

    a. Redux

    b. Mobx

    c. Recoil

你如何理解 Redux?

Redux 是一个状态管理库/状态容器。

Redux 的核心概念:

a. State:用来放状态

b. Action:表示每一步对数据的改变 = 1.type + 2.payload 荷载(数据)

c. Reducer:接收到一个旧的,传一个新的

d. Dispatch: 派发,一般后面接一个action

e. Middleware:中间件

Redux从来不是单独使用的,它要和一个东西配合,比如:ReactRedux。

ReactRedux 的核心概念:

a. connect()(Component):接受两次参数,第二次是component,目的是要把component和store关联起来。

//可以补充一下手写redux的文章