高阶组件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