前言
持续记录一些自己在学习react时遇到的知识点,小冰龙镇楼。
Q:react中useEffect和useLayoutEffect的区别
A:
执行顺序不同:
- useEffect在渲染完成后被调用,其副作用操作是在组件渲染完成后的“提交阶段”执行的。
- useLayoutEffect在dom更新前调用,其副作用操作是在组件渲染完成后的“布局阶段”执行的。
性能影响不同:
- useEffect为异步调用,不会阻塞UI的渲染。
- useLayoutEffect 为同步调用,如果在副作用中执行的操作非常耗时,可能导致页面响应变慢。此外,由于它的副作用会在DOM操作之后同步执行,如果涉及到DOM的更改,可能会因为触发两次页面的绘制引发页面抖动。
Q:在react中ref有什么作用?如果要实现父组件调用子组件方法,使用ref和forwardRef分别应该怎么实现?
A:
在React中,ref 提供了访问DOM节点或React元素的方式。forwardRef 完成对 ref 的转发
使用ref调用子组件方法
// 子组件 ChildComponent
class ChildComponent extends React.Component {
myMethod() {
console.log('myMethod called from ChildComponent');
}
render() {
return <div>Child Component</div>;
}
}
// 父组件 ParentComponent
class ParentComponent extends React.Component {
childRef = React.createRef();
callChildMethod = () => {
this.childRef.current.myMethod();
}
render() {
return (
<div>
<ChildComponent ref={this.childRef} />
<button onClick={this.callChildMethod}>Call Child Method</button>
</div>
);
}
}
使用 forwardRef调用子组件方法
// 子组件 ChildComponent
const ChildComponent = React.forwardRef((props, ref) => {
const myMethod = () => {
console.log('myMethod called from ChildComponent');
}
React.useImperativeHandle(ref, () => ({
myMethod
}));
return <div>Child Component</div>;
});
// 父组件 ParentComponent
function ParentComponent() {
const childRef = React.createRef();
const callChildMethod = () => {
childRef.current.myMethod();
}
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={callChildMethod}>Call Child Method</button>
</div>
);
}
有关forwardRef相关可看:React forwardRef相关总结-CSDN博客
Q:什么是 React Protal? 如何理解?
A:官方解释为
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
React Portals 的主要优势是它可以在组件树中的某一级组件上渲染内容,而不受该组件的父组件或祖先组件的影响。这在处理全局或跨层级的 UI 元素时非常有用,因为它不会破坏组件的层次结构。它允许你将一个组件的渲染内容“传送”到 DOM 结构中的任何位置,通常用于处理一些特殊的 UI 布局需求,如弹出窗口、模态框、通知框等。
其语法为:
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,
第二个参数(container)是一个 DOM 元素。
使用实例
class MyPortal extends React.Component {
constructor(props) {
super(props);
// 创建一个新的 DOM 元素用于 Portal
this.portalElement = document.createElement('div');
// 定义要渲染到 Portal 上的内容
this.portalContent = (
<div>
<p>这是 Portal 中的内容</p>
</div>
);
}
componentDidMount() {
// 将 Portal 内容渲染到指定的 DOM 元素上
document.body.appendChild(this.portalElement);
this.componentDidUpdate();
}
componentDidUpdate() {
// 使用 ReactDOM.createPortal 将内容渲染到 Portal 上
ReactDOM.createPortal(this.portalContent, this.portalElement);
}
componentWillUnmount() {
// 在组件卸载时,清理 Portal
document.body.removeChild(this.portalElement);
}
render() {
// 不需要在组件的 render 方法中返回任何内容
return null;
}
}
Q:在 React 项目中进行优化的手段
A:在 React 项目中,优化是一个持续的过程,它可以帮助我们提高应用的性能、减少内存占用和改善用户体验。以下是一些在React项目中常见的优化策略:
- 代码拆分(Code Splitting)
- 使用React的
React.lazy()
和Suspense
组件来按需加载组件,而不是一次性加载整个应用。 - 利用Webpack、Rollup等打包工具进行代码拆分,将代码分割成更小的包,以便并行加载。
- 使用React的
- 组件优化
- 纯组件(Pure Components):使用
React.PureComponent
或React.memo
来确保组件在props或state没有变化时不会重新渲染。 - 避免不必要的渲染:使用
shouldComponentUpdate
生命周期方法或在函数组件中使用React.memo
来避免不必要的重新渲染。 - 使用React.Profiler:分析组件的渲染时间和性能瓶颈。
- 纯组件(Pure Components):使用
- 列表和表格优化
- 虚拟化(Virtualization):对于大量数据的列表和表格,只渲染视口范围内的数据,如
react-window
库。 - 唯一键(Keys):为列表中的每个元素提供一个唯一的
key
属性,以便React可以高效地更新和重新排序DOM。
- 虚拟化(Virtualization):对于大量数据的列表和表格,只渲染视口范围内的数据,如
- 状态管理
- 使用Redux或MobX等状态管理库:它们提供了高效的状态更新和组件间通信机制。
- 避免在组件树中传递大量props:使用Context API或Redux等库来减少props的传递。
- 图片和媒体优化
- 压缩图片:使用工具如TinyPNG或WebP格式来减小图片大小。
- 懒加载图片:使用库如
react-lazyload
来按需加载图片。
- 减少网络请求
- HTTP缓存:使用HTTP缓存头来缓存资源,减少不必要的网络请求。
- 合并请求:将多个小请求合并为一个请求,以减少网络开销。
- 服务端渲染(SSR)或预渲染
- Next.js或Gatsby等框架支持SSR,可以在服务器上渲染页面并发送给客户端,提高首屏加载速度。
- 预渲染:生成静态HTML页面,并在客户端加载JavaScript来使其具有交互性。
- 使用Profiler API
- React 16.5+引入了Profiler API,它可以帮助你分析哪些组件需要重新渲染,以及重新渲染需要多长时间。
- Web Workers
- 对于计算密集型任务,可以使用Web Workers在后台线程中运行,以避免阻塞UI线程。
- 使用库和工具
- Linting:使用ESLint等工具来检查代码中的潜在问题,并遵循一致的代码风格。
- 性能分析工具:使用React DevTools、Chrome DevTools等性能分析工具来识别和修复性能瓶颈。
- 减少全局状态的使用
- 尽量避免使用全局状态,因为它们可能导致不必要的重新渲染和难以追踪的bug。如果确实需要使用全局状态,请考虑使用Redux、MobX等库来管理它们。
- 优化第三方库
- 评估项目中使用的第三方库的性能,并考虑替换为更高效的替代品。
- 使用Tree Shaking
- Tree Shaking可以帮助你消除JavaScript代码中的未引用代码(dead code),从而减小最终打包的文件大小。确保你的打包工具(如Webpack)已启用Tree Shaking功能。
- 保持代码库整洁
- 定期清理和优化代码库,删除不再需要的代码和依赖项。这有助于保持应用的性能和可维护性。
Q:react中真实dom和虚拟dom都是如何搭建的,有节点删改时会不会进行重绘和重排?
A:
- 真实DOM的搭建:通过HTML和CSS的解析,浏览器构建DOM树和CSSOM,然后合并成渲染树(render tree),最后进行布局和绘制。
- 虚拟DOM的搭建:通过JSX或
React.createElement
在内存中创建JavaScript对象树,作为真实DOM的抽象表示。
真实Dom会进行重绘和重排,虚拟Dom不会(React通过其高效的Diff算法和批量更新策略来最小化重绘和重排的次数。)
Q:react是如何通过虚拟dom把改变的部分传递给真实dom的
A:
- 构建虚拟 DOM:
- 当组件的状态(state)或属性(props)发生变化时,React 会重新调用组件的
render
方法来生成一个新的虚拟 DOM 树。 - 这个新的虚拟 DOM 树描述了组件在给定状态或属性下的理想界面。
- 当组件的状态(state)或属性(props)发生变化时,React 会重新调用组件的
- 比较虚拟 DOM:
- React 使用一个高效的算法(通常是 Diffing 算法,如 React 实现的 React Fiber)来比较之前的虚拟 DOM 树和新的虚拟 DOM 树之间的差异。
- 这个过程会识别出哪些部分已经改变,哪些部分保持不变。
- 计算最小变化集:
- 通过比较,React 能够计算出将旧 DOM 更新为新 DOM 所需要执行的最小操作集。
- 这可能包括添加、删除、移动或更新 DOM 节点。
- 应用变化到真实 DOM:
- 一旦计算出最小的变化集,React 就会将这些变化应用到真实的 DOM 上。
- 这个过程通常非常快,因为 React 只更新实际发生变化的部分,而不是重新渲染整个页面。
- 优化和复用:
- React 还会尝试通过重用和重新排序现有的 DOM 节点来优化性能。
- 例如,如果一个元素在两次渲染之间只是移动了位置,React 会尝试在 DOM 中移动该元素,而不是创建一个新的元素并删除旧的元素。
Q:为什么hooks能重用和共享组件状态?
A: 1)React 允许开发者创建自定义 Hooks,这些自定义 Hooks 可以封装可复用的逻辑和状态。通过自定义 Hooks,开发者可以将组件中的某些逻辑(如状态管理、数据获取、副作用处理等)抽象成独立的函数,然后在多个组件中共享这些函数。(自定义 Hooks 通常以 "use" 开头。
2)Hooks 使得组件的状态和逻辑可以从 UI 中解耦出来,这样不仅可以提高代码的可读性和可维护性,还可以更容易地在多个组件之间共享这些状态和逻辑。例如,你可以创建一个自定义 Hook 来管理表单状态,然后在多个表单组件中重用这个 Hook,而无需在每个组件中重复编写相同的逻辑。
3)Hooks 遵循函数式编程的原则,函数式组件本身具有更高的灵活性和简洁性,它们不依赖于类的继承结构,可以作为参数传递、作为返回值返回,并且没有副作用(除了通过特定的 Hooks 如 useEffect 管理的副作用)。这种编程风格使得代码更加模块化和可预测,也更容易进行单元测试。由于 Hooks 使得组件的逻辑更加集中和清晰,因此它们也更容易被重用和共享。
结合 useContext Hook来使用Context API,或者使用redux 以在组件之间共享状态
举一个自定义hook的例子
import { useState, useEffect } from 'react';
// 自定义Hook:useArraySum
function useArraySum(initialArray) {
// 使用useState来存储数组和总和
const [array, setArray] = useState(initialArray);
const [sum, setSum] = useState(initialArray.reduce((acc, curr) => acc + curr, 0));
// 使用useEffect来监听数组的变化,并重新计算总和
useEffect(() => {
setSum(array.reduce((acc, curr) => acc + curr, 0));
}, [array]); // 依赖项数组,只有当array变化时,这个effect才会运行
// 提供一个函数来更新数组
const updateArray = (newArray) => {
setArray(newArray);
};
// 返回总和和更新数组的函数
return [sum, updateArray];
}
// 使用自定义Hook的组件
function SumComponent() {
// 初始数组
const [initialArray] = useState([1, 2, 3, 4]);
// 使用自定义Hook
const [sum, updateArray] = useArraySum(initialArray);
// 一个按钮来更新数组
const handleUpdate = () => {
updateArray([5, 5, 5]);
};
return (
<div>
<p>Sum: {sum}</p>
<button onClick={handleUpdate}>Update Array</button>
</div>
);
}
export default SumComponent;