个人理解记录 --- React 篇 (持续更新)

前言

持续记录一些自己在学习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项目中常见的优化策略:

  1. 代码拆分(Code Splitting)
    • 使用React的React.lazy()Suspense组件来按需加载组件,而不是一次性加载整个应用。
    • 利用Webpack、Rollup等打包工具进行代码拆分,将代码分割成更小的包,以便并行加载。
  2. 组件优化
    • 纯组件(Pure Components):使用React.PureComponentReact.memo来确保组件在props或state没有变化时不会重新渲染。
    • 避免不必要的渲染:使用shouldComponentUpdate生命周期方法或在函数组件中使用React.memo来避免不必要的重新渲染。
    • 使用React.Profiler:分析组件的渲染时间和性能瓶颈。
  3. 列表和表格优化
    • 虚拟化(Virtualization):对于大量数据的列表和表格,只渲染视口范围内的数据,如react-window库。
    • 唯一键(Keys):为列表中的每个元素提供一个唯一的key属性,以便React可以高效地更新和重新排序DOM。
  4. 状态管理
    • 使用Redux或MobX等状态管理库:它们提供了高效的状态更新和组件间通信机制。
    • 避免在组件树中传递大量props:使用Context API或Redux等库来减少props的传递。
  5. 图片和媒体优化
    • 压缩图片:使用工具如TinyPNG或WebP格式来减小图片大小。
    • 懒加载图片:使用库如react-lazyload来按需加载图片。
  6. 减少网络请求
    • HTTP缓存:使用HTTP缓存头来缓存资源,减少不必要的网络请求。
    • 合并请求:将多个小请求合并为一个请求,以减少网络开销。
  7. 服务端渲染(SSR)或预渲染
    • Next.jsGatsby等框架支持SSR,可以在服务器上渲染页面并发送给客户端,提高首屏加载速度。
    • 预渲染:生成静态HTML页面,并在客户端加载JavaScript来使其具有交互性。
  8. 使用Profiler API
    • React 16.5+引入了Profiler API,它可以帮助你分析哪些组件需要重新渲染,以及重新渲染需要多长时间。
  9. Web Workers
    • 对于计算密集型任务,可以使用Web Workers在后台线程中运行,以避免阻塞UI线程。
  10. 使用库和工具
    • Linting:使用ESLint等工具来检查代码中的潜在问题,并遵循一致的代码风格。
    • 性能分析工具:使用React DevTools、Chrome DevTools等性能分析工具来识别和修复性能瓶颈。
  11. 减少全局状态的使用
    • 尽量避免使用全局状态,因为它们可能导致不必要的重新渲染和难以追踪的bug。如果确实需要使用全局状态,请考虑使用Redux、MobX等库来管理它们。
  12. 优化第三方库
    • 评估项目中使用的第三方库的性能,并考虑替换为更高效的替代品。
  13. 使用Tree Shaking
    • Tree Shaking可以帮助你消除JavaScript代码中的未引用代码(dead code),从而减小最终打包的文件大小。确保你的打包工具(如Webpack)已启用Tree Shaking功能。
  14. 保持代码库整洁
    • 定期清理和优化代码库,删除不再需要的代码和依赖项。这有助于保持应用的性能和可维护性。

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:

  1. 构建虚拟 DOM
    • 当组件的状态(state)或属性(props)发生变化时,React 会重新调用组件的 render 方法来生成一个新的虚拟 DOM 树。
    • 这个新的虚拟 DOM 树描述了组件在给定状态或属性下的理想界面。
  2. 比较虚拟 DOM
    • React 使用一个高效的算法(通常是 Diffing 算法,如 React 实现的 React Fiber)来比较之前的虚拟 DOM 树和新的虚拟 DOM 树之间的差异。
    • 这个过程会识别出哪些部分已经改变,哪些部分保持不变。
  3. 计算最小变化集
    • 通过比较,React 能够计算出将旧 DOM 更新为新 DOM 所需要执行的最小操作集。
    • 这可能包括添加、删除、移动或更新 DOM 节点。
  4. 应用变化到真实 DOM
    • 一旦计算出最小的变化集,React 就会将这些变化应用到真实的 DOM 上。
    • 这个过程通常非常快,因为 React 只更新实际发生变化的部分,而不是重新渲染整个页面。
  5. 优化和复用
    • 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;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值