React 基础
简而言之,React的心智模型有如下几个核心点:
-
声明式UI:使用
JSX语法来对UI界面进行声明,更加关注页面UI最终的呈现效果,而无需过分关注交互过程中引起的每一处视图更新的具体实现 -
组件系统:
UI是由独立、可复用、可组合的组件构成的,每个组件有如下特点:-
独立 (Independent):组件应该遵循“高内聚,低耦合”的原则。一个设计良好的组件应该拥有明确的边界和职责,可以相对独立地被理解、开发和测试。
-
可复用 (Reusable):这是组件化的主要目标之一。通过将通用的UI和逻辑封装成组件(如
Button,Modal,InputField),我们可以在项目的不同地方甚至不同项目中重复使用它们,极大地提高了开发效率和一致性。 -
可组合 (Composable):简单的组件可以被当作“积木”来组合成更复杂的组件,进而构建出整个页面和应用。这种树形结构的组合方式是
React应用构建的基础,非常灵活和强大。 -
内聚 (Cohesive):在一个组件内部,
UI(JSX)、逻辑(事件处理、副作用)和状态(useState,useReducer)是紧密关联、封装在一起的。它们共同完成了该组件的一个特定功能。这种内聚性使得代码更易于维护和理解,因为所有相关的代码都集中在同一个地方。
-
-
“UI 是状态的函数”:
UI = f(state) -
函数式编程:
-
纯组件与纯函数(Pure Components & Pure Functions):理想中的React组件被设计为纯函数。给定相同的
props和state,组件总是应该返回相同的JSX输出(即相同的渲染结果)。这种特性使得组件的行为非常可预测,易于测试和调试。 -
不可变性(Immutability):这是函数式编程的核心原则,也是
React状态更新的黄金法则。状态(State)和属性(Props)绝不应该被直接修改(mutate)。更新状态时,必须使用setState或useState的setter函数创建一个新的对象或数组来代替旧的状态。 通过不可变性可以避免直接修改数据带来的副作用(Side Effects),使得状态变化可追踪、可预测,并极大地简化复杂状态变化的调试(例如,可以很容易地比较新旧状态对象)。 -
副作用与副作用隔离(Side Effects & Effect Hook):函数式编程追求纯函数,但数据获取、订阅、手动操作 DOM 等“副作用”在 UI 开发中是不可避免的。
React通过useEffectHook来明确地管理和隔离这些副作用,而不是将它们混入组件的核心渲染逻辑中。这保持了渲染逻辑的纯净性。 -
高阶函数与组合(Higher-Order Functions & Composition):React 推崇组合(Composition) 而非继承(Inheritance)。通过组合组件来构建UI,这本身就是一种函数式思想。自定义Hooks是这一思想的完美体现:它允许你将组件逻辑提取到可重用的函数中,这些函数本身就是纯函数或是对副作用的封装,实现了逻辑和表现的分离。
-
深入理解了这些要点后,使用React写出优质的代码是非常轻松的。
组件系统是开发React的最核心部分,本章节主要介绍类组件/函数组件的一些核心特性和思想。
类组件在新版本的React中已不再推荐使用,但是这里还是记录一下相关的功能特性。
生命周期 LifeCycle
Section titled “生命周期 LifeCycle”类组件的生命周期
Section titled “类组件的生命周期”React中,仅有类组件存在所谓的生命周期。
React的生命周期可分为三个阶段:挂载、更新和卸载。
-
挂载:
constructor、getDerivedStateFromProps、render、componentDidMount -
更新:
getDerivedStateFromProps、shouldComponentUpdate、render、getSnapshotBeforeUpdate、componentDidUpdate -
卸载:
componentWillUnmount
流程图示:

关于上面提及的生命周期方法,值得注意的有以下几个点:
-
constructor中的super:在
constructor中使用super后,constructor中才能访问到this对象,如:constructor内部调用super():class ComponentA extends React.Component {constructor(props) {super();console.log(props); // {}console.log(this.props); // undefined}}constructor内部调用super(props):class ComponentA extends React.Component {constructor(props) {super(props);console.log(props); // {}console.log(this.props); // {}}}因为组件作为子类,没有自己的
this对象,是需要继承父类的this的。虽然React在后面会帮我们完成这一过程(如在render中使用this.props),但在constructor阶段该过程并没有被执行,所以在调用super之前,都是拿不到this对象的。 -
关于
getDerivedStateFromProps这个方法的执行时机是组件的创建和更新阶段。在调用
render方法之前,该方法会被调用。传入的参数有两个:第一个参数为nextProps,即变化后的props;第二个参数为preState,即改变前的state。一般我们会使用
getDerivedStateFromProps来让组件在props变化时更新state。 -
关于
shouldComponentUpdate组件在每次触发更新前,都会调用
shouldComponentUpdate。这个方法有两个参数,第一个参数为nextProps,第二个参数为nextState。方法的返回值为布尔类型,标识是否继续渲染。我们可以根据这个方法的两个参数的变化情况来对渲染进行控制,从而达到减少渲染,优化性能的目的。 -
关于
getSnapshotBeforeUpdate这个方法并不是很常用到,它在
render之后,dom改变之前执行,其返回值作为componentDidUpdate的第三个参数被接受,可以在组件更新前将组件的一些 UI 状态如滚动位置传给componentDidUpdate,从而实现组件 UI 状态的恢复。
面试题:useEffect模拟实现生命周期
Section titled “面试题:useEffect模拟实现生命周期”函数组件中没有LifeCycle的概念,不过我们可以根据React提供的一些hooks来模拟一部分的生命周期。
首先看一下useEffect的使用:
useEffect(() => { // 依赖数组中依赖项变化时执行的逻辑 return () => { // 组件销毁时调用的逻辑 };}, dep);当dep为空时,useEffect仅会被执行一次,因此可以利用空依赖数组来模拟componentDidMount。相应的,componentWillUnmount也可以通过这种方式模拟,对应的逻辑写在useEffect第一个参数函数的返回值函数中。
如果我们在函数组件中想要监听props变化,也可以使用useEffect。我们只需将props注入依赖数组即可,这样可以在props变化时对组件内部维护的数据状态进行更新。
当useEffect中只传第一个参数时,在每次的函数组件执行的时候都会调用一次,可以利用这个特性来模拟componentDidUpdate。
数据状态:State和Props
Section titled “数据状态:State和Props”State和Props组件内部UI渲染可能依赖的两部分数据。State由组件自身维护,而Props是组件外部传入的数据。
在类组件中,state通过setState改变;在函数组件中则使用useState进行组件内部状态的创建和维护。props数据由父组件传入子组件,这点不管是类组件还是函数组件都是一样的。
关于类组件的setState,有一些值得注意的点:
-
setState有两个参数,第一个可以是对象,也可以是函数,用于状态的更新;第二个是更新完状态后的回调。 -
根据
setState第一个参数的类型不同,相应的处理逻辑也有区别。-
如果第一个参数为函数类型,如:
this.setState((state, props) => {return { name: "XiaoMing" };});传入函数的参数为当前的
state和props,setState内部会调用传入调用函数,返回值会和旧的state合并后再进行state的更新。 -
如果第一个参数为对象类型,如:
this.setState({name: "XiaoMing",});传入的对象将会和旧的
state合并后进行state的更新。
-
-
setState的批量更新机制:例:类组件中的一个方法有如下的更新代码:
//假设 this.state.number 为 1this.setState({number: this.state.number + 1,},() => {console.log(this.state.number);});console.log(this.state.number);this.setState({number: this.state.number + 1,},() => {console.log(this.state.number);});console.log(this.state.number);this.setState({number: this.state.number + 1,},() => {console.log(this.state.number);});console.log(this.state.number);// 结果: 0 0 0 1 1 1可以看到,虽然调用的多次
setState,最后state中的number值还是为 1。这是因为React中存在State批量更新的规则,几次对state的更新被合并为 1 次。注意,批量更新的规则在异步的代码如
Promise、setTimeout中不管用,如果要在这些异步代码中使用批量更新,则需手动调用ReactDOM提供的unstable_batchedUpdates方法。另外,在这个机制下可以使用
flushSync提升更新的优先级,当一个对state的更新在flushSync内部调用,这个更新的优先级是比较高的。如:this.setState({ a: "xiaohong" });ReactDOM.flushSync(() => {this.setState({ a: "xiaoming" });});this.setState({ a: "xiaohuang" });执行上面代码,
state会有两次更新,第一次是触发了flushSync,立马执行更新,之前的setState会被合并,a变为xiaoming;第二次是其他两行代码出发的更新,根据批量更新的规则,a变为xiaohuang。
关于state和props,还有一个经常被提及的概念:受控组件和非受控组件。其实很好理解,其区别在于控制组件中渲染数据的主体是哪个。如果组件渲染数据取决于其自身维护的状态,那就是非受控组件;如果组件渲染依赖于外部数据如props里的数据,即为受控组件。理解完这些后,啥时候该用,啥时候不该用心里也有数了。
React的组件通信方式大致有下面几种:
-
props+callback -
ref -
状态管理(
redux、mobx) -
eventbus -
context
props + callback
Section titled “props + callback”props + callback比较常用于父子组件通信,父组件通过props向子组件传值,子组件中也可以通过调用父组件传入的callback(实际上也是props的数据)来反馈数据给父组件。如:
function Father() { return <Son callback={(e) => { console.log(e) }/>}function Son({ callback }) { return <button onClick={ callback }></button>}ref即reference的缩写,任何需要被引用的数据都可以保存在ref中,包括组件实例。
创建组件实例ref的形式有三种:
-
(类组件)传入字符串:使用时通过
this.refs.xxx的格式获取对应的元素(已不推荐使用) -
(类组件)传入对象:传入
ref的对象是使用React.createRef()方式创建出来,使用时获取到创建的对象中存在current属性即为对应的元素,如:class MyComponent extends React.Component {constructor(props) {super(props);this.myRef = React.createRef();}render() {return <div ref={this.myRef} />;}} -
(类组件)传入函数,该函数会在
DOM被挂载时进行回调,这个函数会传入一个元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可。如:class MyComponent extends React.Component {constructor(props) {super(props);this.myRef = React.createRef();}render() {return <div ref={(element) => (this.myref = element)} />;}} -
(函数组件)使用
useRef():函数组件使用ref的方式,如:function App(props) {const myref = useRef();return (<><div ref={myref}></div></>);}对于函数组件而言,使用
ref时,要配合forwardRef和useImperativeHandle一起用。用法见下面的代码:Son.jsx const { useState, useRef, forwardRef, useImperativeHandle } = React;const Son = forwardRef((props, ref) => {const [message, setMessage] = useState("");useImperativeHandle(ref, () => {return {setMessage,};});return (<div class="son"><h1>子组件</h1><div>子组件内部状态message:{message}</div></div>);});Father.jsx export default () => {const sonRef = useRef(null);const changeInput = (e) => {sonRef.current.setMessage(e.target.value);};return (<div class="father"><h1>父组件</h1><span>父组件input: </span><input class="input" onChange={changeInput} /><Son ref={sonRef} /></div>);};index.css .father,.input {background: white;color: black;}.father {padding: 20px;}.son {margin-top: 20px;border: 1px solid black;padding: 10px;}
对于状态共享比较复杂的系统,可以使用状态管理工具如:redux、mobx、zustand等)。
Context
Section titled “Context”React的Context是一种在组件树中共享数据的方法,可以避免通过props层层传递数据的繁琐操作。通过Context,我们可以将某个组件下需要共享的数据传递给所有子组件,无论它们在组件树中的哪个位置,而不需要一层层地传递属性。
类组件使用Context传值示例:
const ThemeContext = React.createContext(null);const ThemeProvider = ThemeContext.Provider; //提供者const ThemeConsumer = ThemeContext.Consumer; // 订阅消费者// 类组件 - contextType 方式class ConsumerDemo extends React.Component { render() { const { color, background } = this.context; return <div style={{ color, background }}>消费者</div>; }}ConsumerDemo.contextType = ThemeContext;
const Son = () => <ConsumerDemo />;
export default function ProviderDemo() { const [contextValue, setContextValue] = React.useState({ color: "#ccc", background: "pink", }); return ( <div> <ThemeProvider value={contextValue}> <Son /> </ThemeProvider> </div> );}函数组件使用Context传值示例:
const { useState, useRef, useContext, createContext } = React;
const Context = createContext();
const Son = (props) => { const value = useContext(Context); return ( <div class="son"> <h1>子组件{props.index || 0}</h1> <div>{value}</div> </div> );};
export default () => { const [message, setMessage] = useState("");
return ( <Context.Provider value={message}> <div class="father"> <h1>父组件</h1> <span>输入数据:</span> <input class="input" onChange={(e) => setMessage(e.target.value)} />
<Son index="1" /> <Son index="2" /> </div> </Context.Provider> );};组件逻辑复用:高阶组件 HOC
Section titled “组件逻辑复用:高阶组件 HOC”HOC(Higher-Order Component)是指一个函数,接受一个组件作为参数并返回一个新的组件。HOC可以在不修改现有组件代码的情况下添加新的功能,例如可以将逻辑代码和状态管理代码从组件中抽离出来,提高代码复用性。
下面是一个简单的例子:
function withWindowSize(Component) { return class extends React.Component { state = { width: window.innerWidth, height: window.innerHeight }; handleResize = () => { this.setState({ width: window.innerWidth, height: window.innerHeight }); }; componentDidMount() { window.addEventListener('resize', this.handleResize); } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); } render() { return <Component windowSize={this.state} {...this.props} />; } };}
// 使用const MyComponent = (props) => { const { windowSize } = props; return <div>Window size: {windowSize.width} x {windowSize.height}</div>;};export default withWindowSize(MyComponent);组件逻辑复用:React hooks
Section titled “组件逻辑复用:React hooks”在没有React Hook之前,函数组件能够做的只是接受Props、渲染UI,以及触发父组件传过来的事件。React Hook让函数组件也能做类组件的事情,有自己的状态,可以处理一些副作用,能获取ref,也能做数据缓存。
从React团队的开发方向来看,虽然其在文档上表明并没有移除class组件的意思,不过也看得出官方更推荐使用函数组件。可见:动机 | Hook | React 中文文档
使用React Hook要留意下面几个点:
-
以
use开头的函数均会被React视作一个Hook(官方提供的linter有这个规则) -
仅能在函数组件或者其他
Hook内部被使用 -
不要在循环,条件或嵌套函数中调用
Hook, 确保总是在你的React函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。
React Hook有如下优点:
-
可以将状态逻辑和
UI逻辑分离,使代码更加清晰易懂。 -
可以将相同的逻辑应用于多个组件,从而实现代码的复用。
-
可以使用自定义
hooks,将复杂的逻辑抽象出来,使代码更加模块化。
函数组件:常用hooks
Section titled “函数组件:常用hooks”状态类的hooks有两个:useState、useReducer、useContext。
useState
Section titled “useState”useState可以让函数组件具有状态(state)的能力。它接收一个初始值作为参数,返回一个包含state和更新state的函数的数组。每次调用更新函数时,React会重新渲染组件,并重新计算组件的UI。
一个简单的调用示例如下:
const [state, setState] = useState(initialState);其中,state表示当前状态的值,initialState表示初始值。setState是一个更新state的函数,它可以接收一个新的值,并触发组件的重新渲染。
useState的特点包括:
-
可以在函数组件中添加状态,避免了使用类组件的繁琐;
-
可以多次使用
useState,每个state之间是独立的; -
每次修改
state都会重新渲染组件,但React会尽可能地优化性能,避免不必要的渲染; -
更新
state可以使用函数式更新,避免了异步更新带来的问题。
useReducer
Section titled “useReducer”useReducer用于管理组件中的状态。它的作用类似于useState,可以用来更新组件中的状态。但是,相较于useState,useReducer更适合处理复杂的状态逻辑,例如多个状态之间的关系、状态的计算逻辑等。
useReducer接受两个参数:reducer函数和初始状态。reducer函数接受两个参数,一个是当前状态,一个是操作指令,根据操作指令对当前状态进行更新,并返回新的状态。通过调用dispatch函数,组件可以将操作指令传递给reducer函数,从而更新状态。
用法示例如下:
const { useReducer } = React;
const initialState = { count: 0,};
function reducer(state, action) { switch (action.type) { case "increment": return { ...state, count: state.count + 1 }; case "decrement": return { ...state, count: state.count - 1 }; default: throw new Error("Invalid action type"); }}
export default () => { const [state, dispatch] = useReducer(reducer, initialState);
return ( <div> <h1>Count: {state.count}</h1> <div style={{ display: "flex", columnGap: 10 }}> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> </div> </div> );};useContext
Section titled “useContext”在前面组件通信的内容中我们了解了React的Context API,useContext的作用就是允许我们在函数组件中消费Context。
useContext接收一个Context对象并返回该Context的当前值。如果我们使用了useContext,则当Context的值更改时,组件将会重新渲染。
例如:
// 1. 创建一个 Context 对象(通常在一个单独的文件中)import { createContext, useContext } from 'react';
const ThemeContext = createContext('light'); // 'light' 是默认值
function App() { // 2. 在顶层组件使用 Provider 提供 value return ( <ThemeContext.Provider value="dark"> {/* 覆盖默认值 */} <Toolbar /> </ThemeContext.Provider> );}
function Toolbar() { // 中间的组件完全不需要传递任何关于 theme 的 props return ( <div> <ThemedButton /> </div> );}
function ThemedButton() { // 3. 在深层子组件中,使用 useContext 消费 value const theme = useContext(ThemeContext); return <button className={theme}>I am styled by theme: {theme}</button>; // 最终,这个按钮的 className 会是 "dark"}useEffect
Section titled “useEffect”useEffect用于在组件渲染完成后执行一些副作用操作,例如异步数据请求、DOM 操作等。
useEffect接受两个参数,第一个参数是一个回调函数,用于定义需要执行的副作用操作,可以在这个函数的返回值指定一个函数,指定的函数会在组件卸载阶段执行;第二个参数是一个数组,用于指定副作用操作依赖的数据,当这些数据发生变化时,useEffect才会重新执行。
使用示例如下:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { // 异步数据获取函数 const fetchUser = async () => { try { setLoading(true); const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); setUser(userData); } catch (err) { setError(err.message); } finally { setLoading(false); } };
fetchUser(); }, [userId]); // 依赖数组:当 userId 变化时重新执行
if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>;
return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> );}useLayoutEffect
Section titled “useLayoutEffect”useLayoutEffect也被用来处理副作用操作,但和useEffect有区别。useEffect被异步调度,当页面渲染完成后再去执行,不会阻塞页面渲染;useLayoutEffect是在commit阶段新的DOM准备完成,但还未渲染到屏幕之前,同步执行。
当useEffect里面的操作需要处理DOM,并且改变页面的样式,建议使用useLayoutEffect,否则可能会出现出现闪屏问题。

使用示例如下:
function Tooltip() { const [height, setHeight] = useState(0); const tooltipRef = useRef(null);
useLayoutEffect(() => { // 同步测量 DOM const { height } = tooltipRef.current.getBoundingClientRect(); setHeight(height); // 同步更新状态,触发同步的重新渲染 // 此时,浏览器还没有绘制,所以用户看不到中间状态 }, []);
// 组件的渲染逻辑可以根据 height 来调整位置 // ... return <div ref={tooltipRef}> {/* ... */} </div>;}useMemo
Section titled “useMemo”useMemo用于缓存计算结果,避免重复计算。useMemo 接收两个参数,第一个参数是计算函数,第二个参数是依赖项数组。只有当依赖项数组中的值发生变化时,计算函数才会重新执行。
使用示例如下:
import React, { useMemo } from 'react';
function ExpensiveComponent({ list, filterTerm }) { // 只有当 `list` 或 `filterTerm` 变化时,才会重新执行昂贵的过滤计算 const filteredList = useMemo(() => { console.log('正在进行昂贵的计算...'); return list.filter(item => item.name.includes(filterTerm)); }, [list, filterTerm]); // 依赖项
return ( <div> {filteredList.map(item => <div key={item.id}>{item.name}</div>)} </div> );}useCallback
Section titled “useCallback”useCallback用于缓存函数,避免重复创建。useCallback 接收两个参数,第一个参数是回调函数,第二个参数是依赖项数组。只有当依赖项数组中的值发生变化时,回调函数才会重新创建。
使用示例如下:
import React, { useCallback, useState } from 'react';// 一个用 React.memo 优化过的子组件,只有当 props 变化时才会重渲染const ChildButton = React.memo(({ onClick, children }) => { console.log('子组件被渲染了:', children); return <button onClick={onClick}>{children}</button>;});
function ParentComponent() { const [count, setCount] = useState(0); const [name, setName] = useState('');
// 没有 useCallback:每次 ParentComponent 渲染,handleIncrement 都是新函数 // const handleIncrement = () => setCount(c => c + 1);
// 有 useCallback:依赖项 [] 不变,handleIncrement 的函数引用始终不变 const handleIncrement = useCallback(() => setCount(c => c + 1), []);
return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <p>Count: {count}</p> {/* 即使 ParentComponent 因为 input 输入而重渲染, handleIncrement 的引用不变,不会导致 ChildButton 不必要的重渲染 */} <ChildButton onClick={handleIncrement}>Increment</ChildButton> </div> );}使用useMemo、useCallback的时候,有时会出现如下的问题:在内部包裹的函数于外部的state形成闭包,而依赖数组却没有包含该值,导致调用缓存的函数时函数中获取到的state值不是最新的。如果依赖数组包含该值,而这个值经常变动,又会失去使用useMemo和useCallback进行性能优化的意义。
解决这个问题可以从两个方面入手:
-
非必要不使用
useMemo和useCallback进行性能优化 -
利用
useRef的特性,把函数放在ref里,如:const App = () => {const [name,setName] = useState("");const fnRef = useRef();fnRef.current = function() {console.log(name)}const onClick = useCallback(() => {fnRef.current();},[])return (<button onClick={onClick}>Click</button>)}
useRef
Section titled “useRef”useRef是 React 中的一个 Hook 函数,它可以创建一个可变的引用对象,一般用于定义组件引用和DOM引用。
useRef返回一个对象,该对象的current属性包含一个变量,可以存储任何值。与useState不同的是,useRef不会引起组件重新渲染。
useRef的主要作用是保存组件中的状态,以供后续的组件使用,也可以用来获取DOM元素和组件实例的引用,后者在实际开发中用的比较多。
下面是一个简单的示例:
const { useRef } = React;
export default () => { const inputRef = useRef(null);
const handleClick = () => { inputRef.current.focus(); };
return ( <div style={{ display: "flex", columnGap: 10 }}> <input type="text" ref={inputRef} /> <button onClick={handleClick}>Focus Input</button> </div> );};useImperativeHandle
Section titled “useImperativeHandle”useImperativeHandle的作用是自定义一个组件实例暴露给其父组件的ref值。它让父组件能够通过ref调用子组件中定义可供外部访问的特定方法或特定的值,而不是直接访问子组件的DOM节点或完整的实例。
用法示例如下:
// React 18 及以前的写法import { forwardRef, useImperativeHandle } from 'react';
const MyInput = forwardRef((props, ref) => { useImperativeHandle(ref, () => ({ // ...暴露的方法 })); return <input />;});// ✅ React 19 的新写法(推荐)function MyInput({ ref, ...props }) { useImperativeHandle(ref, () => ({ // ...暴露的方法 })); return <input />;}
// 旧的 forwardRef 写法依然有效,但不再需要React实现了一套自己的事件机制,称为合成事件(SyntheticEvent)。这是一种跨浏览器的事件包装器,提供了与原生事件相同的接口,但具有更好的兼容性和性能。
-
事件委托:
React将所有事件委托到文档的顶层处理,而不是直接绑定到特定元素 -
跨浏览器兼容:合成事件在所有浏览器中表现一致
-
冒泡机制:
React事件使用标准的冒泡机制,与DOM规范一致
事件执行顺序
Section titled “事件执行顺序”捕获(原生)-> 捕获(合成)-> 冒泡(原生)-> 冒泡(合成)
-
React 16及之前:事件委托到document,所有事件冒泡到document层处理 -
React 17及之后:事件委托到React树的根容器