✊不积跬步,无以至千里;不积小流,无以成江海
React 16/17/18
- React16 - 核心更改点:
- Fiber架构:虽然不是表面上的新特性, React 16 内部重构为 Fiber 架构,提供更好的任务调度和异步渲染能力,提高了性能和灵活性。
- Context API :官方规范化了上下文API,使状态管理和跨层级组件通信更为方便。
- React17 - 核心更改点:
- 过渡版本,无重大更新
- 主要更新在事件委托上
- React18 - 核心更改点:
- 并发渲染( Concurrent Mode) :正式推出了并发渲染模式(但仍处于试验阶段),允许React在多个渲染之间切换,根据浏览器空闲时间进行渲染,提升了应用的响应性和交互性。
跨级组件的通信方式?
- Context API: React的Context API提供了一种在组件树中共享数据的方法,而无需手动通过每个级别的props传递。你可以创建一个Context对象,并使用<MyContext.Provider>在组件树中的某个位置提供数据。然后,任何子组件(无论多深)都可以使用<MyContext.Consumer>或useContext Hook来访问这些数据。
- Redux: Redux是一个流行的状态管理库,它允许你在应用程序的任何地方管理和访问状态。通过使用Redux,你可以创建一个全局可访问的store来存储应用程序的状态,并使用connect函数或useSelector和useDispatch Hooks将组件与store连接起来。
- props React ****Hooks (如useState和useReducer)与自定义Hooks: 对于较简单的跨级通信,你可以使用useState和useReducer Hooks在父组件中管理状态,并通过自定义Hooks或props将状态和方法传递给子组件。自定义Hooks允许你封装和重用状态逻辑。
- MobX: MobX是另一个状态管理库,它提供了一种更响应式的方法来管理和更新应用程序的状态。MobX鼓励使用可观察的对象和反应机制来自动更新UI。
- 事件总线(Event Bus)或发布-订阅模式: 你可以实现一个简单的事件总线或使用现有的库(如mitt、tiny-emitter等),允许组件订阅事件并在事件发生时接收通知。这种方法在需要解耦组件时特别有用。
- 全局状态容器(如window对象) : 虽然不推荐作为常规做法,但有时你可以将状态附加到全局对象(如window)上以实现跨级通信。这种方法应该谨慎使用,因为它可能导致难以追踪的状态更新和潜在的命名冲突。
- 父组件 回调: 通过父组件向子组件传递回调函数,子组件可以在需要时调用这些函数来通知父组件状态的变化。这种方法适用于较简单的场景和较浅的组件层次结构。
- 使用第三方库: 除了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)
- 创建一个真实的dom
const element = document.createElement(tag)
- 设置属性
setProps(element, props)
4.遍历子节点,并获取到真实的dom插入到当前节点
children
.map(render)
.forEach(element.appendChild.bind(element))
- 在虚拟dom中缓存真实的dom节点
vdom.dom = element
- 返回dom节点
return element 如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过
diff 算法得到一个 patch 再更新到真实节点上。
解决了什么问题
- DOM 操作性能问题。通过虚拟 DOM 和 diff 算法减少不必要的 DOM 操作,保证
性能不太差
-
DOM 操作不方便问题。以前各种 DOM API 要记,现在只有 setState
优点
- 为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲染为
其他东西。
- 让 DOM 操作的整体性能更好,能(通过 diff)减少不必要的 DOM 操作。
缺点
- 性能要求极高的地方,还是得用真实 DOM 操作(目前没遇到这种需求)
- React 为虚拟 DOM 创造了合成事件,跟原生 DOM 事件不太一样,工作中要额外
注意
a. 所有 React 事件都绑定到根元素,自动实现事件委托
b. 如果混用合成事件和原生 DOM 事件,有可能会出 bug
React 或 Vue 的 DOM diff 算法是怎样的?
是什么
DOM diff 就是对比两颗虚拟dom树的算法。当组件变化时,会render出一个新的虚拟dom,通过diff算法对比两颗虚拟树,会得到一个patch,然后react用patch来更新真实 DOM。
怎么做
首先,对比两棵树的根节点
-
如果根节点的类型改变了,比如 div 变成了 p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实 DOM 树,创建新的真实 DOM 树。
-
如果根节点的类型没变,就看看属性变了没有
a. 如果没变,就保留对应的真实节点
b. 如果变了,就只更新该节点的属性,不重新创建节点。
c. 更新 style 时,如果多个 css 属性只有一个改变了,那么 React 只更新改
变的。
然后,同时遍历两棵树的子节点,每个节点的对比过程同上,不过存在如下两种情况。
-
React 依次对比 A-A、B-B、空-C,发现 C 是新增的,最终会创建真实 C 节点插入页面。
-
React 对比 B-A,会删除 B 文本新建 A 文本;对比 C-B,会删除 C 文本,新建 B 文本;(注意,并不是边对比边删除新建,而是把操作汇总到 patch 里再进行 DOM 操作。)对比空-C,会新建 C 文本。
-
其实只需要创建 A 文本,保留 B 和 C 即可,为什么 React 做不到呢?因为 React 需要你加 key 才能做到:React 先对比 key 发现 key 只新增了一个,于是保留 b 和 c,新建 a。
React 有哪些生命周期钩子函数?数据请求放在哪个钩子里?

有两个先直接mark出来,一个是卸载时候的unmounted,一个是开始加载时候的constructor。【constructor是类的构造函数,所以其实hook组件/函数组件是没有生命周期的】
之后看挂载的时候,已知黄色之后的dom就是真实的dom了。所以didmount时已经出现在真实的dom中了。在真实的dom之前, 有两个钩子,getStatus和render。所以挂载时一共有四个钩子。
其次看更新时,更新时分几种情况。其中前两个几乎是一样的,几乎包含了最后一种情况。通用的两个钩子:getStatus和render是已知的。更新之后的didUpdate钩子相当于一个事实(不然就没法完成更新这个动作了)。此时已经有三个钩子了,额外的两个钩子。一个是在render之前,相当于shouldupdate进行一下确认,一个是getSnaps在更新之前。
总结一下:
-
挂载时调用 constructor,更新时不调用
-
更新时调用 shouldComponentUpdate 和 getSnapshotBeforeUpdate,挂载时不调用
-
should... 在 render 前调用,getSnapshot... 在 render 后调用
为什么ajax请求只能放在 componentDidMount里?
- construtor不能放ajax请求是因为,它在ssr的时候被调用。【相对于传统的CSR,SSR的关键区别在,页面的首次渲染不是在浏览器中进行,而是在服务器上。】而服务器端的数据不会被拿到。
- 更新时不会执行,是因为每一次更新所有的数据都会被调用,有可能会触发无限调用。
- unmounted调用是没有意义的。
所以只能在didmounted时调用。
React 如何实现组件间通信?你如何理解 Redux?
React 如何实现组件间通信?
-
父子组件通信:props + 函数
-
爷孙组件通信:两层父子通信或者使用 Context.Provider(提供) 和 Context.Consumer(获取)
-
任意组件通信:其实就变成了状态管理了
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的文章