이 글은 주로 react 프레임워크의 원리에 대한 자세한 설명을 담고 있습니다. 또한 아래에 React에 대한 심층적인 이해가 많이 있습니다. 이제 이 글을 살펴보겠습니다.
#🎜 🎜#React 저는 이 프레임워크에 대해 애증의 관계를 맺은 지 2년이 넘었습니다. 그러나 그 단점은 점차 드러납니다. 대규모 프로젝트에서는Redux
및ReactRouter
와 같은 타사 프레임워크와 결합하면 복잡한 비즈니스 코드의 양이 매우 커집니다. (프론트엔드 코드가 이전 코드의 1.5배인 경우가 많습니다) . 초기 단계에서 기본 디자인이 좋지 않으면 개발 효율성이 떨어지는 문제에 직면하는 경우가 많습니다. 다음은 React 프레임워크의 몇 가지 핵심 개념을 요약한 것입니다. 모든 사람에게 도움이 되기를 바랍니다.
Redux
、ReactRouter
等三方框架后,结合复杂的业务代码量会变得非常大(前端代码常常是以前的1.5倍)。如果前期底层设计得不好,时常面临着开发效率低的问题。下面归纳了一些React框架的核心概念,希望对大家有帮助:React的diff
算法是Virtual DOM
之所以任性的最大依仗,大家知道页面的性能 一般是由渲染速度和渲染次数决定,如何最大程度地利用diff
算法进行开发?我们先看看它的原理。
计算一棵树形结构转换成另一棵树形结构的最少操作,传统 diff 算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3)
,其中 n 是树中节点的总数。也就是说如果要展示1000个节点,就要依次执行上十亿次的比较。这个性能消耗对对于前端项目来说是不可接受的。
如上所见,传统 diff 算法的复杂度为 O(n^3)
,显然这是无法满足性能要求的。而React
通过制定大胆的策略,将 O(n^3)
复杂度的问题转换成 O(n)
复杂度的问题。他是怎么做到的?
Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。如下图所示:
React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。
// tree diff算法实现updateChildren: function(nextNestedChildrenElements, transaction, context) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildrenElements, transaction, context); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } } }
如下图,A 节点(包括其子节点)整个被移动到 D 节点下,由于 React 只会简单的考虑同层级节点的位置变换,而对于不同层级的节点,只有创建和删除操作。当根节点发现子节点中 A 消失了,就会直接销毁 A;当 D 发现多了一个子节点 A,则会创建新的 A(包括子节点)作为其子节点。此时,React diff
的执行情况:create A -> create B -> create C -> delete A。
由此可发现,当出现节点跨层级移动时,并不会出现想象中的移动操作,而是以 A 为根节点的树被整个重新创建,这是一种影响 React
性能的操作。
拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
如果是同一类型的组件,按照原策略继续比较 virtual DOM tree
。
如果不是,则将该组件判断为 dirty component
,从而替换整个组件下的所有子节点。
对于同一类型的组件,有可能其 Virtual DOM
没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React
允许用户通过 shouldComponentUpdate()
diff
알고리즘은 Virtual DOM
의 고의성에 대한 가장 큰 기반입니다. 우리 모두 알고 있듯이 페이지의 성능은 일반적으로 렌더링 속도와 렌더링 횟수에 따라 결정됩니다. . diff
code>알고리즘 개발을 최대한 활용하는 방법은 무엇입니까? 먼저 어떻게 작동하는지 살펴보겠습니다. 한 트리 구조를 다른 트리 구조로 변환하는 데 필요한 최소 연산을 계산합니다. 기존 diff 알고리즘은 루프 재귀를 통해 노드를 순차적으로 비교하는 비효율적인 알고리즘입니다. 복잡성은 O(n^3)
에 도달합니다. 여기서 n은 트리의 총 노드 수입니다. 즉, 1,000개의 노드를 표시하려면 수십억 번의 비교를 순차적으로 수행해야 합니다. 이러한 성능 소비는 프런트엔드 프로젝트에서는 허용되지 않습니다.
O(n^3)
입니다. , 분명히 이것은 성능 요구 사항을 충족할 수 없습니다. 그리고 React
는 대담한 전략을 공식화하여 O(n^3)
복잡성 문제를 O(n)
복잡성 문제로 변환합니다. 그는 어떻게 했나요? #🎜🎜#// 卸载组件unmountComponent: function() { // 设置状态为 UNMOUNTING this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; // 如果存在 componentWillUnmount,则触发 if (this.componentWillUnmount) { this.componentWillUnmount(); } // 更新状态为 null this._compositeLifeCycleState = null; this._renderedComponent.unmountComponent(); this._renderedComponent = null; ReactComponent.Mixin.unmountComponent.call(this); }
React diff
의 실행 상태는 create A -> create B -> create C -> delete A입니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#노드가 레벨을 이동할 때 가상 이동 연산은 발생하지 않지만 A를 루트 노드로 하는 트리가 완전히 다시 생성되는 것을 확인할 수 있습니다. React
의 성능에 영향을 미치는 작업입니다. #🎜🎜#가상 DOM 트리
를 계속 비교합니다. . #🎜🎜##🎜🎜#더티 구성 요소
로 판단되어 전체 구성 요소 아래의 모든 하위 노드를 대체합니다. #🎜🎜##🎜🎜#Virtual DOM
에 변경이 없을 수도 있습니다. 계산 시간을 많이 절약할 수 있으므로 React
를 사용하면 사용자가 shouldComponentUpdate()
를 사용하여 구성 요소를 비교해야 하는지 결정할 수 있습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜#위와 같이 컴포넌트 D
가 컴포넌트 G
로 변경되면 두 컴포넌트
가 유사한 구조를 가지고 있더라도 일단 >React
D와 G가 서로 다른 유형의 컴포넌트라고 판단되면 둘의 구조를 비교하지 않고 직접 컴포넌트 D
를 삭제하고 컴포넌트 G를 다시 생성합니다.
및 기타 하위 노드. React diff
는 두 구성 요소
의 유형은 다르지만 구조가 유사한 경우 성능에 영향을 주지만 React
공식 블로그에서는 다음과 같이 말합니다. 구성 요소
가 DOM 트리
와 유사할 가능성이 거의 없으므로 이러한 극단적인 요소가 개발 프로세스에 큰 영향을 미치기는 어렵습니다. component D
改变为 component G
时,即使这两个 component
结构相似,一旦 React
判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D
,重新创建 component G
以及其子节点。虽然当两个 component
是不同类型但结构相似时,React diff
会影响性能,但正如 React
官方博客所言:不同类型的 component
是很少存在相似 DOM tree
的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。
对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!
新老集合所包含的节点,如下图所示,新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作,即可。
(1)[基于tree diff] 开发组件时,保持稳定的DOM结构有助于维持整体的性能。换而言之,尽可能少地动态操作DOM结构,尤其是移动操作。当节点数过大或者页面更新次数过多时,页面卡顿的现象比较明显。可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
(2)[基于component diff] 开发组件时,注意使用 shouldComponentUpdate()
来减少组件不必要的更新。除此之外,对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff
的性能消耗。
(3)[基于element diff] 对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
React的生命周期具体可分为四种情况:
当首次装载组件时,按顺序执行 getDefaultProps
、getInitialState
、componentWillMount
、render
和 componentDidMount
;
当卸载组件时,执行 componentWillUnmount
;
当重新装载组件时,此时按顺序执行 getInitialState
、componentWillMount
、render
和 componentDidMount
,但并不执行 getDefaultProps
;
当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps
、shouldComponentUpdate
、componentWillUpdate
、render
和 componentDidUpdate
。
mountComponent
负责管理生命周期中的 getInitialState
、componentWillMount
、render
和 componentDidMount
。
updateComponent
负责管理生命周期中的 componentWillReceiveProps
、shouldComponentUpdate
、componentWillUpdate
、render
和 componentDidUpdate
。
unmountComponent
负责管理生命周期中的 componentWillUnmount
。(想看更多就到PHP中文网React参考手册栏目中学习)
首先将状态设置为 UNMOUNTING
,若存在 componentWillUnmount
,则执行;如果此时在 componentWillUnmount
中调用 setState
,是不会触发 reRender
。更新状态为 NULL
shouldComponentUpdate()
사용에 주의하세요. 또한, 유사한 구조를 최대한 컴포넌트로 캡슐화해야 하는데, 이는 코드의 양을 줄일 뿐만 아니라 컴포넌트 차이
의 성능 소모도 줄여줍니다. #🎜🎜##🎜🎜# (3) [요소 차이 기준] 목록 구조의 경우 노드 수가 많을 때 마지막 노드를 목록의 선두로 이동하는 등의 작업을 줄여보세요. 너무 크거나 업데이트 작업이 너무 크면 자주 사용하면 React의 렌더링 성능에 어느 정도 영향을 미칠 것입니다. #🎜🎜#getDefaultProps
, getInitialState
, comComponentWillMount
, render를 실행합니다. code>를 순서대로 실행하고 <code>comComponentDidMount
;#🎜🎜#
comComponentWillUnmount
;#🎜🎜#getInitialState
, comComponentWillMount
, render
및 comComponentDidMount
순서대로 실행되지만 getDefaultProps
는 실행되지 않습니다. #🎜🎜#를 componentWillReceiveProps
, shouldComponentUpdate
, comComponentWillUpdate
, render
및 comComponentDidUpdate
순서로 실행합니다. #🎜🎜#mountComponent
는 다음을 담당합니다. 수명 주기에서 getInitialState
, comComponentWillMount
, render
및 comComponentDidMount
를 관리합니다. #🎜🎜##🎜🎜##🎜🎜#updateComponent
는 수명 주기, comComponentWillUpdate
, 렌더링
및 comComponentDidUpdate
. #🎜🎜##🎜🎜##🎜🎜#unmountComponent
는 수명 주기에서 comComponentWillUnmount
를 관리하는 역할을 담당합니다. (자세한 내용을 알고 싶다면 PHP 중국어 웹사이트 React 참조 매뉴얼을 방문하세요. ) #🎜 🎜##🎜🎜#먼저 상태를 UNMOUNTING
으로 설정하고, comComponentWillUnmount
가 있으면 이때 comComponentWillUnmount
가 호출되면 실행합니다. , >setState는 reRender
를 트리거하지 않습니다. 업데이트 상태가 NULL
이고 구성 요소 제거 작업이 완료되었습니다. 구현 코드는 다음과 같습니다. #🎜🎜#// 卸载组件unmountComponent: function() { // 设置状态为 UNMOUNTING this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; // 如果存在 componentWillUnmount,则触发 if (this.componentWillUnmount) { this.componentWillUnmount(); } // 更新状态为 null this._compositeLifeCycleState = null; this._renderedComponent.unmountComponent(); this._renderedComponent = null; ReactComponent.Mixin.unmountComponent.call(this); }
生命周期 | 调用次数 | 能否使用setState() |
---|---|---|
getDefaultProps | 1 | 否 |
getInitialState | 1 | 否 |
componentWillMount | 1 | 是 |
render | >=1 | 否 |
componentDidMount | 1 | 是 |
componentWillReceiveProps | >=0 | 是 |
shouldComponentUpdate | >=0 | 否 |
componentWillUpdate | >=0 | 否 |
componentDidUpdate | >=0 | 否 |
componentWillUnmount | 1 | 否 |
componentDidUnmount | 1 | 否 |
setState
是React
框架的核心方法之一,下面介绍一下它的原理:
// 更新 statesetState: function(partialState, callback) { // 合并 _pendingState this.replaceState( assign({}, this._pendingState || this.state, partialState), callback ); },
当调用 setState
时,会对 state
以及 _pendingState
更新队列进行合并操作,但其实真正更新 state
的幕后黑手是replaceState
。
// 更新 statereplaceState: function(completeState, callback) { validateLifeCycleOnReplaceState(this); // 更新队列 this._pendingState = completeState; // 判断状态是否为 MOUNTING,如果不是,即可执行更新 if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { ReactUpdates.enqueueUpdate(this, callback); } },
replaceState
会先判断当前状态是否为 MOUNTING
,如果不是即会调用 ReactUpdates.enqueueUpdate
执行更新。
当状态不为 MOUNTING
或 RECEIVING_PROPS
时,performUpdateIfNecessary
会获取 _pendingElement
、_pendingState
、_pendingForceUpdate
,并调用 updateComponent
进行组件更新。
// 如果存在 _pendingElement、_pendingState、_pendingForceUpdate,则更新组件performUpdateIfNecessary: function(transaction) { var compositeLifeCycleState = this._compositeLifeCycleState; // 当状态为 MOUNTING 或 RECEIVING_PROPS时,则不更新 if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { return; } var prevElement = this._currentElement; var nextElement = prevElement; if (this._pendingElement != null) { nextElement = this._pendingElement; this._pendingElement = null; } // 调用 updateComponent this.updateComponent( transaction, prevElement, nextElement ); }
如果在
shouldComponentUpdate
或componentWillUpdate
中调用setState
,此时的状态已经从RECEIVING_PROPS -> NULL
,则performUpdateIfNecessary
就会调用updateComponent
进行组件更新,但updateComponent
又会调用shouldComponentUpdate
和componentWillUpdate
,因此造成循环调用,使得浏览器内存占满后崩溃。
不建议在 getDefaultProps
、getInitialState
、shouldComponentUpdate
、componentWillUpdate
、render
和 componentWillUnmount
中调用 setState,特别注意:不能在 shouldComponentUpdate
和 componentWillUpdate
中调用 setState
,会导致循环调用。
本篇文章到这就结束了(想看更多就到PHP中文网React使用手册栏目中学习),有问题的可以在下方留言提问。
위 내용은 React 프레임워크에는 어떤 알고리즘이 있나요? React 프레임워크의 알고리즘에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!