React的执行过程:
组件初始化
- 创建React元素:React应用的起点通常是通过
React.createElement()
函数创建React元素。这个函数接受三个参数:元素的类型(可以是HTML标签名、自定义组件名或React.Fragment
等特殊类型)、元素的属性对象以及子元素。例如,const element = React.createElement('div', { className: 'container' }, 'Hello, React!');
创建了一个简单的div
元素。 - 渲染React元素:创建好React元素后,需要将其渲染到页面上的某个DOM节点中。通过
ReactDOM.render()
函数来实现,它接受两个参数:要渲染的React元素和目标DOM节点。例如,ReactDOM.render(element, document.getElementById('root'));
会将上述创建的div
元素渲染到id
为root
的DOM节点中。
组件挂载
- 创建组件实例:当渲染一个自定义组件时,React会首先创建该组件的实例。对于类组件,会调用组件的构造函数,并传入
props
。在构造函数中,通常会初始化组件的状态state
。对于函数组件,每次渲染都会执行函数组件本身,它接收props
作为参数,并返回一个React元素。 - 挂载生命周期方法:对于类组件,在组件实例创建后,React会依次调用
getDerivedStateFromProps()
方法和componentWillMount()
方法(在React 17中已被标记为不安全的生命周期方法,不建议使用)。getDerivedStateFromProps()
用于根据新的props
来更新组件的状态,它是一个静态方法,接收当前的props
和state
作为参数,并返回一个新的状态对象或null
。componentWillMount()
方法在组件挂载之前被调用,通常用于一些初始化操作,但由于其可能会导致一些难以预测的副作用,在新的React版本中已逐渐被废弃。 - 渲染组件:在完成生命周期方法的调用后,组件会执行
render()
方法来生成虚拟DOM树。render()
方法必须返回一个有效的React元素或null
。对于类组件,它返回的是通过React.createElement()
或其他方式创建的React元素;对于函数组件,它直接返回一个React元素。这个虚拟DOM树描述了组件的UI结构和内容。 - 创建真实DOM节点:React会根据虚拟DOM树创建相应的真实DOM节点,并将其插入到页面中的目标DOM节点中。在这个过程中,React会对虚拟DOM树进行 diff 算法的比较,只更新需要改变的部分,以提高性能。同时,React会为每个真实DOM节点添加一些必要的属性和事件处理函数,以便与组件的状态和交互逻辑进行关联。
- 执行
componentDidMount()
:在组件挂载到页面后,React会调用componentDidMount()
方法。这个方法通常用于执行一些需要在组件挂载后立即执行的操作,如发起网络请求、订阅事件监听器等。由于此时组件已经渲染到页面上,因此可以安全地访问和操作DOM节点。
组件更新
- 触发更新:组件的更新可以由多种原因触发,如
props
的改变、组件内部状态的改变、父组件的重新渲染等。当这些情况发生时,React会重新执行组件的更新过程。 - 更新生命周期方法:对于类组件,首先会调用
getDerivedStateFromProps()
方法,根据新的props
来更新组件的状态。然后,如果组件使用了shouldComponentUpdate()
方法,React会调用该方法来判断组件是否需要重新渲染。如果shouldComponentUpdate()
返回true
,则继续执行后续的更新过程;如果返回false
,则跳过本次更新,直接使用之前的渲染结果。接着,React会调用componentWillUpdate()
方法(同样在React 17中已被标记为不安全的生命周期方法,不建议使用),在组件更新之前执行一些准备工作。 - 重新渲染组件:与挂载阶段类似,组件会再次执行
render()
方法来生成新的虚拟DOM树。然后,React会使用 diff 算法将新的虚拟DOM树与旧的虚拟DOM树进行比较,找出需要更新的部分,并对真实DOM节点进行相应的更新操作。这个过程只更新发生变化的DOM节点,而不是重新渲染整个组件树,从而提高了更新的效率。 - 执行
componentDidUpdate()
:在组件更新完成后,React会调用componentDidUpdate()
方法。这个方法接收前一个props
和state
作为参数,可以用于在组件更新后执行一些操作,如根据新的状态更新DOM节点的样式、发起新的网络请求等。需要注意的是,在componentDidUpdate()
中访问this.props
和this.state
时,应该使用当前的props
和state
,而不是前一个props
和state
。
组件卸载
- 执行
componentWillUnmount()
:当组件从页面中移除时,React会调用componentWillUnmount()
方法。这个方法通常用于执行一些清理操作,如取消网络请求、移除事件监听器、清理定时器等。在这个方法中,应该释放所有与组件相关的资源,以避免内存泄漏和其他潜在的问题。 - 移除真实DOM节点:在
componentWillUnmount()
方法执行完成后,React会将与组件对应的真实DOM节点从页面中移除,释放相关的DOM资源。
事件处理
- 事件绑定:在React中,事件处理函数通常是在组件的
render()
方法中通过onClick
、onChange
等属性绑定到DOM节点上的。React会对这些事件进行代理,将所有的事件绑定到根节点上,然后根据事件的冒泡机制来触发相应的事件处理函数。这样做的好处是可以提高性能,减少内存占用,并且可以在不同的浏览器中保持一致的事件行为。 - 合成事件:React使用合成事件来处理DOM事件。合成事件是对原生DOM事件的封装,它提供了一个跨浏览器兼容的事件对象,并且在事件处理函数中自动绑定了
this
指针,使得开发者可以更方便地编写事件处理逻辑。例如,在一个按钮的点击事件处理函数中,可以直接通过this.props
和this.state
来访问组件的属性和状态,而不需要手动绑定this
。 - 事件冒泡和捕获:React的事件系统遵循DOM事件的冒泡和捕获机制。开发者可以通过在事件处理函数中使用
event.stopPropagation()
来阻止事件的冒泡,或者使用event.preventDefault()
来阻止默认的事件行为。这样可以根据具体的业务需求来控制事件的传播和处理。
状态管理
- 状态初始化:在类组件中,状态通常是在构造函数中通过
this.state
来初始化的。状态是一个对象,包含了组件的各种数据,这些数据会影响组件的渲染结果。例如,一个计数器组件可能有一个count
状态,用于记录当前的计数。 - 状态更新:当需要更新组件的状态时,应该使用
this.setState()
方法。this.setState()
会异步地更新组件的状态,并触发组件的重新渲染。在更新状态时,可以传递一个对象作为参数,该对象包含了需要更新的状态属性和新的值。React会自动将新的状态与旧的状态进行合并,并重新渲染组件。例如,对于上述的计数器组件,可以通过this.setState({ count: this.state.count + 1 });
来增加计数。 - 状态的不可变性:在React中,状态应该被视为不可变的。这意味着不应该直接修改
this.state
对象,而是应该通过this.setState()
方法来创建一个新的状态对象。这样做的好处是可以提高性能,并且便于进行状态的比较和更新。同时,遵循状态的不可变性原则也有助于提高代码的可维护性和可预测性。
React的执行过程是一个复杂而有序的过程,通过各个阶段的生命周期方法、虚拟DOM的渲染和更新、事件处理以及状态管理等机制,实现了高效、灵活的组件化开发模式,使得开发者能够构建出高性能、可维护的前端应用。