, ,

React总结

⾼阶组件(HOC)是什么?你在业务中使⽤过解决了什么问题。 HOC本质上是⼀个函数,它接收⼀个组件作为参数,然…

⾼阶组件(HOC)是什么?你在业务中使⽤过解决了什么问题。

HOC本质上是⼀个函数,它接收⼀个组件作为参数,然后返回⼀个新的组件。返回的新组件将拥有被包裹的组件的所有props,并且可以添加额外的props或状态。

HOC可以⽤于抽象出组件之间的共享代码,以增强组件的复⽤性和可维护性。也可以⽤于控制props,封装组件状态,或者通过引⽤(ref)来访问组件实例。

在这个示例中, withExtraProp 是⼀个HOC,它接收⼀个组件Component ,然后返回⼀个新的组件,新的组件会向 Component 添加⼀个额外的prop。

在业务中可能会使⽤到HOC的例⼦:

1. 授权和权限管理: 例如,你可能有⼀些组件只允许认证过的⽤户访问。你可以创建⼀个HOC,它接收⼀个组件并返回⼀个新的组件,新的组件在渲染之前会检查⽤户是否已经认证。

2. 数据获取: 另⼀个常⻅的HOC⽤例是⽤于数据获取。例如,你可以创建⼀个HOC,它接收⼀个组件并返回⼀个新的组件,新的组件在挂载时获取数据,并将数据通过props传递给被包裹的组件。

3. 错误处理: 你可以创建⼀个HOC,它接收⼀个组件并返回⼀个新的组件,新的组件包裹原组件的渲染,并在发⽣错误时显示错误信息或其他备⽤内容。

什么时候应该使⽤类组件⽽不是函数组件?React组件错误捕获怎么做?

错误边界是React中的⼀种特性,它允许你在⼦组件树中捕获JavaScript错误,并在发⽣错误时显示备⽤内容,⽽不是让整个组件树崩溃。错误边界在React中只能通过类组件来实现。

class ErrorBoundary extends React.Component {
  state = { hasError: false, errMsg: "" };

  // 捕获错误,更新state
  static getDerivedStateFromError(error) {
    return { hasError: true, errMsg: error.message };
  }

  // 捕获错误日志,上报埋点
  componentDidCatch(error, info) {
    console.error("组件渲染异常:", error, info.componentStack);
    // 上报错误监控平台
    // reportError(error, info);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>页面加载出错,请刷新重试</div>;
    }
    return this.props.children;
  }
}
<ErrorBoundary fallback={<div>渲染失败</div>}>
  <PageContent />
</ErrorBoundary>

这样,如果 MyWidget 组件或者它的⼦组件在渲染过程中发⽣错误, ErrorBoundary 就会捕获到这个错误,并显示备⽤UI,⽽不会导致整个应⽤崩溃。

React 中如何创建Refs?创建Refs的⽅式有什么区别?

在React中,Refs主要⽤于获取和操作DOM元素或者React组件的实例,是⼀种逃脱props传递的⽅法。React中创建Refs的主要⽅式有两种: React.createRef() 和回调Refs。

1. React.createRef() : 这是React 16.3版本后推出的新API,使⽤这种⽅式创建的Ref可以在整个组件的⽣命周期中保持不变。

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
 }
componentDidMount() {
const node = this.myRef.current;
// 你现在可以访问DOM节点或者组件实例
 }
render() {
return <div ref={this.myRef} />;

2. 回调Refs: 除了 React.createRef() 外,你还可以使⽤⼀个函数作为Ref。这个函数将会在组件挂载和卸载时分别被调⽤,并将DOM节点或组件实例作为参数。

class MyComponent extends React.Component {
componentDidMount() {
// 你现在可以访问DOM节点或者组件实例
 }
render() {
return <div ref={node => (this.myRef = node)} />;
 }
}

这两种Refs创建⽅式的主要区别在于:

React.createRef() 创建的Ref更简洁,API更⼀致,⽽且Ref的值在组件的整个⽣命周期中保持不变。

回调Refs更灵活,它允许你在组件挂载和卸载时执⾏⼀些额外的逻辑。但是,如果回调函数是在 render ⽅法中定义的,那么每次 render 时都会创建⼀个新的函数实例,可能会导致⼀些性能问题。总的来说,除⾮你有特殊需求,否则建议使⽤ React.createRef() 来创建Ref。

函数组件中,你可以使⽤ React.useRef() 和 回调 Refs 来创建 Refs。

1. React.useRef() :这是 React Hooks API 的⼀部分,可以在函数组件中使⽤。使⽤ useRef 创建的 Ref 在整个组件的⽣命周期中保持不变。

import React, { useRef } from 'react';
function MyComponent() {
const myRef = useRef(null);
React.useEffect(() => {
const node = myRef.current;
// 你现在可以访问 DOM 节点或者组件实例
 }, []);
return <div ref={myRef} />;
}

2. 回调 Refs: 你也可以在函数组件中使⽤回调 Refs。这种⽅式下,你会提供⼀个函数,该函数会在组件挂载和卸载时分别被调⽤,并将 DOM 节点或组件实例作为参数。

import React, { useEffect, useState } from 'react';
function MyComponent() {
const [myRef, setMyRef] = useState(null);
useEffect(() => {
if (myRef) {
// 你现在可以访问 DOM 节点或者组件实例
 }
 }, [myRef]);
return <div ref={node => setMyRef(node)} />;
}

createContext解决了什么问题?React⽗组件如何与⼦组件通信?⼦组件如何改变⽗组件的状态?

React的 createContext API主要解决了props“穿透”的问题,即你需要把⼀个prop层层传递给深层嵌套的⼦组件。这在应⽤中有⼀些全局可⽤的数据时(如主题,⽤户信息等)⾮常有⽤。通过创建⼀个context,你可以让组件直接访问这些数据,⽆需通过props层层传递。

import React, { createContext, useContext } from 'react';
// 创建⼀个Context对象
const ThemeContext = createContext('light');
function App() {
// 使⽤Provider组件来提供context的值
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
 );
}
function Toolbar() {
return <Button />;
}
function Button() {
// 使⽤useContext Hook来消费context的值
const theme = useContext(ThemeContext);
return <button>{theme}</button>;
}

在上述代码中,我们⾸先使⽤ createContext 创建了⼀个 ThemeContext 。然后,我们在 App 组件中使⽤ ThemeContext.Provider 来提供context的值。最后,在 Button 组件中,我们使⽤ useContext Hook来消费context的值。

React⽗组件如何与⼦组件通信?

⽗组件通过props向⼦组件传递数据和函数。⼦组件可以通过props获取到这些数据和函数。

⼦组件如何改变⽗组件的状态?

⼦组件⽆法直接改变⽗组件的状态。但是,⽗组件可以通过props向⼦组件传递⼀个函数,⼦组件调⽤这个函数就能间接地改变⽗组件的状态。

import React, { useState } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
 };
return (
<div>
<p>Count: {count}</p>
<Child onIncrement={handleIncrement} />
</div>
 );
}
function Child({ onIncrement }) {
return <button onClick={onIncrement}>Increment</button>;
}

memo有什么⽤途?

React 中的 memo 是⼀个⾼阶组件,它⽤于优化组件的渲染性能。 memo 可以将⼀个纯函数组件(⽆状态组件)包装起来,以避免不必要的重新渲染。

useMemo专⽤的hooks,缓存值。useCallback⼀般缓存我们的函数。

React.memo 的⽤途:

React.memo 是⼀个⾼阶组件,它类似于 React.PureComponent ,但只适⽤于函数组件,不适⽤于类组件。 React.memo 对⼀个组件进⾏封装,使其仅在props 改变时进⾏重新渲染,⽽不是每次⽗组件重新渲染时都进⾏渲染。这样可以避免不必要的渲染,提⾼性能。

useMemo 和 memo 的区别:

useMemo 是⼀个 Hook,它⽤于避免执⾏昂贵的计算操作。当依赖项改变时, useMemo 将重新计算缓存的值。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b),
[a, b]);

React.memo 是⼀个⾼阶组件,它⽤于避免函数组件进⾏不必要的重新渲染。简单来说, useMemo 是⽤于优化计算操作,⽽ React.memo 是⽤于优化渲染。

useCallback 和 useMemo 的区别:

useCallback 和 useMemo 都是⽤于优化的 Hook,但它们⽤于优化的对象不同。useCallback ⽤于返回⼀个 memoized 的回调函数:

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

useMemo ⽤于返回⼀个 memoized 的值:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b),
[a, b]);

如果你有⼀个依赖于某些值的函数,并且你想防⽌这个函数在这些值未改变时被重新创建,那么你应该使⽤ useCallback 。如果你有⼀个依赖于某些值的昂贵计算,并且你想防⽌这个计算在这些值未改变时被重新执⾏,那么你应该使⽤useMemo 。

React中的状态管理库你如何选择?什么是状态撕裂?

Redux: 这是最常⻅的状态管理库,适⽤于⼤型应⽤和需要跨组件共享状态的情况。Redux 提供了⼀个集中式的状态存储,可以让你在任何地⽅访问和修改状态。如果你的应⽤状态逻辑⽐较复杂,或者你需要在应⽤的不同部分共享⼤量的状态

Context API useState/useReducer Hooks: 对于⼀些⼩型应⽤或组件,你可能不需要⼀个完整的状态管理库。React 的 Context API 和Hooks(例如 useState 和 useReducer)可能已经⾜够满⾜你的需求。使⽤这些 React 内置的功能,你可以在组件间共享状态,⽽⽆需添加额外的依赖。

状态撕裂是指在并发渲染(Concurrent Mode)中,由于渲染的优先级不同,可能导致应⽤中的不同部分看到的同⼀份共享状态不⼀致的问题。这是因为在 Concurrent Mode 下,React 可以选择暂停、中断或延迟某些更新,以优先处理更重要的更新。如果你的状态更新和组件的渲染不是同步的,那么就可能出现状态撕裂的问题。

useState 到底同步还是异步

1. 更新状态的 setXxx 函数本身是异步调度,不是同步更新 state

setCount(xxx) 调用后,不会立刻修改当前作用域里的 state 值,会放到更新队列批量处理,等本轮渲染函数执行完再统一更新、重渲染。

const [count, setCount] = useState(0);

const add = () => {
  setCount(count + 1);
  console.log(count); // 打印 0,不是 1!
};

原因:执行 setCount 只是提交更新任务,当前 count 还是闭包里旧值,渲染完成后才更新。

批量更新机制(表现出 “异步” 的根源)

① 合成事件(onClick/onChange 等 React 事件)、useEffect 内:批量合并更新

多次连续 set 会合并,只触发一次重渲染

const addMulti = () => {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
  // 最终只 +1,不是 +3
};

想要基于最新状态连续更新,用函数式更新

setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);

② 原生异步回调(setTimeout、Promise、fetch、原生 addEventListener):React 18 前后有区别

  • React 17 及更早:不会批量,每次 set 立刻触发渲染,看起来像同步
  • React 18+ 并发渲染下:统一自动批量更新,无论同步 / 异步回调,都会合并更新

React18 示例(setTimeout 内多次 set 也只会渲染一次):

const addAsync = () => {
  setTimeout(() => {
    setCount(p => p + 1);
    setCount(p => p + 1);
  }, 0);
};

state 的读取是同步 函数执行时,当前闭包的 state 值可以立刻读取,固定不变;

setState 调度更新是异步任务 状态修改、DOM 更新排队延后执行,无法立刻拿到新 state。

.如何立刻拿到更新后的 state?

  1. 函数式更新:依赖上一次 prevState,不受闭包旧值影响
  2. 用 useEffect 监听 state,state 变化后执行逻辑
useEffect(() => {
  // 这里是最新的count
  console.log("更新后", count);
}, [count])
  1. useRef 存一份实时数据(不触发渲染)

Previous Post

Next Post

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor