127、React中的虚拟DOM
在React中,diff算法需要与虚拟DOM配合才能发挥出真正的威力。React会使用diff算法计算出虚拟DOM中真正发生变化的部分,并且只会针对该部分进行dom操作,从而避免了对页面进行大面积的更新渲染,减小性能的开销。
React diff算法
在传统的diff算法中复杂度会达到O(n^3)。React中定义了三种策略,在对比时,根据策略只需遍历一次树就可以完成对比,将复杂度降到了O(n):
-
tree diff:在两个树对比时,只会比较同一层级的节点,会忽略掉跨层级的操作。
-
component diff:在对比两个组件时,首先会判断它们两个的类型是否相同
- 如果不是,则将该组件判断为
dirty component,从而替换整个组件下的所有子节点
- 如果不是,则将该组件判断为
-
element diff:对于同一层级的一组节点,会使用具有
唯一性的key来区分是否需要创建,删除,或者是移动。
Element Diff
当节点处于同一层级时,React diff 提供了三种节点操作,分别为:
INSERT_MARKUP(插入)- 新的
component类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作
- 新的
MOVE_EXISTING(移动)- 在老集合有新
component类型,且element是可更新的类型,这种情况下prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
- 在老集合有新
REMOVE_NODE(删除)- 老
component类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作, - 或者老
component不在新集合里的,也需要执行删除操作
- 老
存在如下结构:
新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作
- 首先对新集合的节点进行循环遍历,
for (name in nextChildren) - 通过唯一 key 可以判断新老集合中是否存在相同的节点,
if (prevChild === nextChild) - 如果存在相同节点,则进行移动操作
- 但在移动前需要将当前节点在老集合中的位置与
lastIndex进行比较,if (child._mountIndex < lastIndex),则进行节点移动操作,否则不执行该操作。lastIndex一直在更新,表示访问过的节点在老集合中最右的位置(即最大的位置)- 如果新集合中当前访问的节点比
lastIndex大,说明当前访问节点在老集合中就比上一个节点位置靠后,则该节点不会影响其他节点的位置,因此不用添加到差异队列中,即不执行移动操作 - 只有当访问的节点比
lastIndex小时,才需要进行移动操作。
当完成新集合中所有节点 diff 时,最后还需要对老集合进行循环遍历,判断是否存在新集合中没有但老集合中仍存在的节点,发现存在这样的节点 x,因此删除节点 x,到此 diff 全部完成。
128、React栈调和,diff算法说一下?和vue的diff算法有什么不同?key有什么用?
1.React 中的调和(Reconciliation)和 Diff 算法:
- 调和(Reconciliation): 在 React 中,调和指的是通过算法确定虚拟 DOM 树与实际 DOM 树之间的差异,并只更新必要的部分,而不是每次都重新渲染整个页面。这个过程称为调和。
- Diff 算法: Diff 算法是调和过程中的一部分,用于比较前后两棵虚拟 DOM 树的差异。React 使用虚拟 DOM 树的 Diff 算法来高效地更新实际 DOM,减少不必要的 DOM 操作,提升性能。
2. Vue 的响应式更新机制的 diff 算法:
- 在 Vue 中,采用的是基于依赖追踪的响应式更新机制。当数据发生变化时,Vue 会重新计算模板中的表达式,并更新相关的 DOM 元素。Vue 2.x 中使用的是双向绑定和虚拟 DOM 结合的方式来实现更新,但没有像 React 那样的明确的 Diff 算法。
3. React 和 Vue 的 Diff 算法不同之处:
- React 使用的是虚拟 DOM 的 Diff 算法,它会逐层比较虚拟 DOM 树的节点,找出需要更新的部分,并进行最小化的 DOM 操作。
- Vue 的响应式更新机制没有像 React 那样明确的 Diff 算法,而是通过观察数据的变化并更新相关的 DOM 元素。
4. Key 的作用:
- 在 React 和 Vue 中,key 是用来帮助框架识别列表中每个子元素的唯一标识符。在进行列表渲染时,使用 key 可以帮助框架更高效地更新视图。
- 在进行列表中元素的增删操作时,key 的存在可以帮助框架更准确地识别新旧节点的对应关系,避免无谓的 DOM 操作,优化性能。
总的来说,React 使用虚拟 DOM 和 Diff 算法来进行高效的 DOM 更新,而 Vue 则采用响应式更新机制,没有明确的 Diff 算法。在实际开发中,合理使用 key 可以帮助优化列表渲染的性能,避免不必要的 DOM 操作。
129、React为什么要用虚拟dom呢
React是一个流行的JavaScript库,用于构建用户界面,特别是单页应用。React引入了虚拟DOM(Virtual DOM)的概念,这是其核心特性之一。以下是React为什么要使用虚拟DOM的详细解释:
性能优化
-
减少DOM操作:
- 直接操作DOM是昂贵的,因为每次更改都会引起浏览器的重绘(repaint)或重排(reflow),这会降低性能。
- 虚拟DOM作为一个轻量级的JavaScript对象,可以高效地在内存中更新,然后再批量更新到实际DOM。
-
差异比较(Diffing):
- 当组件的状态改变时,React会创建一个新的虚拟DOM树,并与旧的虚拟DOM树进行比较,找出差异。
- React使用高效的算法(如深度优先搜索)来确定最小的更新范围,只更新变化的部分。
-
异步更新:
- React可以将多个状态更新合并为单个DOM更新,减少实际的DOM操作次数。
开发效率
-
声明式编程:
- 虚拟DOM允许开发者以声明式的方式编写UI,关注于“想要的结果”,而不是“如何实现”。
- 这简化了代码逻辑,使得开发和维护更加容易。
-
组件化:
- 虚拟DOM支持组件化开发,每个组件都有自己的虚拟DOM树。
- 这使得组件可以在不同的应用中重用,提高了开发效率。
跨平台
-
跨浏览器兼容性:
- 虚拟DOM作为一个中间层,使得React应用可以在不同的浏览器上保持一致的行为。
-
跨平台框架:
- 虚拟DOM使得React可以扩展到其他平台,如React Native用于移动应用开发。
最佳实践:
- 利用虚拟DOM的性能优势,避免不必要的渲染和DOM操作。
- 合理划分组件,使得组件独立且可复用。
- 利用React的Hooks和生命周期方法来管理组件的状态和生命周期。
通过使用虚拟DOM,React提供了一种高效、可扩展且易于开发的方式来构建复杂的用户界面。这些优势使得React成为现代前端开发中最受欢迎的库之一。
130、React中的Fiber架构
React Fiber 是 React v16 中引入的一种新的协调算法,它被设计用来解决 React 在处理大型应用时可能出现的性能问题。本文将详细介绍 React Fiber 的背景、原理和优势。
背景
在 React v15 以及之前版本中,React 应用是通过递归遍历组件树实现的。当组件状态发生变化时,React 会重新计算并渲染整个组件树,然后将变化的部分更新到 DOM 上。这种算法虽然简单易懂,但是也存在一些缺点:
- 当组件树非常庞大时,递归遍历整个组件树的开销很大,导致应用性能下降。
- 如果某个组件在更新过程中发生了阻塞,那么整个组件树的更新也会被阻塞,用户体验不佳。
为了解决这些问题,React 团队在 v16 中引入了一种新的协调算法——React Fiber。
设计原理
-
React Fiber 的核心思想是将组件树的遍历变成了可中断的异步任务。具体来说,React Fiber 会将整个组件树拆分成多个小的任务单元(Fiber),并按照优先级顺序依次执行这些任务单元。每当执行完一个任务单元时,React Fiber 就会检查当前是否有更高优先级的任务需要执行。如果有,则立即暂停当前任务,并开始执行更高优先级的任务,直到完成后再回来继续执行原来的任务。这样一来,React Fiber 可以让应用更加灵活地响应用户交互和其他事件。
-
在 React Fiber 中,每个任务单元都对应一个 Fiber 对象,该对象包含了当前组件的状态和相关信息。Fiber 对象还存储了组件所在的位置、子组件信息、副作用等信息。当某个任务单元执行时,React Fiber 会利用 Fiber 对象的信息进行 DOM 更新、事件处理等操作。
-
React Fiber 还提供了一系列 API,用于控制任务单元的执行顺序、暂停和恢复任务等操作。这些 API 包括
requestIdleCallback、cancelIdleCallback、setTimeout、setImmediate等。 -
为了支持可中断的异步任务,React Fiber 引入了一种新的数据结构——双缓存技术。具体来说,React Fiber 维护了两个 fiber 树:current fiber 树和 work in progress fiber 树。current fiber 树是当前渲染结果对应的 fiber 树,work in progress fiber 树则是正在进行的更新操作对应的 fiber 树。当更新操作完成后,React Fiber 会将 work in progress fiber 树替换为 current fiber 树,从而实现视图更新。
-
最后,React Fiber 的 diff 算法也有所改进。传统的 diff 算法是基于递归遍历的,而 React Fiber 则采用了迭代遍历算法。具体来说,React Fiber 会将更新操作转换成一系列指令(effect),然后将这些指令保存到 effect list 中。最后,React Fiber 会按照顺序执行 effect list 中的指令,从而完成页面渲染。
优势
相比于传统的递归遍历算法,React Fiber 具有以下优势:
- 更快的渲染速度:由于 React Fiber 可以将任务切分成小的单位执行,因此可以更加高效地渲染组件。
- 更好的用户体验:由于 React Fiber 可以随时中断和恢复任务,因此可以更加灵活地响应用户交互和其他事件。
- 更加可扩展:React Fiber 提供了一系列 API,使得其可以更加方便地集成进其他系统中。
总结来说,React Fiber 是一种全新的协调算法,它通过分离任务单元、采用双缓存技术、支持可中断的异步任务等方式,解决了 React 在处理大型应用时可能出现的性能问题。
131、React Fiber在哪个过程是可以中断的
React Fiber 是 React 中用于实现协调、调度和渲染的新的核心算法。它引入了可中断的协调过程,使得 React 的更新可以在执行过程中被中断和恢复,从而提高了对用户交互的响应性。
在 React Fiber 中,协调阶段是可以被中断的过程。通过可中断的协调过程,React 实现了更好的任务调度和响应性能,提高了用户界面的渲染性能和用户体验。
在 React Fiber 中,更新过程被划分为多个单元,称为 Fiber。Fiber 可以理解为一个轻量级的任务单元,可以在需要时暂停、继续和中断。Fiber 通过构建一个任务优先级队列,React 可以在每个浏览器帧(Browser frame)之间动态地分配和调度这些任务。
具体来说,React Fiber 中的可中断过程是指协调阶段(Reconciliation phase)。协调阶段是 React 在更新过程中进行组件调度和更新的阶段,通过对比前后两次更新的虚拟 DOM 树,找出差异并计算出最小的更新操作。在协调阶段,React Fiber 使用一种称为增量渲染(Incremental Rendering)的方式,将工作拆分为多个单元,并按优先级顺序分配执行。
React Fiber 支持优先级调度,可以根据任务的优先级来决定是否中断当前任务并切换到更高优先级的任务。当浏览器需要渲染新帧时,React Fiber 可以根据当前的任务优先级来决定是否中断正在执行的任务,并将控制权交还给浏览器,以便及时响应用户的交互。
132、React中的组件通信有哪些方法
-
父组件向子组件传递属性(Props):可以使用Props将数据从父组件传递给子组件,子组件可以按照需要使用这些数据进行渲染和操作。
-
子组件向父组件传递事件:可以在子组件中定义事件处理函数,并使用Props将这些函数传递给父组件。当子组件触发事件时,可以通过Props调用父组件的事件处理函数。
-
上下文(Context):使用上下文可以在组件树中共享数据,而不必通过Props逐级传递。上下文提供了一种在祖先组件和后代组件之间传递信息的方法,但应该慎重使用,因为过度使用上下文会导致组件之间的耦合度增加。
-
发布/订阅模式:可以使用事件发布/订阅机制来实现组件之间的通信。一个组件可以向事件总线(或事件中心)发布消息,其他组件可以订阅这些消息,并在需要时进行响应。
-
共享状态(State):可以将状态放在共享的Store中,例如Redux或MobX,然后在各个组件中获取和修改状态。共享状态可以实现高效的组件通信,但需要保证状态的一致性和正确性。
总之,React中有多种组件通信方式可供选择。开发者应该根据具体情况选择最适合的方式,以确保组件之间的松耦合和高效通信。
133、React中的高阶组件HOC
React中的高阶组件(Higher-Order Component,HOC)是一个函数,接受一个组件作为输入,并返回一个新的增强版组件。HOC本身不是React组件,它是一个工厂函数,负责生成增强后的组件。
在React应用程序中,HOC可以用于封装通用逻辑和功能,并使它们可重用,同时保持代码的DRY原则。例如,HOC可以用于:
- 提取重复逻辑,如数据获取、路由保护、日志记录等,并将它们添加到多个组件中;
- 提供一些UI功能,如拖放、重新排序等,并使组件更加可复用;
- 劫持组件渲染过程,例如使用Redux或其他状态管理库注入数据。
以下是一个简单的HOC示例:
function withLogger(WrappedComponent) {
return function WithLogger(props) {
console.log('Rendering:', WrappedComponent.name, props);
return <WrappedComponent {...props} />;
};
}
function MyComponent(props) {
return <div>Hello, {props.name}!</div>;
}
const EnhancedComponent = withLogger(MyComponent);
在这个示例中,withLogger()是一个HOC,它接收一个被包装的组件(MyComponent),并返回一个增强版本的组件(WithLogger)。增强后的组件会在每次渲染时输出一条日志,然后渲染被包装的组件。
HOC可以链式组合,这在很多第三方库中很常见。例如,Redux使用了许多HOC来将状态管理器注入到应用程序中的各个组件中。
总之,HOC是一种强大且灵活的技术,用于增强React组件的功能和可重用性。它可以帮助我们提高开发效率,同时使我们的代码更加模块化和可维护。
134、React中常用的高阶组件有哪些
-
withRouter:将路由信息注入到组件中,使它们能够访问到路由对象(如location、history和match等)。 -
connect:将React组件与Redux Store连接起来,并将State和Dispatch作为Props传递给组件。这使得组件能够直接从Store中读取和操作数据。 -
memo:对于纯函数组件,使用memo可以缓存组件输出,以提高性能。 -
withStyles:用于添加CSS样式到组件中。 -
redux-thunk:使Action Creator返回一个函数而不是一个Action对象,从而可以执行异步操作并dispatch新的Action。 -
recompose:提供了一组高阶功能,用于增强函数式React组件。例如,compose函数可以将多个HOC组合在一起。 -
react-redux:提供了一组基于Redux Store的React组件,并简化了React与Redux之间的集成。
总之,React中的HOC提供了许多灵活且有用的功能,可以帮助我们更好地组织和重用代码。开发者可以使用这些HOC来封装通用的逻辑和功能,并使组件更加可复用。
135、React中的受控组件与非受控组件
在 React 中,受控组件和非受控组件是两种处理表单输入的方式。
- 受控组件:受控组件是受 React 组件状态(state)控制的表单元素。当用户在表单元素上输入数据时,React 会通过设置组件状态更新输入的值,并将新值传递给组件处理函数。
例如,以下代码使用 state 控制 <input> 元素的值:
import React, { useState } from 'react';
function ControlledForm() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Submit value:', value);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={value} onChange={handleChange} />
</label>
<button type="submit">Submit</button>
</form>
);
}
- 非受控组件:非受控组件则使用原生 DOM 元素的引用从而获得表单元素的值。这种方式更少地使用 React 状态(state),但需要通过管理 DOM 元素或引用进行操作。
例如,以下代码使用 ref 获取 <input> 元素的值:
import React, { useRef } from 'react';
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
console.log('Submit value:', inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={inputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
}
总之,受控组件和非受控组件各有优劣。受控组件使得表单输入更易于管理和验证,并且可以编写更清晰、可读性更强的代码。而非受控组件则具有更高的性能和灵活性,并且通常用于处理大量的表单输入。
136、React中为什么要劫持事件
在 React 中,事件劫持(SyntheticEvent)是为了解决浏览器事件兼容性的问题。 在不同浏览器下,同一个事件有着不同的实现方式和属性定义。React 对此做的尝试是为所有浏览器提供相同的 API,无论是什么浏览器,都能通过 event.target 访问到相应的 DOM 元素。
具体来说,React 对浏览器原生事件对象进行了封装,将所有浏览器的事件处理程序标准化为一个通用的 SyntheticEvent 对象。这个对象和浏览器原生的事件对象类似,但性能更好且跨浏览器兼容性更好。由于 SyntheticEvent 对象只是在合成事件处理程序中创建一次,因此它可以被重用和回收,从而提高了性能。
此外,React 还通过 stopPropagation() 和 preventDefault() 方法,允许事件处理程序在 React 应用程序的组件层次结构中传递并拦截事件,而无需在各个 DOM 元素上设置单独的事件处理程序。这使得事件处理程序的管理和维护更加方便和灵活。
总之,通过事件劫持,React 提供了一种更高效、灵活和一致的事件处理方式,使得开发人员能够更轻松地处理复杂的交互逻辑和跨浏览器的兼容性问题。
137、React中错误边界的概念
当部分
js出现错误后,不应该导致整个程序崩溃,为了解决这个问题,react 16引入了一个错误边界的概念。
错误边界是一种 class 组件,这种组件可以捕获发生在其子组件树任何位置的 js 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生错误的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。
如果一个 class 组件中定义了 static getDerivedStateFromError()或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch()打印错误信息。
只有 class 组件才可以成为错误边界组件,大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。不能在hooks组件中使用。
138、React中的Suspense组件
React 中的 Suspense 是一个组件,它允许在数据加载完成之前展示一些占位内容,从而更好地优化应用程序的性能和用户体验。当我们使用 React 进行代码拆分时,可以使用 Suspense 包装需要延迟加载的组件,并设置 fallback 属性指定在组件加载期间需要显示的占位符。
例如,以下是使用 Suspense 在代码分割组件中实现延迟加载的示例:
import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./components/LazyComponent'));
function App() {
return (
<div>
<h1>Hello, React Suspense!</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
在这个示例中,React.lazy 方法可用于实现延迟加载,并且在 fallback 中设置了一个占位符,在组件加载期间将显示该占位符。可以结合 ErrorBoundary 使用,以捕获组件加载错误并提供反馈。
139、什么是 JSX
SX是一种JavaScript的语法扩展,它允许在JavaScript代码中编写类似于HTML的标记语言。它主要是为了方便React组件的编写而产生的一种语法。
使用JSX可以将模板和逻辑代码放在同一个文件中,从而使得组件的开发更加方便、简洁,同时也大大提高了代码的可读性和可维护性。JSX允许在JavaScript代码中直接嵌入标签和表达式,其中标签类似于HTML标签,而表达式则是JavaScript表达式。例如:
const element = <h1>Hello, {name}!</h1>;
上面的element变量是一个React元素(React Element),它可以渲染到页面中。在这个例子中,我们使用了JSX中的标签语法<h1>和</h1>来创建一个h1标签元素,同时使用了花括号{}来插入一个变量name的值。
虽然不是必须的,但是在React项目中通常使用Babel等工具来将JSX代码转换成普通的JavaScript代码以便浏览器可以正确地解析执行。这样,在项目运行之前,就可以通过构建工具(比如webpack)进行编译,从而最终生成可供浏览器执行的JavaScript文件。
总的来说,JSX是一种语法扩展,与React结合使用可以提高开发效率和代码的可读性和可维护性。
扩展
JSX 的优点:
- 易读性高: JSX 可以使代码更具可读性和易维护性,因为它类似于编写 HTML 标记,使得组件结构清晰明了。
- 强大的表达能力: JSX 具有 JavaScript 的全部功能,可以在其中使用表达式、变量、条件语句等,使得编写动态 UI 更加灵活。
- 静态类型检查: 在使用 JSX 时,可以借助静态类型检查工具(如 TypeScript 或 Flow)来检测代码中的类型错误,提高代码质量和稳定性。
- 组件化开发: JSX 可以帮助开发者更好地实现组件化开发,将 UI 视图和逻辑封装在组件内部,提高代码复用性。
2. JSX 的工作原理:
- 当使用 JSX 编写 React 组件时,babel 等工具会将 JSX 代码转译为普通的 JavaScript 代码,这个过程称为 JSX 编译。
- 在编译过程中,JSX 中的标签会被转换为对应的 React.createElement() 函数调用,最终生成虚拟 DOM 对象。
- 虚拟 DOM 对象会被 React 的调和算法进行比对,找出需要更新的部分,并最终渲染到页面上。
3. JSX 与模板引擎的区别:
- JSX 不同于传统的模板引擎,它更像是在 JavaScript 代码中嵌入 XML 结构,而不是将 HTML 和 JavaScript 分离。
- JSX 允许在代码中直接使用 JavaScript 表达式,使得动态数据绑定和条件渲染更加方便。
140、JSX有哪些使用规则
- 标签必须配对,即开标签和闭标签必须一一对应。
- 标签可以自闭合,比如
<br/>。 - 所有标签必须闭合,即不能出现类似
<img>这样只有一个开标签而没有闭标签的标签。如果一个标签没有实际内容,则可以使用自闭合方式(即使用/>来代替</tag>)。 - 标签名不区分大小写,但是建议遵循React组件名称的约定(即采用大写字母开头的驼峰式命名)。
- 在标签中可以添加属性,例如
<div class="my-class" onClick={handleClick}>,多个属性之间使用空格分隔。 - 属性的值可以是字符串,也可以是JavaScript表达式,只需要将表达式用花括号
{}包裹起来即可。 - 任何JavaScript表达式都可以用在JSX中。
- JSX注释与HTML注释类似,使用
{/* ... */}格式。 - 在JSX中嵌套元素时,可以使用花括号
{}包裹子元素。
总的来说,JSX允许在JavaScript中编写类似于HTML标签的语法,以便更方便地编写React组件。
141、React的合成事件和事件委托机制,混用合成事件和原生事件谁会先执行
React 使用了合成事件(React 使用了合成事件(SyntheticEvent)来处理浏览器中的原生事件。合成事件是对原生事件的封装,它提供了跨浏览器的一致性,并且具有更好的性能和可扩展性。
合成事件的特点包括:
- 跨浏览器兼容性:合成事件在不同浏览器之间提供了一致的行为,解决了浏览器兼容性问题。
- 合并事件处理程序:React 通过合并多个事件处理程序,减少了事件处理函数的创建和销毁的次数,提高了性能。
- 自动绑定 this:在使用类组件时,合成事件会自动将事件处理程序绑定到组件实例上,无需手动绑定。
- 事件委托机制:合成事件利用事件冒泡机制实现事件委托,通过在顶层监听事件,将事件交由合适的组件进行处理,减少了事件处理函数的数量。
事件委托机制是指将事件处理委托给其父组件或更高层级组件来处理。在 React 中,事件从子组件传递到父组件时,React 会利用事件冒泡机制(由底层 DOM 实现)将事件委托给父组件处理。这样可以减少事件处理函数的数量,简化代码结构,并提高性能。
当在 React 中混用合成事件和原生事件时,合成事件会先执行,然后再执行原生事件。这是因为 React 通过监听浏览器的原生事件来实现合成事件,所以合成事件会先于原生事件执行。这种顺序保证了 React 的事件系统可以正常工作,并且可以在业务逻辑中使用合成事件和原生事件的组合。
需要注意的是,使用合成事件时应遵循 React 的事件系统,不应直接操作 DOM 或使用原生事件处理函数。而对于一些特殊的需求,如与第三方库的交互或访问底层 DOM API,可以使用 ref 来获取 DOM 节点,并在 componentDidMount 和 componentWillUnmount 生命周期方法中手动绑定和解绑原生事件处理函数)来处理浏览器中的原生事件。合成事件是对原生事件的封装,它提供了跨浏览器的一致性,并且具有更好的性能和可扩展性。
142、【阿里巴巴】React框架的特点是什么,跟传统的框架对比
React框架的特点主要包括以下几个方面:
-
声明式编程:React采用声明式编程模式,将UI抽象为组件,使得开发者可以更加关注于UI的实现而非底层的实现细节。
-
虚拟DOM:React采用虚拟DOM技术,通过对比前后两个虚拟DOM的差异,最小化地更新DOM,从而提高了性能。
-
组件化:React将UI抽象为组件,使得组件内部的状态和行为都可以被封装,提高了代码的可维护性和可复用性。
-
单向数据流:React采用单向数据流的模式,自上而下地传递数据,使得数据流清晰可见,方便调试和维护。
-
函数式编程:React鼓励使用函数式编程的思想,使得编写代码更加简洁、易于测试和调试。
相比于传统的框架,React框架具有以下特点:
-
更加灵活:React采用组件化的开发模式,使得开发者可以更加灵活地组织UI组件,提高了代码的可复用性和可维护性。
-
更加高效:React采用虚拟DOM技术,可以最小化地更新DOM,从而提高了性能和渲染效率。
-
更加简单:React采用声明式编程模式,隐藏了底层实现细节,使得开发者可以更加专注于UI的实现和业务逻辑的实现。
-
更加容易学习:React的API简单明了,学习起来相对容易,而且它的生态系统非常丰富,提供了大量的工具和库,使得开发变得更加便捷。
总之,React框架的特点包括声明式编程、虚拟DOM、组件化、单向数据流和函数式编程等,相比于传统的框架,它更加灵活、高效、简单和容易学习。
143、【阿里巴巴】React框架渲染层面有什么特别,跟document 的方式有什么区别吗
React框架的渲染层面有以下特点:
-
虚拟DOM:React采用虚拟DOM技术,将UI抽象为一个虚拟的DOM树,通过对比前后两个虚拟DOM的差异,最小化地更新DOM,从而提高了性能。
-
组件化:React将UI抽象为组件,使得组件内部的状态和行为都可以被封装,提高了代码的可维护性和可复用性。
-
单向数据流:React采用单向数据流的模式,自上而下地传递数据,使得数据流清晰可见,方便调试和维护。
相比于直接使用document对象来操作DOM,React采用了虚拟DOM和组件化的开发模式,使得开发者可以更加灵活地组织UI组件,提高了代码的可复用性和可维护性。同时,由于React采用虚拟DOM技术,可以最小化地更新DOM,从而提高了性能和渲染效率。
另外,使用原生的document对象来操作DOM,需要手动更新DOM的状态,而React框架会自动地根据数据的变化来更新DOM的状态,从而减少了手动更新DOM的工作量,提高了开发效率。
总之,React框架的渲染层面通过虚拟DOM和组件化的开发模式提高了代码的可复用性和可维护性,同时采用了自动更新DOM的机制和优化性能的技术,使得开发效率和渲染效率都得到提高。
144、React16之后的生命周期
自 React 16 版本开始,React 引入了新的生命周期 API ,其中包括了 getDerivedStateFromProps、getSnapshotBeforeUpdate 等新方法。
以下是 React 16 及以上版本中的生命周期方法列表:
-
constructor(props):React 组件初始化构造函数,一般用于初始化 state 以及绑定 this。 -
static getDerivedStateFromProps(props, state):该生命周期方法适用于在 props 发生改变时,根据新的 props 计算出新的 state 值。 -
render():渲染组件,该方法是 React 组件必须实现的唯一一个方法。 -
componentDidMount():组件挂载后调用的方法,在该方法中可以进行 DOM 操作或发起 AJAX 请求等操作。 -
shouldComponentUpdate(nextProps, nextState):该方法返回值决定组件是否需要重新渲染。通过比较 nextProps 和 nextState 与当前 this.props 和 this.state 的值,确定是否需要进行重新渲染。 -
getSnapshotBeforeUpdate(prevProps, prevState):该生命周期方法可以在组件更新前获取到最新的 DOM 信息(如滚动位置等),返回值会作为 componentDidUpdate 方法的第三个参数 snapshot 传递给 componentDidUpdate。 -
componentDidUpdate(prevProps, prevState, snapshot):组件更新后调用的方法,在该方法中可以进行 DOM 操作或发起 AJAX 请求等操作。 -
componentWillUnmount():组件即将卸载时调用的方法,在该方法中可以进行一些清理操作,例如移除事件监听器、取消 AJAX 请求等。 -
static getDerivedStateFromError(error):该生命周期方法可以捕获子组件中的错误,并返回一个新的 state 对象以展示错误信息。 -
componentDidCatch(error, info):该生命周期方法与getDerivedStateFromError配合使用,用于在组件内部捕获错误并处理。
总之,React 16 及以上版本中的生命周期 API 更加丰富和灵活,可以更好的控制组件的渲染过程和错误处理。(注意:React 17 版本取消了少数生命周期钩子的 UNSAFE_ 前缀)。
145、React中的setState同步异步问题
在 React 中,setState() 方法的更新可能是异步的,并且在调用 setState() 之后不能立即访问更新后的状态值。通常情况下,React 会将一组 setState() 调用批量处理为单个更新操作,从而提高性能。
当 setState() 被调用时,React 会将新的 state 对象合并到现有的 state 中,并对组件进行重新渲染。但是,该过程不一定会立即执行。在某些情况下,React 可以推迟更新,以优化性能和提高渲染速度,例如:
- 在事件处理程序中调用
setState()。 - 在生命周期方法
componentDidUpdate()和componentWillUpdate()中调用setState()。 - 在异步代码中调用
setState()。
在这些情况下,React 可能会将一组 setState() 调用合并为单个更新操作,从而提高性能。这意味着每次 setState() 调用都不一定会立即更新组件的状态,而是等待一段时间后一次性更新。如果需要在调用 setState() 后立即访问更新后的状态值,则可以将一个回调函数作为 setState() 的第二个参数传递,在状态更新完成后再执行它。
this.setState(
{ name: 'Obaseki Nosa' },
() => console.log(this.state.name)
);
以上代码中,第一个参数是要更新的状态对象,第二个参数是一个回调函数,当状态更新完成时会被调用,并在控制台打印出更新后的状态值。
146、React中的setState是同步的还是异步
在 React 中,setState 方法既可以是同步的,也可以是异步的,取决于调用 setState 的情境。下面我会详细解释 setState 方法是如何工作的:
1. 同步的 setState:
- 当在 React 生命周期方法(如 componentDidMount、componentDidUpdate)中调用 setState 时,setState 操作是同步的,React 会立即更新组件状态,并且触发重新渲染。
- 在同步的情况下,React 会尽快更新组件状态和 DOM,确保用户能够看到最新的 UI 变化。
2. 异步的 setState:
- 当在事件处理函数、定时器、Promise 等异步操作中调用 setState 时,setState 操作是异步的。
- 异步的 setState 会将更新操作放入队列中,等待当前代码执行完毕后再批量处理更新,以提高性能和效率。
- 这意味着连续多次调用 setState 不会立即更新组件状态,而是会被合并成一次更新,只触发一次重新渲染。
3. setState 的批量更新:
- React 会对连续的 setState 调用进行批量处理,减少不必要的 DOM 操作。这种优化方式称为批量更新。
- React 会在事件循环结束前对所有批量更新进行合并处理,然后再进行实际的 DOM 更新,以提升性能。
4. 如何确定 setState 是同步还是异步:
- 如果需要在调用 setState 后立即获取更新后的状态,可以在 setState 的回调函数中进行操作,因为回调函数会在状态更新之后被调用。
- 可以使用 componentDidUpdate 生命周期方法来观察状态更新后的效果,以确定 setState 的行为是同步还是异步的。
总的来说,React 中的 setState 方法既可以是同步的,也可以是异步的,取决于调用 setState 的时机和情境。了解 setState 的工作原理对于正确处理组件状态更新非常重要,在开发过程中需要注意 setState 的同步和异步特性,避免出现意外的结果。
147、React中的PureComponent
React.PureComponent 是 React 提供的一个优化性能的类组件,它在 Component 的基础上实现了 shouldComponentUpdate 方法,用于自动判断是否需要重新渲染。
shouldComponentUpdate 会比较前后两次的 props 和 state 是否有变化,只有当它们发生变化时,PureComponent 才会重新渲染。这样可以减少不必要的渲染操作,提高组件的渲染效率。
以下是使用 PureComponent 的示例:
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
// 组件的渲染逻辑
}
}
export default MyComponent;
在这个示例中,我们继承了 React.PureComponent,并重写了 render 方法,实现了组件的渲染逻辑。通过使用 PureComponent,当 MyComponent 的 props 和 state 没有变化时,就不会重新渲染,从而提高组件的性能。
需要注意的是,在使用 PureComponent 时,必须确保 props 和 state 的值是不可变(immutable)的,因为 shouldComponentUpdate 只会浅层比较 props 和 state 的值,如果是可变的,可能会导致不正确的结果。
另外,对于一些需要进行复杂比较的场景,PureComponent 可能不太适用。在这种情况下,建议手动实现 shouldComponentUpdate 方法,根据自己的业务需求来判断是否需要重新渲染组件。
148、React中手动实现shouldComponentUpdate
在 React 中,手动实现 shouldComponentUpdate 方法可以帮助我们优化组件的性能。通过判断当前 props 和 state 的值是否发生了变化,如果没有变化,就可以避免不必要的重新渲染。
以下是手动实现 shouldComponentUpdate 方法的示例:
import React, { Component } from 'react';
class MyComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 判断当前 props 和 state 是否发生了变化
if (this.props.foo === nextProps.foo && this.state.bar === nextState.bar) {
return false; // 不需要更新组件
}
return true; // 需要更新组件
}
render() {
// 组件的渲染逻辑
}
}
export default MyComponent;
在这个示例中,我们手动实现了 shouldComponentUpdate 方法来判断是否需要更新组件。如果当前的 props 和 state 值与下一个 props 和 state 值相同,就返回 false,表示不需要更新组件;否则返回 true,表示需要更新组件。
需要注意的是,手动实现 shouldComponentUpdate 方法需要谨慎处理,因为它可能会影响组件的正确性。如果判断不当,可能会导致组件出错,或者在某些场景下不会重新渲染,影响用户体验。因此,在使用 shouldComponentUpdate 方法时,需要仔细分析组件的业务逻辑,确保判断正确。
149、React中的shouldComponentUpdate
在React中,shouldComponentUpdate是一个生命周期方法,用于控制组件是否需要重新渲染。通过在shouldComponentUpdate中返回true或false来决定是否更新组件。
默认情况下,React组件的shouldComponentUpdate方法会在每次组件接收到新的属性(props)或状态(state)时被调用。如果没有显式地定义shouldComponentUpdate方法,React将默认返回true,表示组件应该重新渲染。
您可以重写shouldComponentUpdate方法,根据自己的需求判断是否需要重新渲染组件。这个方法接收两个参数:nextProps和nextState,分别表示下一个属性和下一个状态的值。在shouldComponentUpdate方法中,您可以比较当前属性和状态与下一个属性和状态,然后根据逻辑返回true或false。
下面是一个示例,演示如何使用shouldComponentUpdate来优化组件的性能:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有当新的属性 name 不等于当前属性 name 时才重新渲染
if (nextProps.name !== this.props.name) {
return true;
}
// 只有当新的状态 count 大于等于 10 时才重新渲染
if (nextState.count >= 10) {
return true;
}
// 其他情况下不重新渲染
return false;
}
render() {
return (
<div>
<p>Hello, {this.props.name}</p>
<p>Count: {this.state.count}</p>
{/* 其他组件内容 */}
</div>
);
}
}
在上面的示例中,shouldComponentUpdate方法首先比较了下一个属性 name 和当前属性 name 的值,如果它们不相等,就返回true,表示需要重新渲染组件。接着,它比较了下一个状态 count 是否大于等于 10,如果是,则返回true。
通过使用shouldComponentUpdate方法,您可以优化组件的性能,避免不必要的重新渲染。请注意,在优化性能时,确保您正确地比较属性和状态,以及正确地处理更新的逻辑。
150、React v16.8中的hooks
React Hooks 是在 React 16.8.0 版本中首次引入的。
React v16.8 中引入了 Hooks ,是 React 一种全新的状态管理方式,它提供了一些可以让函数组件拥有类组件同样功能的 API。
其中最常用的 Hook API 包括:
-
useState:该 Hook 用于在函数组件中添加一个状态管理器。通过useState,可以创建一个状态变量及其更新函数,并在组件内使用该变量来保存和更新组件的状态。[1] -
useEffect:该 Hook 用于在组件渲染完成后执行一些副作用操作(例如订阅数据、更新 DOM 等)。通过useEffect,可以在组件加载、更新和卸载时设置和清理副作用操作,并且可以在副作用操作之间共享状态。[2] -
useContext:该 Hook 用于在组件之间共享一些全局的状态或函数,以避免通过多层嵌套的 Props 传递进行数据传输。通过useContext可以让组件在全局状态或函数的上下文中运行,并让它们能够方便地读取或更新全局状态或函数。[3] -
useReducer:该 Hook 用于在组件中使用一种“状态容器”模式,以避免通过多层 Props 传递或 Context 共享进行状态管理。通过useReducer可以创建一个状态容器及其更新函数,并在组件内使用该容器来保存和更新组件的状态。[4] -
useMemo:该 Hook 用于在组件渲染完成后缓存一些计算结果,以避免因为重复计算导致的性能问题。通过useMemo可以创建一个缓存变量,并在组件内使用该变量来保存计算结果并缓存。[5] -
useCallback:该 Hook 用于在组件渲染完成后,将一些函数进行缓存,以避免因函数重复创建导致的性能问题。通过useCallback可以创建一个缓存函数,并在组件内使用该函数来代替重复创建的函数。[6] -
useRef:该 Hook 用于在组件渲染完成后创建一个引用,以便在组件多次渲染时能够保留上一次渲染中的值。通过useRef可以创建一个引用变量,并在组件内使用该变量来保存一些持久化的数据。[7] -
useImperativeHandle:该 Hook 用于在组件中实现一些自定义的 Ref 对象,并且要求将一些组件内部的方法或状态暴露给父组件使用。通过useImperativeHandle,可以创建一个自定义的 Ref 对象,并在组件内指定一些公开的方法或属性。[8] -
useLayoutEffect:该 Hook 与useEffect类似,但它会在浏览器渲染更新之前同步执行副作用操作,以确保 React 组件与浏览器同步更新。通常情况下,应该使用useEffect但在需要直接操作 DOM 元素或进行测量布局界面时,应当使用useLayoutEffect。[9] -
useDebugValue:该 Hook 可以帮助开发者在调试工具中显示额外的信息,以便更好地理解 Hook 的使用和行为。通常情况下,这个 Hook 只用于调试过程中,而不是实际的应用程序代码中。[10]
151、React v18中的hooks
-
useSyncExternalStore:该 Hook 可以让 React 组件与外部存储(例如浏览器 API、Web Worker 或其他 JavaScript 环境)同步。通过订阅外部存储中的值,该 Hook 可以自动更新组件的状态,使得应用程序可以更好地与浏览器交互和管理状态。[2] -
useTransition:该 Hook 可以使得 React 组件在执行某些异步操作时更加平滑,避免因为在异步操作未完成时频繁渲染导致的卡顿和闪屏等问题。通过将某些重要的界面元素标记为“暂态(transitional)”,该 Hook 可以使用协调器(coordinator)来确保异步操作完成后再进行渲染,从而提供更好的用户体验。[3] -
useDeferredValue:该 Hook 可以让 React 应用显示过期或不完全更新的数据,同时在后台加载新数据,从而实现更加平滑的用户体验。通过将一部分界面元素标记为“延迟渲染(deferred rendering)”,该 Hook 可以在加载新数据时将这些元素保留在屏幕上,而不必重新进行渲染,从而提高应用的性能和可靠性。[4] -
useInsertionEffect:该 Hook 可以让 React 组件在插入到 DOM 中时立即执行一些副作用操作(例如绑定事件监听器、设置初始状态等),而不必等待渲染完成或等待下一个渲染周期。通过使用useInsertionEffect,可以在组件挂载时立即执行某些副作用操作,从而提高应用的响应速度和性能。[5] -
useId:该 Hook 可以让 React 应用生成唯一的 ID 标识符,以便在应用中创建唯一的 DOM 元素。通过使用useId,可以确保在应用中产生唯一的元素 ID,避免因为命名冲突导致的意外错误。
152、React中函数组件使用哪些hook来代替生命周期
React 中函数式组件可以使用以下 Hook 来代替类组件中的生命周期方法:
componentDidMount:可以使用 useEffect 和空依赖数组来实现,在组件挂载后执行一次,只在组件卸载时进行清理。
useEffect(() => {
// componentDidMount 逻辑
return () => {
// componentWillUnmount 逻辑
}
}, [])
componentDidUpdate:同样也可以使用 useEffect 来实现,在组件更新后执行一次(除了第一次),使用依赖数组来决定什么时候会触发更新,同时需要注意防止死循环。
useEffect(() => {
// componentDidUpdate 逻辑
}, [dependencies])
componentWillUnmount:使用 useEffect 和返回的清理函数来实现,在组件卸载时执行一次。
useEffect(() => {
// componentDidMount 逻辑
return () => {
// componentWillUnmount 逻辑
}
}, [])
getDerivedStateFromProps:虽然 useEffect 不能直接取代 getDerivedStateFromProps,但是可以通过在 useEffect 中更新状态来达到相同的效果。
const [state, setState] = useState(props.someProp)
useEffect(() => {
setState(props.someProp)
}, [props.someProp])
shouldComponentUpdate:由于函数组件无法使用 shouldComponentUpdate,因此可以使用 React.memo 方法对组件进行包裹来达到同样的效果。
const MemoizedComponent = React.memo(Component)
componentDidCatch:可以使用 ErrorBoundary 组件来代替,在子组件发生错误时进行捕获和处理。
function ErrorBoundary(props) {
const [error, setError] = useState(null)
const [errorInfo, setErrorInfo] = useState(null)
if (error) {
return (
<div>
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{error && error.toString()}
<br />
{errorInfo.componentStack}
</details>
</div>
)
}
return props.children
}
<ErrorBoundary>
{/* 子组件 */}
</ErrorBoundary>
总的来说, React Hook 可以让我们更加方便地使用函数式组件实现类组件的生命周期方法,而且还能够避免很多 this 指向的问题。
153、React中如何拿到函数组件的实例
可以通过 forwardRef 和 useImperativeHandle 两个 React 方法获取函数组件的实例。它们的作用不同,但都可以让父组件获取到子组件的引用。
使用 forwardRef 可以创建一个接受 ref 的函数组件,并将 ref 转发给其内部的子组件。这样,在父组件中通过 ref 拿到该函数组件的实例后,就可以调用该组件内部子组件上的方法。示例代码如下:
// 子组件
const Child = React.forwardRef((props, ref) => {
const handleClick = () => {
console.log('click from child');
};
useImperativeHandle(ref, () => ({
handleClick,
}));
return <button onClick={handleClick}>Click me</button>;
});
// 父组件
function Parent() {
const childRef = useRef();
const handleClick = () => {
childRef.current.handleClick();
};
return (
<>
<Child ref={childRef} />
<button onClick={handleClick}>Call child's handleClick</button>
</>
);
}
其中,父组件中通过 useRef 创建一个 ref,并将其传递给子组件 <Child>。在子组件中,通过 forwardRef 定义一个匿名函数,并使用 useImperativeHandle 将需要暴露给父组件的方法添加到 ref.current 属性中。
使用 useImperativeHandle 可以在函数组件内部定义一个自定义的 ref 对象,并暴露一个函数来供父组件调用。使用时,需要将这个自定义的 ref 对象作为第二个参数传递给 useImperativeHandle。
在这个例子中,子组件 <Child> 中通过 useImperativeHandle 定义了一个自己的 ref 对象,并将 handleClick 方法添加到 ref.current 上暴露出去。父组件中创建了一个 ref 对象,并将其传递给了子组件。在父组件中可以通过 childRef.current.handleClick() 来调用子组件中暴露出来的方法。
154、React中函数组件的性能优化
- React.memo:优化函数组件的性能
React.memo 是 React 提供的一个高阶组件,用来优化函数组件的性能。使用 React.memo 可以帮助我们在避免不必要的重新渲染时提高组件的性能。
通过使用 React.memo,React 可以跟踪组件的 props 是否发生变化。如果 props 没有发生变化,则可以避免重新渲染组件,从而提高性能。这个功能类似于类组件中的 shouldComponentUpdate 生命周期方法。
使用 React.memo 需要注意的是:被包裹的组件必须是无状态组件(stateless component),并且其 props 变化所引起的渲染开销比不使用React.memo 带来的开销更大才会有明显的性能提升。
import React, { memo } from 'react';
const MyComponent = memo((props) => {
// 组件的渲染逻辑
});
export default MyComponent;
在这个例子中,我们使用了 React.memo 对 MyComponent 进行了包裹。这样,当 MyComponent 的 props 没有变化时,它就不会重新渲染了。
- React中useCallbck 和 useMemo的区别
useCallback 和 useMemo 都是 React 中用于性能优化的 Hooks,它们在减少组件重新渲染方面有类似的作用,但是它们的使用场景略有不同。下面是它们之间的主要区别:
调用时机
useCallback 与 useMemo 最显著的区别是它们被调用的时机不同。useCallback 在渲染期间返回一个记忆化函数的引用,该函数仅在其依赖项发生更改时才会重新创建。而 useMemo 在渲染期间返回一个记忆化值的引用,该值仅在其依赖项发生更改时才会重新计算。
返回值类型不同
useCallback 和 useMemo 返回的值类型不同,因为它们的用途不同。useCallback 返回一个记忆化的函数,用于避免将新的函数传递给子组件进行 props 比较。而 useMemo 返回一个记忆化的值,可以用于保留中间状态。
使用场景
useCallback 通常用于在渲染期间生成回调函数,以避免在每个重新渲染时创建新的回调。它特别有用在通过 props 传递的回调函数涉及到子组件重新渲染时。而 useMemo 用于缓存昂贵的计算结果,以避免在每个重新渲染时进行重新计算。它特别适用于复杂数据处理和DOM操作。
综上所述,useCallback 和 useMemo 都是 React 中极具价值的性能优化工具,但它们最好的使用场景并不相同。可以根据需要选择 useCallback 或 useMemo 进行性能优化,以获得最佳的效果和性能提升。
- React中的useMemo和memo
在React中,useMemo和memo都是用来优化组件性能的API,但它们的使用场景和实现方式略有不同。
useMemo是一个React Hook,用于缓存函数执行的结果并返回该结果。它接收两个参数:一个计算函数和一个依赖项数组。当依赖项发生变化时,useMemo会重新执行计算函数并返回新的结果。如果依赖项没有变化,useMemo会直接返回缓存的结果,从而避免重复计算,提高性能。
下面是一个示例,演示如何使用useMemo来缓存计算结果:
function MyComponent({ a, b }) {
const result = useMemo(() => {
console.log("calculating...");
return a + b;
}, [a, b]);
return <div>Result: {result}</div>;
}
在上面的示例中,useMemo缓存了计算结果a + b,并且只在a或b发生变化时重新计算。因此,如果a或b没有改变,console.log语句也不会被触发。
另外一个用于优化组件性能的API是memo。它是一个高阶组件(HOC),用于封装一个无状态组件并返回一个新的组件。memo通过比较当前和前一个属性(props)和状态(state)的值,确定是否需要重新渲染组件。如果属性和状态的值没有变化,memo会直接返回缓存的结果,从而提高性能。
下面是一个示例,演示如何使用memo来缓存组件的结果:
const MyComponent = memo(({ a, b }) => {
console.log("rendering...");
return <div>Result: {a + b}</div>;
});
在上面的示例中,memo封装了一个无状态组件,并缓存了其结果。如果a或b没有改变,memo不会重新渲染组件。
需要注意的是,useMemo和memo都是用于优化组件性能的API,但它们的使用场景略有不同。useMemo适用于缓存计算结果,而memo适用于缓存组件渲染结果。同时,useMemo可以在函数组件中使用,而memo则需要封装无状态组件。
155、React Hook为什么不能放到条件语句中
React Hook 不能放到条件语句中的原因是 React 需要使用 Hook 的规则是必须确保每次渲染时,Hook 调用的顺序都是一致的。也就是说,在一个组件内部,每一次渲染时,Hook 的调用顺序必须是相同的。
如果将 Hook 放到条件语句中,当条件发生变化时,Hook 的调用顺序就可能被打乱,从而导致组件状态不一致、出现错误等问题。因此,React 在运行时会对 Hook 的调用顺序进行验证,来确保 Hook 的使用符合规范。
举个例子,如果我们在组件的函数体内,使用了 useState 这个 Hook 来管理组件的状态,那么这个 Hook 的调用顺序必须始终保持不变。如果我们将这个 Hook 放到条件语句中,那么当条件变化时,这个 Hook 的调用顺序就可能被改变,从而违反了 React Hook 的使用规则。
总之,React Hook 不能放到条件语句中是为了确保 Hook 的使用规范和组件状态的正确性。如果需要在条件语句中使用 Hook,可以使用类组件或自定义 Hook 等其他方式来解决。
156、React中实现自定义Hook
React 中自定义 Hook 实现的原理非常简单,就是利用 Hook 的机制来封装可复用的逻辑代码。自定义 Hook 必须使用 use 开头的命名,可以调用其他 Hook,比如 useState、useEffect 等。
下面是一个自定义 Hook 的示例,它将实现一个倒计时的逻辑:
import { useState, useEffect } from 'react'
function useCountdown(initialTime) {
const [timeLeft, setTimeLeft] = useState(initialTime)
useEffect(() => {
const intervalId = setInterval(() => {
setTimeLeft(prevTime => prevTime - 1)
}, 1000)
return () => clearInterval(intervalId)
}, [])
return timeLeft
}
在这个自定义 Hook 中,我们使用了 useState 来管理时间数据的状态,以及 useEffect 来实现定时器。每次定时器执行时,会更新时间状态,并返回最新的时间数据。通过这样一个自定义 Hook,我们可以在很多组件中复用倒计时功能。
值得注意的是,不同的组件都可以使用相同的自定义 Hook,但每一个组件都会有自己独立的状态,因此它们之间不会相互影响。
总之,自定义 Hook 实际上是一个函数,使用了 React 的一些内置 Hook 来封装和管理逻辑代码,从而让相似的逻辑可以在多个组件中复用。
157、React中常用的自定义Hook有哪些
- useToggle:快速地切换布尔值状态,用于切换开关、表单项
- useLocalStorage:在组件中使用本地存储来保存和读取数据。
- useDebounce:缓解用户输入造成的连续请求问题,延迟一段时间后再执行代码
- useFetch:管理异步请求状态,减少代码复杂度。
技术详解
在 React 中,自定义 Hook 是一种可以让你复用逻辑的机制,它允许你将组件之间的代码提取到可重用的函数中。
下面是 React 中常用的一些自定义 Hook:
useToggle 可以帮助我们快速地切换布尔值状态,通常用于切换开关、表单项等。
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue((v) => !v);
}, []);
return [value, toggle];
}
useLocalStorage 可以在组件中使用本地存储来保存和读取数据。
function useLocalStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
const jsonValue = localStorage.getItem(key);
if (jsonValue != null) return JSON.parse(jsonValue);
if (typeof defaultValue === "function") {
return defaultValue();
} else {
return defaultValue;
}
});
useEffect(() => {
if (value === undefined) return void localStorage.removeItem(key);
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
useDebounce 可以帮助我们缓解用户输入造成的连续请求问题,延迟一段时间后再执行代码。
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timeoutId = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timeoutId);
};
}, [value, delay]);
return debouncedValue;
}
useFetch 可以帮助我们管理异步请求状态,并减少代码复杂度。
function useFetch(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
fetch(url)
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error))
.finally(() => setIsLoading(false));
}, [url]);
return { data, error, isLoading };
}
使用自定义 Hook 可以帮助我们更好地管理组件逻辑,提高代码的可读性和可复用性。
158、React hooks的使用限制有哪些
React hooks 是 React16.8 引入的新特性,它可以让我们在无需编写 class 组件的情况下使用 state 和其他 React 功能。虽然 React hooks 很强大,但是在使用它们的时候也有一些限制和注意事项,主要包括以下几个方面:
只能在函数组件和自定义 hooks 中使用
React hooks 只能在函数式组件和自定义 hooks 中使用,不能在 class 组件或普通 JavaScript 函数中使用。
需要在组件的顶层使用
React hooks 必须在组件的顶层使用,不能在条件语句、循环语句或嵌套函数中使用。这是因为 React hooks 的调用顺序必须保持一致,如果不在组件的顶层使用,可能会导致 hooks 的调用顺序发生错误。
需要按照顺序使用
React hooks 需要按照严格的顺序使用,即不能跳过某个 hook。另外,React 还提供了一些规则来约束 hooks 的使用,例如 useState 钩子必须在函数的最顶层使用,不能在条件语句和循环语句中使用。
需要遵循 React 的生命周期
虽然使用 React hooks 可以不再需要使用 class 组件,但是 hooks 仍然需要遵循 React 的生命周期,例如 useEffect 钩子就相当于 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期的组合。因此,在使用 hooks 的时候也需要注意 React 的生命周期。
总之,在使用 React hooks 的时候,需要遵循一些限制和注意事项,以保证组件的正确性和可维护性。如果不理解或不遵循这些规则,可能会导致代码出现错误或难以调试。