Vanson's Eternal Blog

React夯实基础

React basic.png
Published on
/30 mins read/---

高阶组件HOC

高阶组件(Higher-Order Component,HOC)是React中一种重用组件逻辑的高级技术。

定义

高阶组件是一个函数,它接受一个组件(或组件类)作为输入,并返回一个新的组件。这个新组件通常会:

  • 包装原始组件
  • 增强或修改原始组件的行为
  • 重用代码逻辑
import React from 'react';
 
// 高阶组件:日志记录功能
const withLogging = (WrappedComponent) => {
  return class extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} mounted`);
    }
 
    componentWillUnmount() {
      console.log(`Component ${WrappedComponent.name} unmounted`);
    }
 
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};
 
// 原始组件
const MyComponent = () => {
  return <div>My Component</div>;
};
 
// 使用HOC增强组件
const EnhancedComponent = withLogging(MyComponent);
 
// 使用增强后的组件
export default EnhancedComponent;
 

用法

  • withLogging 是一个HOC,它接受一个组件WrappedComponent作为参数。
  • withLogging 返回一个新的组件,这个新组件在WrappedComponent的基础上添加了日志记录功能。
  • MyComponent 是一个普通的React组件。
  • EnhancedComponent 是通过HOC增强后的组件,它具有日志记录功能。

随着React Hooks的引入,许多HOC的用例可以通过Hooks来实现。

高阶组件HOC应用场景

代码重用,逻辑和引导抽象

HOC允许你将通用逻辑抽象出来,避免重复代码。这使得组件更加模块化和可维护。

参考上述的 withLogging 实现。

渲染劫持

HOC可以在渲染过程中修改或增强组件的输出。这在需要动态修改组件显示内容时非常有用。

权限控制

import React from 'react';
 
// 高阶组件:权限控制
const withPermission = (WrappedComponent, requiredPermission) => {
  return class extends React.Component {
    render() {
      const { permissions } = this.props;
      if (!permissions.includes(requiredPermission)) {
        return <div>Access Denied</div>;
      }
      return <WrappedComponent {...this.props} />;
    }
  };
};
 
// 原始组件
const AdminPanel = () => {
  return <div>Admin Panel Content</div>;
};
 
// 使用HOC增强组件
const ProtectedAdminPanel = withPermission(AdminPanel, 'admin');
 
// 使用增强后的组件
export default ProtectedAdminPanel;
 

状态抽象和控制

HOC可以管理组件的状态,提供统一的状态管理。这在需要共享状态或复杂状态逻辑时非常有用。

状态提升

 
import React from 'react';
 
// 高阶组件:状态提升
const withState = (WrappedComponent, initialState) => {
  return class extends React.Component {
    state = initialState;
 
    render() {
      return <WrappedComponent {...this.props} state={this.state} />;
    }
  };
};
 
// 原始组件
const Counter = ({ state }) => {
  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => state.setCount(state.count + 1)}>Increment</button>
    </div>
  );
};
 
// 使用HOC增强组件
const StatefulCounter = withState(Counter, {
  count: 0,
  setCount: (newCount) => this.setState({ count: newCount }),
});
 
// 使用增强后的组件
export default StatefulCounter;
 
 

Props 控制

HOC可以修改或添加Props,增强组件的灵活性。这在需要为组件提供默认Props或动态Props时非常有用。

import React from 'react';
 
// 高阶组件:默认Props
const withDefaultProps = (WrappedComponent, defaultProps) => {
  return class extends React.Component {
    render() {
      return <WrappedComponent {...this.props} {...defaultProps} />;
    }
  };
};
 
// 原始组件
const Greeting = ({ name, message }) => {
  return <div>{message} {name}!</div>;
};
 
// 使用HOC增强组件
const DefaultGreeting = withDefaultProps(Greeting, {
  message: 'Hello',
});
 
// 使用增强后的组件
export default DefaultGreeting;
 

纯组件

纯组件(Pure Component)是一种特殊的组件,它们的渲染过程仅依赖于输入的props和state,而不会受到外部因素的影响。

纯组件的渲染过程是“纯”的,意味着只要输入相同,输出就一定相同。这种特性使得纯组件更容易理解和维护,同时也提高了应用的性能。

特点

  • 纯渲染逻辑:纯组件的渲染逻辑仅依赖于其props和state,不会受到外部状态或副作用的影响。
  • 性能优化:纯组件通过实现shouldComponentUpdate生命周期方法,避免了不必要的渲染,从而提高了性能。
  • 简单性:纯组件的逻辑简单,易于测试和维护。

实现

通过继承React.PureComponent来创建纯组件。React.PureComponent默认实现了shouldComponentUpdate方法,该方法会进行浅比较(shallow comparison)来判断props和state是否发生变化。 如果props或state没有变化,组件不会重新渲染。

import React, { PureComponent } from 'react';
 
class PureComp extends PureComponent {
  render() {
    console.log('PureComp rendered');
    return <div>Pure Component</div>;
  }
}
 
export default PureComp;
 

使用场景

  • 展示数据:当组件仅用于展示数据且不涉及复杂的交互逻辑时,纯组件是一个理想的选择。
  • 列表项:在渲染列表项时,纯组件可以避免不必要的渲染,提高性能。
  • 性能敏感的场景:在需要高性能的应用中,纯组件可以帮助减少不必要的渲染。

局限性

  • 复杂交互:纯组件不适合处理复杂的交互逻辑,因为它们不支持副作用。
  • 深比较:React.PureComponent的shouldComponentUpdate方法只进行浅比较,对于嵌套对象或数组的变化可能无法正确检测。

React 中 key 的重要性

特点:

  • 识别唯一的 Virtual DOM 元素:当列表项的顺序发生变化时,React 使用 key 来识别哪些项需要重新渲染,哪些项可以复用。
  • 驱动 UI 的数据识别:当列表项的 key 发生变化时,React 会重新渲染对应的DOM元素。
  • 提高渲染性能:通过 key,React 可以快速识别哪些项已经添加、删除或移动,从而只对这些项进行DOM操作,而不是对整个列表进行重新渲染。
  • 列表项的唯一性:避免使用数组索引作为 key,因为索引在列表项重新排序时会发生变化,导致性能问题。
import React from 'react';
 
const ListComponent = ({ items }) => {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};
 
export default ListComponent;
 

在 React 组件中更新嵌套状态

您在 React 组件中有一个深度嵌套的状态对象,它表示用户信息,包括用户的地址。您的任务是在单击按钮时更新地址对象中的城市属性。 挑战在于确保在状态中仅更新城市值,而不直接改变原始状态。单击“更新城市”按钮后,UI 应反映新的城市值。 具体任务:单击“更新城市”按钮时,将城市值更新为“旧金山”,并确保此更改立即反映在 UI 中。

为了正确更新城市属性而不改变原始状态,您应该使用扩展运算符创建需要更新的状态的每个级别的浅表副本。

  • 初始化状态:使用useState钩子初始化一个嵌套状态对象,包含用户信息和地址信息。
  • 更新城市属性:
    • 定义updateCity函数,使用扩展运算符(...)创建状态对象的副本,逐步更新嵌套对象。
    • 通过setState函数更新状态,确保不可变性。
  • 渲染UI:
    • 返回一个包含用户信息和一个按钮的div元素。
    • 按钮的onClick事件绑定到updateCity函数。
 
import React, { useState } from 'react';
 
const NestedStateComponent = () => {
  // 初始化状态
  const [state, setState] = useState({
    user: {
      name: 'John Doe',
      address: {
        city: 'New York',
        zip: '10001'
      }
    }
  });
 
  // 更新城市属性的函数
  const updateCity = () => {
    // 使用扩展运算符创建状态的副本
    setState(prevState => ({
      ...prevState,
      user: {
        ...prevState.user,
        address: {
          ...prevState.user.address,
          city: 'San Francisco'
        }
      }
    }));
  };
 
  return (
    <div>
      <h1>{state.user.name}</h1>
      <h2>{state.user.address.city}</h2>
      <button onClick={updateCity}>Update City</button>
    </div>
  );
};
 
export default NestedStateComponent;
 

防止子组件不必要的重新渲染

在React中,当父组件重新渲染时,所有子组件也会重新渲染,即使它们的props没有发生变化。这会导致性能问题,尤其是在子组件渲染开销较大的情况下。

解决方案

使用 React.memo 来记忆子组件的渲染输出。React.memo 是一个高阶组件,它会比较组件的props,如果props没有变化,就不会重新渲染组件。

  • 初始渲染:当父组件首次渲染时,ChildComponent 会渲染一次,控制台输出 ChildComponent rendered。
  • 点击按钮:每次点击“Increment Count”按钮时,父组件的 count 状态更新,父组件会重新渲染。但由于 data prop 没有变化,ChildComponent 不会重新渲染,控制台不会再次输出 ChildComponent rendered。
import React, { useState, memo } from 'react';
 
// 使用 React.memo 包装 ChildComponent
const ChildComponent = memo(({ data }) => {
  console.log('ChildComponent rendered');
  return <div>{data.name}</div>;
});
 
const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [data, setData] = useState({ name: 'John' });
 
  const incrementCount = () => {
    setCount(count + 1);
  };
 
  return (
    <div>
      <button onClick={incrementCount}>Increment Count</button>
      <p>Count: {count}</p>
      <ChildComponent data={data} />
    </div>
  );
};
 
export default ParentComponent;
 
  • React.memo:React.memo 是一个高阶组件,它会记忆组件的渲染输出。如果传递给组件的props没有变化,React.memo 会阻止组件重新渲染。
  • 性能优化:通过使用 React.memo,只有当 data prop 发生变化时,ChildComponent 才会重新渲染。这样可以避免不必要的渲染,提高性能。

防止父组件状态导致子重新渲染

在React中,当父组件重新渲染时,传递给子组件的函数 prop 会被重新创建,导致子组件认为 prop 发生了变化,从而重新渲染。

这会导致性能问题,尤其是在子组件渲染开销较大的情况下。

解决方案

使用 useCallback 来记忆事件处理函数,确保函数的引用在父组件重新渲染时保持不变。结合 React.memo,可以防止子组件不必要的重新渲染。

// 使用 useCallback 和 React.memo 来优化子组件渲染的代码示例:
import React, { useState, useCallback, memo } from 'react';
 
// 使用 React.memo 包装 ChildComponent
const ChildComponent = memo(({ onClick }) => {
  console.log('ChildComponent rendered');
  return <button onClick={onClick}>Child Button</button>;
});
 
const ParentComponent = () => {
  const [count, setCount] = useState(0);
 
  // 使用 useCallback 记忆 handleClick 函数
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // 依赖数组为空,确保函数只创建一次
 
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <p>Count: {count}</p>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};
 
export default ParentComponent;
 
 

解释

  • useCallback:useCallback 是一个 React Hook,它会记忆函数,确保在父组件重新渲染时,函数的引用保持不变。这可以防止子组件因为函数 prop 的引用变化而重新渲染。
  • React.memo:React.memo 是一个高阶组件,它会记忆组件的渲染输出。如果传递给组件的 props 没有变化,React.memo 会阻止组件重新渲染。
  • 性能优化:通过结合使用 useCallback 和 React.memo,可以确保子组件仅在实际 props 发生变化时重新渲染,从而避免不必要的渲染,提高性能。

验证

  • 初始渲染:当父组件首次渲染时,ChildComponent 会渲染一次,控制台输出 ChildComponent rendered。
  • 点击按钮:每次点击“Increment Count”按钮时,父组件的 count 状态更新,父组件会重新渲染。但由于 handleClick 函数的引用没有变化,ChildComponent 不会重新渲染,控制台不会再次输出 ChildComponent rendered。
  • 点击子组件按钮:点击“Child Button”时,控制台输出 Button clicked,验证事件处理函数的正确性。

useState 是同步还是异步的?

结论

在 React 中,useState 的更新操作是异步的。 当你调用 setCount(count + 1) 时,React 并不会立即更新 count 的值,而是将这个更新操作放入一个批量更新队列中。 等到 React 处理完所有当前事件的更新操作后,才会批量执行这些更新,并重新渲染组件。

function Counter() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // 仍然是旧值
  };
 
  return <button onClick={handleClick}>Click me: {count}</button>;
}
 
  • setCount(count + 1) 触发了状态更新,但不会立即修改 count。
  • console.log(count) 仍然会打印旧值,因为 count 还没有被更新。

工作机制

  • 非真正异步‌:useState 的更新行为并非传统意义上的异步(如 Promise 或 setTimeout),而是 React 的「批量更新策略(Batching)」和「调度机制(Scheduler)」共同作用的结果。
  • ‌同步执行,异步生效‌:setState 调用本身是同步的,但状态的更新会被 React 加入「更新队列」,在 React 的调度周期结束后才会生效。

强制同步更新

绕过批处理的强制同步更新。

// React 18+ 使用 flushSync 强制同步更新
import { flushSync } from 'react-dom';
 
const handleClick = () => {
  flushSync(() => {
    setCount(count + 1); // 立即更新
  });
  console.log(count);    // 可获取新值
};
 

这种方式确保了 prevCount 是最新的值,即使在多次调用 setCount 时也能正确地获取到最新的状态。

获取新值

1、使用函数式更新 当你需要基于当前的 state 值进行更新时,使用函数式更新:

 
setCount(prevCount => prevCount + 1);
 

2、避免直接依赖 state 的值 在调用 setState 后,不要依赖 state 的值,因为它是异步更新的。如果需要处理更新后的值,可以使用 useEffect 或其他生命周期方法。

设计哲学

React 为了优化性能,会合并多个 setState 调用,减少不必要的渲染。 如果 useState 是同步的,每次 setState 都会导致组件重新渲染,这会显著影响性能。

useState 的源码实现?

useState 的本质

在 React 内部,「useState 实际上是对 useReducer 的封装」。其基本实现类似于下面这样:

function useState(initialState) {
  return useReducer(basicStateReducer, initialState);
}
 
function basicStateReducer(state, action) {
  // 如果传入的是函数,则调用它获得下一个 state,否则直接返回 action
  return typeof action === 'function' ? action(state) : action;
}
 

这段代码展示了 useState 的精髓:「每次状态更新时,你可以传入新的状态值,也可以传入一个函数(接收当前状态返回新的状态)。」

内部 Hook 的数据结构

React 为每个组件维护一个与之对应的 「hook 链表」(挂载在 Fiber 对象上)。

// ReactFiberHooks.js
function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}
 
function useState(initialState) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}
 
function useReducer(reducer, initialArg, init) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}
 
 

每个 hook 保存了自己的状态值和一个更新队列。当你调用 useState 时,React 会在内部执行类似如下的逻辑:

挂载阶段(组件初次渲染时)

在组件初次挂载时,会调用 mountState,其大致实现如下:

 
function mountState(initialState) {
  // 创建一个新的 hook,并挂载到当前正在渲染的 fiber 上
  const hook = mountWorkInProgressHook();
 
  // 如果 initialState 为函数,则调用它以获得初始状态
  hook.memoizedState = typeof initialState === 'function' ? initialState() : initialState;
  
  // 初始化更新队列
  hook.queue = {
    pending: null,   // 待处理的更新(以循环链表存储)
    dispatch: null,  // 更新函数
    last: null,      // 指向最后一个更新
  };
 
  // 创建并绑定 dispatch 函数,后续用于添加更新
  const dispatch = hook.queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber, hook.queue);
 
  return [hook.memoizedState, dispatch];
}

这里主要做了两件事:

  • 保存初始状态:(memoizedState)。
  • 创建一个更新队列:这个队列用于收集后续的 state 更新。

更新阶段(组件重新渲染时)

当组件重新渲染时,会调用 updateState,大致逻辑如下:

 
function updateState(initialState) {
  // 找到对应的 hook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  const pending = queue.pending;
 
  if (pending !== null) {
    let newState = hook.memoizedState;
    // pending 是一个循环链表,依次应用每个更新
    let first = pending.next;
    let update = first;
    do {
      const action = update.action;
      // 通过 reducer 计算下一个 state
      newState = typeof action === 'function' ? action(newState) : action;
      update = update.next;
    } while (update !== first);
 
    hook.memoizedState = newState;
    // 清空更新队列
    queue.pending = null;
  }
 
  return [hook.memoizedState, queue.dispatch];
}
 

这部分代码做了以下工作:

  • 获取当前状态:从 hook 的 memoizedState 获取当前状态。
  • 遍历更新队列:从 pending 开始,依次处理每个更新操作。
  • 应用更新:根据更新操作的类型(值或函数),计算新的状态。
  • 更新状态:将新的状态存储回 memoizedState。
  • 清空更新队列:将 pending 设置为 null,表示所有更新已处理完毕。

更新调度与批量处理

每次你调用 setState(也就是 dispatch 函数),React 并不会立刻同步更新 state,而是会将更新加入到 hook 的队列中,并等待合适的时机进行「批量处理」。这种设计可以优化性能,避免重复渲染。

在事件处理函数中,React 会批量合并多次更新,然后在下一次渲染时一次性应用;而在某些异步回调(如 setTimeout、Promise.then)中,React 18 之后也会自动批处理更新,这使得行为更趋一致。

小结

  • 核心原理:useState 是对 useReducer 的封装,内部采用了基本的 reducer 模式(basicStateReducer)。
  • 数据结构:每个 hook 记录了 state 和一个循环链表形式的更新队列。
  • 更新流程:调用 setState 时,将更新添加到队列中;渲染时遍历队列计算新的 state。

其他的Hook

通用基础架构

Hook 链表存储

所有 Hooks 在 Fiber 节点上以 ‌单向链表‌ 形式存储,每个 Hook 包含:

type Hook = {
  memoizedState: any,        // 当前状态值(useState的值、useEffect的依赖等)
  baseState: any,            // 基础状态(用于优先级中断恢复)
  baseQueue: Update<any> | null, // 未处理的更新队列
  queue: UpdateQueue<any> | null, // 更新队列(useState/useReducer专用)
  next: Hook | null,          // 指向下一个 Hook
};
 

‌双阶段处理‌

每个 Hook 在 ‌mount 阶段‌ 和 ‌update 阶段‌ 有独立处理逻辑:

// 伪代码示例:处理组件函数
function renderWithHooks(Component, props) {
  if (currentFiber.memoizedState === null) {
    // Mount 阶段
    dispatcher = HooksDispatcherOnMount;
  } else {
    // Update 阶段
    dispatcher = HooksDispatcherOnUpdate;
  }
  
  ReactCurrentDispatcher.current = dispatcher;
  const children = Component(props);
  // ...处理副作用链表
  return children;
}
 

useReducer

原理

useReducer 是 useState 的一种扩展,它允许你使用 reducer(纯函数)来根据前一个状态和 action 计算下一个状态。实际上,useState 内部就是调用了 useReducer(使用一个简单的状态更新函数)。

实现机制

挂载阶段

  • React 为该 hook 分配一个 hook 对象,将初始状态存入其中。
  • 初始化一个更新队列,用于存储后续的更新操作。

调用 dispatch 时:

  • 将 action 插入到该 hook 的更新队列(通常以循环链表的形式)。

更新阶段:

  • React 会遍历队列,依次调用 reducer 函数计算出新的状态。
  • 最后更新 hook 对象中的状态值。

useEffect 和 useLayoutEffect

这两个 hook 用来处理副作用。它们不会直接存储业务状态,而是在 Fiber 上记录 effect 描述信息。

  • useEffect:回调会在浏览器绘制之后异步执行。
  • useLayoutEffect:回调会在所有 DOM 变更完成后、浏览器绘制之前同步执行。

实现机制

渲染阶段:

  • 每次调用会将 effect 信息(包括回调函数、依赖数组等)添加到一个 effect 列表中。

commit 阶段:

  • 根据 effect 的类型,React 会依次调用这些回调函数,同时处理卸载和更新逻辑。

实际应用

这使得它适用于大多数不需要立即反映到 UI 上的副作用场景。

在组件加载时获取数据

useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    setData(data);
  };
  fetchData();
}, []);
 

订阅外部数据源,如事件或 WebSockets

useEffect(() => {
  const subscription = fetchData().subscribe(data => {
    // 异步处理数据
  });
  return () => subscription.unsubscribe(); // 清理副作用
}, []);
 

useMemo 和 useCallback

原理

这两个 hook 都用于性能优化:

  • useMemo:用来记忆计算结果,避免在每次渲染时都执行高代价的计算。
  • useCallback:实际上是对 useMemo 的语法糖,用来记忆函数引用。

实现机制

  • 渲染阶段:React 会保存上一次计算的值和依赖数组到对应的 hook 对象中(存储在 Fiber 的 hook 链表上)。
  • 更新阶段:当组件重新渲染时,会对比当前依赖数组和上一次的依赖数组,只有在依赖发生变化时才会重新计算并存储新值,否则复用旧值。

useRef

原理

useRef 用于保存一个可变对象(通常为 { current: ... }),该对象在组件的整个生命周期内保持不变。更新 ref 的值不会触发重新渲染。

实现机制

  • 挂载阶段:React 创建一个对象并存储在 hook 对象中。
  • 返回的对象:在后续渲染中保持同一引用,允许直接修改 .current 属性来存储数据。

应用

访问 DOM 元素

最常见的场景是存储对 DOM 元素的引用:

 
const inputRef = useRef(null);
const focusInput = () => {
    if (inputRef.current) {
        inputRef.current.focus();
    }
};
return (
    <div>
        <input ref={inputRef} type="text" />
        <button onClick={focusInput}>Focus Input</button>
    </div>
);
 

保留组件状态

可以存储不想触发重新渲染的值。例如,存储定时器的 ID,或者在组件的不同生命周期中保留某些状态

 
const countRef = useRef(0);
const handleIncrement = () => {
    countRef.current += 1;
    console.log(countRef.current);
};
 

useContext

原理

useContext 用于订阅 React 上下文(Context),当 Context 的值发生变化时,依赖它的组件会重新渲染。

是 React Hooks 中的一个重要 Hook,用于在组件树中共享数据,而无需通过每一层手动传递 props。这使得它在全局状态管理、主题切换、应用配置等方面非常有用

实现机制

  • 渲染阶段:useContext 会读取当前 Context 的值(通常来自最近的 Provider)。
  • 内部机制:依赖于 React 的调度机制,当 Provider 更新时,依赖该 Context 的组件会被标记为需要更新。

实际应用

全局状态管理

useContext 常用于管理全局状态,如用户认证信息、语言设置等

import React, { createContext, useContext, useState } from 'react';
 
const AuthContext = createContext();
 
function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
 
  const handleLogin = () => {
    setIsLoggedIn(true);
  };
 
  const handleLogout = () => {
    setIsLoggedIn(false);
  };
 
  return (
    <AuthContext.Provider value={{ isLoggedIn, handleLogin, handleLogout }}>
      <LoginComponent />
      <DashboardComponent />
    </AuthContext.Provider>
  );
}
 
function LoginComponent() {
  const { handleLogin } = useContext(AuthContext);
  // 显示登录表单,登录后调用 handleLogin
}
 
function DashboardComponent() {
  const { isLoggedIn } = useContext(AuthContext);
  // 显示仪表板内容,如果用户未登录则显示登录提示
}
 

主题切换

useContext 也适用于主题切换,如字体大小、颜色等

 
import React, { createContext, useContext, useState } from 'react';
 
const ThemeContext = createContext('light');
 
function App() {
  const [theme, setTheme] = useState('light');
 
  return (
    <ThemeContext.Provider value={theme}>
      <ThemeDisplay />
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </ThemeContext.Provider>
  );
}
 
function ThemeDisplay() {
  const theme = useContext(ThemeContext);
  return <div>Current Theme: {theme}</div>;
}
 

应用级别的配置

useContext 可以用于存储应用级别的配置,如 API 端点、API 密钥等

 
import React, { createContext, useContext } from 'react';
 
const AppConfigContext = createContext({
  apiUrl: 'https://api.example.com',
  apiKey: 'your-api-key',
});
 
function App() {
  return (
    <AppConfigContext.Provider value={{ apiUrl: 'https://api.example.com', apiKey: 'your-api-key' }}>
      <ApiComponent />
    </AppConfigContext.Provider>
  );
}
 
function ApiComponent() {
  const { apiUrl, apiKey } = useContext(AppConfigContext);
  // 使用 apiUrl 和 apiKey 进行 API 调用
}
 

useImperativeHandle

原理

这个 hook 用来在使用 ref 时自定义暴露给父组件的实例值。它通常和 forwardRef 配合使用。

实现机制

  • 渲染阶段:根据传入的工厂函数和依赖数组计算出一个对象,并将这个对象赋值给对应的 ref。
  • 更新阶段:当依赖变化时,React 会更新该对象,确保父组件拿到的是最新的引用。

useDebugValue

原理

useDebugValue 主要用于开发阶段,在 React DevTools 中显示自定义的调试信息,对组件逻辑没有实际影响。

实现机制

  • 开发环境下:React 会将传入的调试值记录在 hook 对象中,供调试工具读取。
  • 生产环境下:通常会被优化掉,不产生任何额外开销。

参考文档:

16、什么是React 路由?

https://mp.weixin.qq.com/s/oankkt_8SmX-6wuP9K3IhQ

https://mp.weixin.qq.com/s/5SNI84Z2ciqBc14OrZIuyw

https://mp.weixin.qq.com/s/7ZR4BP3I8NKo3m4nqLXzEA

← Previous postPython中的Closure闭包
Next post →Vue夯实基础