react 页面是由各个 react 组件构成的.
基础
传值
各个组件之间传值,向外部传值用 this.props....
向内部传值用 this.state...
在内部保存值用 this.setState
类
类有三个要素.
声明的参数
constructor 构造函数
调用的函数.
React 类
一般调用的是 render()
函数,将内部的 JSX
语法构建到虚拟 DOM 中.从而渲染到页面上.
在定义子类的构造函数时,都要调用 super()
方法.
因此,所有含有构造函数的 React 组件,必须以 super(props)
开头.
组件通信
父传子
子组件通过一个绑定的参数接受来自父组件的 props.父组件把值写在 state 中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Name extends React.Component { state = { lastName = "Tom" } render(){ <div className="parent"> <Child name={this.state.lastName} /> //name={this.state.lastName}作为一个整体传递给子组件,作为props </div> } }
function Child(props) { return <div>父组件的数据: {props.name}</div> }
|
子传父
通过回调函数,父组件提供回调函数,子组件调用,将要传递的数据通过参数传递.
父组件拿到数据进行更新 state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Parent extends React.Component { state = { parentName: '' } const getChildMsg = (data) => { console.log("来自子组件",data) this.setState({ parentName: data }) }
render() { return ( <div> <p>父组件接受: {this.state.parentName}</p> <Child getMsg={this.getChildMsg} /> </div> ) } } class Child extends React.Component { state = { msg: '子组件的信息' }
const handleClick = () => { this.props.getMsg(this.state.msg) }
render() { return ( <div className="child"> <button onClick={this.handleCick}>Click me!</button> </div> ) }
|
父级的方法传递到子级的底层时,进行调用的时候,最好有所关联
filter 方法并没有更新当前的数组而是创建了一个新数组,它会过滤掉不符合条件的数据项,符合条件的数据项就会组成新的数组。
filter
是 JavaScript 中删除数组元素的首选方法。
现在我们需要将removeUser
传递给组件,并且在每一个列表行上渲染一个按钮,点击这个按钮调用这个方法。我们通过属性的方式将removeUser
方法传递给 Table 组件。
TableBody 中,在调用removeItem()
时,将数据对应的索引号(index)
作为参数传递给了removeItem()
方法,这样过滤方法就知道该删除哪个数据项了。创建一个按钮,并在onClick
中调用removeItem()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| function App() { const [users, setUsers] = useState([{ name: "aaa" }]); const removeUser = (index) => { setUsers(users.filter((use, i) => index !== i)); }; return ( <div className="app"> <Table items={users} removeItem={removeUsers} /> </div> ); } function Table(props) { const { items, removeUsers } = props; return ( <table> <TableHeader /> <TableBody items={items} removeItem={removeItem} /> </table> ); } function TableBody(props) { const { items, removeUsers } = props; const rows = items.map((item, index) => ( <tr key={index}> <td>{items.name}</td> <td>{items.job}</td> <td> <button onClick={() => removeUSer(index)}></button> </td> </tr> )); return <tbody>{rows}</tbody>; }
|
注意
组件传值的时候不要直接把传递过来的函数放到组件上,建议另写一个函数调用的时候带上这个props
传递的函数.
下面的handleSubmit
是从props
拿到的,但是又封装一个函数才进行调用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const Form = (props) => { const initalData = { name: "", age: null, }; const [formData, setFormData] = useState(initalData); const handleChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value, }); }; const submitForm = () => { props.handleSubmit(formData); setFormData(initalData); };
return ( <> <form> <label>姓名</label> <input type="text" name="name" value={formData.name} onChange={handleChange} /> <label>年龄</label> <input type="text" name="age" value={formData.age} onChange={handleChange} /> <button onSubmit={submitForm}>提交</button> </form> </> ); };
|
兄弟通信
状态提升
当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。
- 将共享状态提升到最近的公共父组件,由公共父组件管理状态
- 思想: 状态提升
- 公共父组件职责: 提供共享状态,提供操作共享状态的方法
- 要通讯的子组件只需要通过
props
接收或者操作状态的方法
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Parent extends React.Component { state = { count: 0 } const onIncrement = () => { this.setState({ count += 1 }) } render() { return ( <div> <Child1 count={this.state.count} /> <Child2 onIncrement={this.onIncrement} /> </div> ) } }
const Child1 = props => { return ( <div>计数器: {props.count} </div> ) }
const Child2 = props => { return ( <button onClick={() =>props.onIncrement()}>+1</button> ) }
|
命名规范
因为 DOM 元素 是一个内置组件,因此其 onClick 属性在 React 中有特殊的含义。而对于用户自定义的组件来说,命名就可以由用户自己来定义了。我们给 Square 的 onClick 和 Board 的 handleClick 赋予任意的名称,代码依旧有效。在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 on[Event],将处理事件的监听方法命名为 handle[Event] 这样的格式。
函数式组件
将 class 组件修改为函数式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }
|
注意:
onClick={()=>this.props.onClick()}
修改为
onClick={props.onClick}
两侧括号消失
Props
- 可以给组件传任意类型的值
props
是只读的对象,只能读取属性的值,无法修改
- 使用类组件时,如果写了构造函数,应该将
props
传递给super()
,否则无法在构造函数中获取props
props 深入
children 属性
表示组件标签的子节点.当组件标签有子节点时,props 就会有该属性.
children 和普通的 props 属性一样,值可以是任意值.
1 2 3 4 5 6 7 8 9 10 11 12
| const Test = () => <button>子组件</button> const App = () => { return ( <div>{props.children}</div> ) } ReactDOM.render( <App> {Test /> </App>, document.getElementById("root") )
|
props 校验
安装包prop-types
,引入包,
使用组件名.propTypes={}
来给组件的 props 添加校验规则.
校验规则通过PropTypes
对象来指定
1 2 3 4 5 6 7 8 9
| import PropTypes from "prop-types"; function App(props) { return <h1>{props.colors}</h1>; } App.propTypes = { colors: PropTypes.array, };
|
约束规则
- 常见类型: array, bool, func, number, object, string
- React 元素类型: element
- 必填项: isRequired
- 特定结构的对象:
shape({})
1 2 3 4 5 6 7 8 9
| optionalFunc: PropTypes.func,
requiredFunc: PropTypes.func.isRequired,
optionalObjectwithShape: PropTypes.shape({ color: Proptypes.string, fontSize: PropTypes.number })
|
props 默认值
组件.defaultProps
: 给组件传入默认值,在未传入 props 时生效.
State
不要直接修改 state,使用 this.setState()
1 2 3 4 5
| this.state.comment = "hello";
this.setState({ comment: "hello" });
|
构造函数是唯一可以给 this.state
赋值的地方.
setState
setSate 接收一个函数,而函数的参数就是当前 state 的值.变化后记得将新的 state 返回出去.
1
| this.setState((count) => count + 1);
|
state 的更新可能异步
this.props
和 this.state
可能会异步更新,不要依赖他们的值更新下一个状态.
1 2 3 4
| this.setState({ counter: this.state.counter + this.props.increment, });
|
正确解决方法:
让 setState
接收一个函数而不是一个对象.
这个函数用上一个 state
作为参数,将此次更新被应用时的 props
作为第二个参数:
1 2 3 4
| this.setState({ (state, props) => ({ count: state.counter + props.increment }))
|
state 的更新可能会被吞并
当调用 setState
时, React
会把提供的对象合并到当前的 state 中.
setState 是同步还是异步
分为两种时期的两种模式。
React17 之前的 legacy mode,和 react17 之后的 concurrent mode。
1 2 3 4 5
| import ReactDOM from ‘react-dom’; import App from './App';
const rootElement = document.getElementById('root'); ReactDOM.render(<App />,rootElement);
|
Legacy mode: 特点是同步执行。
分成两种情况:
- React 流程的 setState。如生命周期函数,React 事件响应函数。
- React 控制之外的函数。定时器,DOM 事件,Promise。
在 React 流程内,setState 是批量延后执行,也就看成了异步。React 将 setState 的各种更新先不更新,在生命周期的最后阶段才合成为一个对象,批量更新。
1 2 3 4 5 6 7 8
| import ReactDOM from ‘react-dom’; import { crreateRoot } from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root') const root = createRoot(rootElement) root.render(<App />);
|
Concurrent Mode:并发模式。
即将 render 操作拆成一个个小的任务,异步执行。
Context
跨组件通信,也就是不同层级的组件访问相同的数据,比如当前的登录用户,主题,语言.
使用方法
使用React.createContext
创建Provider
和Consumer
两个组件
1
| const { Provider, Consumer } = React.createContext();
|
使用Provider
作为父节点.
1 2 3 4 5
| <Provider> <div className="App"> <Child /> </div> </Provider>
|
使用value
属性表示要传递的数据.
调用Consumer
组件进行接收.Consumer 内部需要一个函数作为子元素.
即 data 接收的就是 value 的值.
1
| <Consumer>{(data) => <span>data参数表示接收到的数据--{data}</span>}</Consumer>
|
关于 Provider 渲染
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。
Class.contextType
挂载在 class 上的 contextType 属性可以赋值为 context 对象,此属性可以让我们通过this.context
来获取 Context 的值.可以在任何生命周期中访问.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class MyClass extends React.Component { componentDidMount() { let value = this.context; } componentDidUpdate() { let value = this.context; } componentWillUnmount() { let value = this.context; } render() { let value = this.context; } } MyClass.contextType = MyContext;
|
额外的
如果想避免层层传递某些属性,也可以使用组件组合()
数据是向下流动的
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
如果把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。
事件处理
不能通过返回 false
阻止事件默认行为,必须显示的使用 preventDefault
.
this 的绑定
将 this 绑定到构造函数上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true, }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState((state) => { isToggleOn: !state.isToggleOn; }); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? "ON" : "OFF"} </button> ); } } ReactDom.render(<Toggle />, document.getElementById("app"));
|
不想 bind
的解决方法.
- 使用箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Toggle extends React.Component { ... handleClick = ()=> { this.setState(state =>{ isToggleOn: !state.isToggleOn })
render() { return ( <button onClick={this.handleClick}> Click me </button> ); } }
|
- 在 render 中使用箭头函数
1 2 3 4 5 6 7 8
| render(){ return ( <button onClick={()=> {this.handleClick()}> Clcik me </button> ) }
|
向事件处理程序传递参数
若 id 是要删除的哪一行
1 2
| <button onClick={(e)=>this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
|
关于 Click 中的箭头函数问题
1 2 3 4 5 6 7
| onClick={这里是一个函数或函数引用} onClick={() => this.handleClick(i)},这里面就是一个匿名函数,
onClick={handleClick()}
|
所以,如果要传递参数时,才需要使用箭头函数,或者绑定 this.
子组件的箭头函数
1 2 3 4 5 6 7 8 9 10 11 12
| <Panel handleClick={handleClick} handleResult={() => handleResult()} />;
const handleClick = (arg: string) => {}; const handleResult = (reverse = options.reverse) => {};
|
条件渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function UserGreeting(props) { return <h1>Welcome back!</h1>; } function GuestGreeting(props) { return <h1>Please sign up</h1>; } function Greeting(props) { const isLoginIn = props.isLoginIn; if (isLoginIn) { return <UserGreeting />; } return <GuestGreeting />; }
ReactDOM.render(<Greeting isLoginIn={false} />, document.getElementBId("root"));
|
列表和 Key
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key.当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key.
和Vue
不同,key
写在返回式中.
map()
使用 map()
进行遍历时,记得 return
.
1 2 3 4 5
| let numbers = [1,2,3] numbers.map(number => <li key={number.toString()}>{number}</li>)
numbers.map(number =>{return <li key={number.toString()}>{number}</li>)
|
表单
暂时空置
状态提升
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
component composition 这种透传数据的模式
引用官网的一句话
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
我们把我们需要用到数据的那个组件直接丢到数据来源的 props 身上 ,然后消费数据,把消费完的组件,也就是要被渲染到页面的内容,通过 props
传回来。这就是 component compositon
,简单粗暴,我们在原来的地方,直接渲染这个组件即可
例如:我们在 Page
组件中需要传递个 Auth
组件 user
信息,它们之间有很多的深层嵌套
我们可以这么做 (官网例子)
1 2 3 4 5 6 7 8 9 10 11
| <Page user={user} avatarSize={avatarSize} /> <PageLayout user={user} avatarSize={avatarSize} /> <NavigationBar user={user} avatarSize={avatarSize} /> <Link href={user.permalink}> <Avatar user={user} size={avatarSize} /> </Link>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Page(props) { const user = props.user; const userLink = ( <Link href={user.permalink}> <Avatar user={user} size={props.avatarSize} /> </Link> ); return <PageLayout userLink={userLink} />; }
<Page user={user} avatarSize={avatarSize} />
<PageLayout userLink={...} />
<NavigationBar userLink={...} />
{props.userLink}
|
这样我们只用传递 userLink
即可,
组合 VS 继承
包含关系
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中.
1 2 3 4 5 6 7
| function FancyBorder(props) { return ( <div className={"FancyBorder FancyBorder-" + props.color}> {props.children} </div> ); }
|
JSX 标签中的所有内容都会作为一个 children
prop 传递给 FancyBorder 组件。因为 FancyBorder 将 {props.children}
渲染在一个
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children
,而是自行约定:将所需内容传入 props
,并使用相应的 prop
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Contacts() { return <div className="Contacts" />; } function Chat() { return <div className="Chat" />; } function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left">{props.left}</div> <div className="SplitPane-right">{props.right}</div> </div> ); }
function App() { return <SplitPane left={<Contacts />} right={<Chat />} />; } ReactDOM.render(<App />, document.getElementById("root"));
|
特例关系
暂无
生命周期
创建时
constructor
=>render()
=>componentDidMount
生命周期 |
触发时机 |
作用 |
constructor |
创建组件 |
1.初始化 state |
2.为事件处理程序绑定 this |
|
|
render |
每次渲染都会触发 |
渲染 UI(注意不能调用 setState) |
componentDIdMount |
组件挂载(完成 DOM 渲染后) |
1.发送网络请求 |
2.DOM 操作 |
|
|
更新时
执行时机: 1.setState
2.forceUpdate()
3.组件接收新的 props
执行顺序: render()
=> componentDidUpdate
钩子函数 |
触发时机 |
作用 |
render |
每次组件渲染都会触发 |
渲染 UI,与挂载阶段是同一个 render |
componentDidUpdate |
组件更新(完成 DOM 渲染后) |
1.发送网络请求 |
2.DOM 操作
注意如果要 setState 必须放在 if 条件中 |
1 2 3 4 5 6 7
| componentDidUpdate(prevProps) { if(prevProps.count !== this.props.count) { this.setState({}) } }
|
卸载时
钩子函数 |
触发时机 |
作用 |
componentWillUnmount |
组件卸载 |
执行清理工作(如卸载定时器) |
1 2 3 4 5 6 7 8
| componentDidMount() { this.timeId = setInterval(() => { console.log("定时器执行") },1000) } componentWillUnmount() { clearInterval(this.timerId) }
|
其他
shouldComponentUpdate
组件
组件复用
要复用什么? 1.state
2.操作state
的方法(组件状态逻辑)
方式: 1.render props
模式 2.HOC
高阶组件 3. 自定义 hooks
render props
思路: 将复用的 state 和操作 state 的方法封装到一个组件
问题 1: 如何拿到该组件中复用的 state?
方法: 在使用组件时,添加一个值为函数的 prop,通过函数参数来获取(需要组件内部实现)
问题 2: 如何渲染任意 UI?
方法: 使用该函数的返回值作为要渲染的内容.(需要组件内部实现)
render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
1 2 3 4
| <Mouse render={(mouse) => {}} /> <Mouse render={(mouse) => ( <p>鼠标当前位置:{mouse.x},{mouse.y}</p> ) }/>
|
具体步骤:
- 创建 Mouse 组件,在组件中提供复用的状态逻辑代码
- 将要复用的状态作为
props.render(state)
方法的参数,暴露到组件外部
- 使用
props.render()
的返回值作为要渲染的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Mouse extends React.Component { state = { x = 0, y = 0 } handleMouseMove = e => { this.setState({ x: e.clientX, y: clientY }) } componentDidMount() { window.addEventListener('mousemove', this.handleMouseMove) } render() { return this.props.render(this.state) } }
class App extends React.Component {
render() { return ( <div> <Mouse render={(mouse) => { return ( <p>鼠标位置: {mouse.x}, {mouse.y}</p> ) }} /> <Mouse render={(mouse) => { return ( <img src={img} alt="cat" style={{ postion: 'absolute', top: mouse.y, left: mouse.x }} </div> ) } }
|
使用 children 替代 render
1 2 3 4 5 6 7
| <Mouse> { {(x,y)} => <p>鼠标的位置: {x},{y}</p> } </Mouse>
render(){ return this.props.children(this.state) }
|
代码优化
- 使用 propTypes 进行校验
- 将事件绑定解除
1 2 3 4 5 6
| Mouse.propTypes = { children: PropTypes.func.isRequired } componentWillUnmount() { window.removeEventListener('mousemove',this.handleMouseMove) }
|
HOC 高阶组件
目的: 实现逻辑复用
方法: 采用包装(装饰)模式
思路: 实际上是一个函数,接收要包装的组件,返回增强后的组件
方法: 内部创建一个类组件,在类组件内提供复用的状态逻辑代码,通过 prop 将复用的状态传递给被包装的组件.
使用场景: 比如暗黑模式.将组件进行包裹.
使用步骤
- 创建一个函数,名称约定以
with
开头.
- 指定函数参数,参数以首字母大写开头
- 在函数内部创建一个类组件,提供复用的状态逻辑代码
- 在该组件,渲染参数组件,同时将状态通过 prop 传递给参数组件
- 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import React from 'react';
function withMouse(wrappedComponent) { class Mouse extends React.Component { state = { x = 0, y = 0 } handleMouseMove = e => { this.setState({ x: e.clientX, y: clientY }) } componentDidMount() { window.addEventListener('mousemove', this.handleMouseMove) } componentWillUnmount() { window.removeEventListener('mousemove', this.handleMouseMove) } render() { return <wrappedComponent {...this.state} /> } } return Mouse
}
const Position = props => { <p>鼠标位置: {props.x}, {props.y}</p> } const Cat =(props) => ( <img src={img} alt="cat" style={{ postion: 'absolute', top: props.y, left: props.x }} /> )
const MousePosition = withMouse(Position) const MouseCat = withMouse(Cat)
class App extends React.Component { render() { return ( <div> <h1>高阶组件</h1> <MousePosition /> <MouseCat /> </div> ) } }
|
设置 display Name
原因: 默认情况,React 使用组件名作为 displayName,则会出现两个名字相同的情况
方法: 设置 displayName,便以区分.注意名字首字母大写.
1 2 3 4 5 6 7 8 9 10 11 12
| function withMouse(wrappedComponent) { class Mouse extends React.Component { } Mouse.displayName = `WithMouse${getDisplayName(wrappedComponent)}`; return Mouse; }
function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || "Component"; }
|
传递 props
将 props 和 state 一起放在返回的组件上
1
| return <wrappedComponent {...state} {...props} />;
|
DOM 元素
渲染文章时防止 XSS 攻击,使用特殊的写法dangerouslySetInnerHTML={{__html:'文章内容'}}
1 2 3 4 5 6
| <Content> {contentData && contentData.map((item, index) => ( <p key={index} dangerouslySetInnerHTML={{ __html: item }}></p> ))} </Content>
|
React 合成事件
- jsx 上写的事件没有绑定在真实 DOM 上,而是通过事件代理的方式绑定在 document 上.
- 冒泡到 document 上的也不是原生事件,而是合成事件.如果不想冒泡,需要使用
event.preventDefault
,不能使用event.stopPropagation
.
- 合成事件是有个专门的事件池来管理它们的创建和销毁.当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
React 合成事件和 dom 原生事件的顺序
- react 的所有事件都挂载在 document 中
- 当真实 dom 触发后冒泡到 document 后才会对 react 事件进行处理
- 所以原生的事件会先执行
- 然后执行 react 合成事件
- 最后执行真正在 document 上挂载的事件
React 事件系统
React 的事件系统分为几个部分:
事件系统流程:
在 React 代码执行时,内部会自动执行事件的注册;
第一次渲染,创建 fiberRoot 时,会进行事件的监听,所有的事件通过 addEventListener 委托在 id=root 的 DOM 元素上进行监听;
在我们触发事件时,会进行事件合成,同类型事件复用一个合成事件类实例对象;
最后进行事件的派发,执行我们代码中的事件回调函数;