React生命周期及事件詳解


引用原文:http://blog.csdn.net/limm33/article/details/50942808

 

一、組件的詳細說明和生命周期ComponentSpecs and Lifecycle

組件的詳細說明(Component Specifications)

當通過調用 React.createClass() 來創建組件的時候,你應該提供一個包含 render 方法的對象,並且也可以包含其它的在這里描述的生命周期方法。

render

ReactComponent render()

render() 方法是必須的。

當調用的時候,會檢測 this.props 和 this.state,返回一個單子級組件。該子級組件可以是虛擬的本地DOM 組件(比如 <div /> 或者 React.DOM.div()),也可以是自定義的復合組件。

你也可以返回 null 或者 false 來表明不需要渲染任何東西。實際上,React渲染一個<noscript> 標簽來處理當前的差異檢查邏輯。當返回 null 或者 false 的時候,this.getDOMNode() 將返回 null。

render() 函數應該是純粹的,也就是說該函數不修改組件state,每次調用都返回相同的結果,不讀寫 DOM 信息,也不和瀏覽器交互(例如通過使用 setTimeout)。如果需要和瀏覽器交互,在 componentDidMount() 中或者其它生命周期方法中做這件事。保持render() 純粹,可以使服務器端渲染更加切實可行,也使組件更容易被理解。

getInitialState

object getInitialState()

在組件掛載之前調用一次。返回值將會作為 this.state 的初始值。

getDefaultProps

object getDefaultProps()

在組件類創建的時候調用一次,然后返回值被緩存下來。如果父組件沒有指定props 中的某個鍵,則此處返回的對象中的相應屬性將會合並到 this.props (使用 in 檢測屬性)。

該方法在任何實例創建之前調用,因此不能依賴於 this.props。另外,getDefaultProps() 返回的任何復雜對象將會在實例間共享,而不是每個實例擁有一份拷貝。

propTypes

object propTypes

propTypes 對象允許驗證傳入到組件的props。更多關於 propTypes 的信息,參考可重用的組件

mixins

array mixins

mixin 數組允許使用混合來在多個組件之間共享行為。更多關於混合的信息,參考可重用的組件

statics

object statics

statics 對象允許你定義靜態的方法,這些靜態的方法可以在組件類上調用。例如:

var MyComponent=React.createClass({
  statics: {
    customMethod: function(foo) {
      return foo==='bar';
    }
  },
  render:function(){
  }
});

MyComponent.customMethod('bar'); // true

在這個塊兒里面定義的方法都是靜態的,意味着你可以在任何組件實例創建之前調用它們,這些方法不能獲取組件的props 和 state。如果你想在靜態方法中檢查 props 的值,在調用處把 props 作為參數傳入到靜態方法。

displayName

string displayName

displayName 字符串用於輸出調試信息。JSX自動設置該值;參考JSX 深入

生命周期方法

許多方法在組件生命周期中某個確定的時間點執行。

掛載: componentWillMount

componentWillMount()

服務器端和客戶端都只調用一次,在初始化渲染執行之前立刻調用。如果在這個方法內調用setState,render() 將會感知到更新后的state,將會執行僅一次,盡管 state 改變了。

掛載: componentDidMount

componentDidMount()

在初始化渲染執行之后立刻調用一次,僅客戶端有效(服務器端不會調用)。在生命周期中的這個時間點,組件擁有一個DOM 展現,你可以通過 this.getDOMNode() 來獲取相應 DOM 節點。

如果想和其它JavaScript 框架集成,使用 setTimeout 或者 setInterval 來設置定時器,或者發送 AJAX請求,可以在該方法中執行這些操作。

注意:

為了兼容 v0.9,DOM節點作為最后一個參數傳入。你依然可以通過this.getDOMNode() 獲取 DOM 節點。

更新: componentWillReceiveProps

componentWillReceiveProps(object nextProps)

在組件接收到新的props 的時候調用。在初始化渲染的時候,該方法不會調用。

用此函數可以作為react 在 prop 傳入之后, render() 渲染之前更新 state 的機會。老的 props 可以通過 this.props 獲取到。在該函數中調用 this.setState() 將不會引起第二次渲染。

componentWillReceiveProps:function(nextProps){
  this.setState({
    likesIncreasing: nextProps.likeCount> this.props.likeCount
  });
}

注意:

對於state,沒有相似的方法: componentWillReceiveState。將要傳進來的 prop 可能會引起 state 改變,反之則不然。如果需要在state 改變的時候執行一些操作,請使用 componentWillUpdate。

更新: shouldComponentUpdate

booleanshouldComponentUpdate(object nextProps, object nextState)

在接收到新的props 或者 state,將要渲染之前調用。該方法在初始化渲染的時候不會調用,在使用 forceUpdate 方法的時候也不會。

如果確定新的props 和 state 不會導致組件更新,則此處應該 返回 false。

shouldComponentUpdate:function(nextProps,nextState) {
  return nextProps.id!== this.props.id;
}

如果 shouldComponentUpdate 返回false,則 render() 將不會執行,直到下一次 state 改變。(另外,componentWillUpdate 和 componentDidUpdate 也不會被調用。)

默認情況下,shouldComponentUpdate 總會返回true,在 state 改變的時候避免細微的bug,但是如果總是小心地把 state 當做不可變的,在 render() 中只從 props 和state 讀取值,此時你可以覆蓋 shouldComponentUpdate 方法,實現新老 props 和state 的比對邏輯。

如果性能是個瓶頸,尤其是有幾十個甚至上百個組件的時候,使用 shouldComponentUpdate可以提升應用的性能。

更新: componentWillUpdate

componentWillUpdate(object nextProps, object nextState)

在接收到新的props 或者 state 之前立刻調用。在初始化渲染的時候該方法不會被調用。

使用該方法做一些更新之前的准備工作。

注意:

不能在剛方法中使用 this.setState()。如果需要更新 state 來響應某個 prop 的改變,請使用 componentWillReceiveProps。

更新: componentDidUpdate

componentDidUpdate(object prevProps, object prevState)

在組件的更新已經同步到DOM 中之后立刻被調用。該方法不會在初始化渲染的時候調用。

使用該方法可以在組件更新之后操作DOM 元素。

注意:

為了兼容 v0.9,DOM節點會作為最后一個參數傳入。如果使用這個方法,你仍然可以使用 this.getDOMNode() 來訪問 DOM 節點。

移除: componentWillUnmount

componentWillUnmount()

在組件從DOM 中移除的時候立刻被調用。

在該方法中執行任何必要的清理,比如無效的定時器,或者清除在 componentDidMount 中創建的 DOM 元素。

 

二、組件API

ReactComponent

React組件實例在渲染的時候創建。這些實例在接下來的渲染中被重復使用,可以在組件方法中通過 this 訪問。唯一一種在 React 之外獲取 React組件實例句柄的方式就是保存React.render 的返回值。在其它組件內,可以使用 refs 得到相同的結果。

setState

setState(object nextState[,function callback])

合並nextState 和當前 state。這是在事件處理函數中和請求回調函數中觸發 UI 更新的主要方法。另外,也支持可選的回調函數,該函數在 setState 執行完畢並且組件重新渲染完成之后調用。

注意:

絕對不要直接改變 this.state,因為在之后調用 setState() 可能會替換掉你做的更改。把 this.state 當做不可變的。

setState() 不會立刻改變 this.state,而是創建一個即將處理的 state 轉變。在調用該方法之后獲取 this.state 的值可能會得到現有的值,而不是最新設置的值。

不保證 setState() 調用的同步性,為了提升性能,可能會批量執行 state 轉變和 DOM 渲染。

setState() 將總是觸發一次重繪,除非在 shouldComponentUpdate() 中實現了條件渲染邏輯。如果使用可變的對象,但是又不能在 shouldComponentUpdate() 中實現這種邏輯,僅在新 state 和之前的 state存在差異的時候調用 setState() 可以避免不必要的重新渲染。

replaceState

replaceState(object nextState[,function callback])

類似於 setState(),但是刪除之前所有已存在的 state鍵,這些鍵都不在 nextState 中。

forceUpdate()

forceUpdate([function callback])

如果 render() 方法從 this.props 或者 this.state 之外的地方讀取數據,你需要通過調用 forceUpdate() 告訴 React什么時候需要再次運行 render()。如果直接改變了this.state,也需要調用 forceUpdate()。

調用 forceUpdate() 將會導致 render() 方法在相應的組件上被調用,並且子級組件也會調用自己的 render(),但是如果標記改變了,那么 React僅會更新 DOM。

通常情況下,應該盡量避免所有使用 forceUpdate() 的情況,在 render() 中僅從this.props 和 this.state 中讀取數據。這會使應用大大簡化,並且更加高效。

getDOMNode

DOMElement getDOMNode()

如果組件已經掛載到了DOM 上,該方法返回相應的本地瀏覽器 DOM 元素。從 DOM 中讀取值的時候,該方法很有用,比如獲取表單字段的值和做一些 DOM 操作。當 render 返回null 或者 false 的時候,this.getDOMNode() 返回 null。

isMounted()

bool isMounted()

如果組件渲染到了DOM 中,isMounted() 返回true。可以使用該方法保證 setState() 和forceUpdate() 在異步場景下的調用不會出錯。

setProps

setProps(object nextProps[,function callback])

當和一個外部的JavaScript 應用集成的時候,你可能想給一個用 React.render() 渲染的組件打上改變的標記。

盡管在同一個節點上再次調用 React.render() 來更新根組件是首選的方式,也可以調用setProps() 來改變組件的屬性,觸發一次重新渲染。另外,可以傳遞一個可選的回調函數,該函數將會在 setProps 完成並且組件重新渲染完成之后調用。

注意:

When possible,the declarative approach of calling React.render() againis preferred; it tends to make updates easier to reason about. (There's nosignificant performance difference between the two approaches.)

剛方法僅在根組件上面調用。也就是說,僅在直接傳給 React.render() 的組件上可用,在它的子級組件上不可用。如果你傾向於在子組件上使用 setProps(),不要利用響應式更新,而是當子組件在 render() 中創建的時候傳入新的prop 到子組件中。

replaceProps

replaceProps(object nextProps[,function callback])

類似於 setProps(),但是刪除所有已存在的props,而不是合並新舊兩個 props 對象。

 

三、組件的詳細說明和生命周期(ComponentSpecs and Lifecycle)

組件的詳細說明(Component Specifications)

當通過調用 React.createClass() 來創建組件的時候,你應該提供一個包含 render 方法的對象,並且也可以包含其它的在這里描述的生命周期方法。

render

ReactComponent render()

render() 方法是必須的。

當調用的時候,會檢測 this.props 和 this.state,返回一個單子級組件。該子級組件可以是虛擬的本地DOM 組件(比如 <div /> 或者 React.DOM.div()),也可以是自定義的復合組件。

你也可以返回 null 或者 false 來表明不需要渲染任何東西。實際上,React渲染一個<noscript> 標簽來處理當前的差異檢查邏輯。當返回 null 或者 false 的時候,this.getDOMNode() 將返回 null。

render() 函數應該是純粹的,也就是說該函數不修改組件state,每次調用都返回相同的結果,不讀寫 DOM 信息,也不和瀏覽器交互(例如通過使用 setTimeout)。如果需要和瀏覽器交互,在 componentDidMount() 中或者其它生命周期方法中做這件事。保持render() 純粹,可以使服務器端渲染更加切實可行,也使組件更容易被理解。

getInitialState

object getInitialState()

在組件掛載之前調用一次。返回值將會作為 this.state 的初始值。

getDefaultProps

object getDefaultProps()

在組件類創建的時候調用一次,然后返回值被緩存下來。如果父組件沒有指定props 中的某個鍵,則此處返回的對象中的相應屬性將會合並到 this.props (使用 in 檢測屬性)。

該方法在任何實例創建之前調用,因此不能依賴於 this.props。另外,getDefaultProps() 返回的任何復雜對象將會在實例間共享,而不是每個實例擁有一份拷貝。

propTypes

object propTypes

propTypes 對象允許驗證傳入到組件的props。更多關於 propTypes 的信息,參考可重用的組件

mixins

array mixins

mixin 數組允許使用混合來在多個組件之間共享行為。更多關於混合的信息,參考可重用的組件

statics

object statics

statics 對象允許你定義靜態的方法,這些靜態的方法可以在組件類上調用。例如:

var MyComponent=React.createClass({
  statics: {
    customMethod: function(foo) {
      return foo==='bar';
    }
  },
  render:function(){
  }
});

MyComponent.customMethod('bar'); // true

在這個塊兒里面定義的方法都是靜態的,意味着你可以在任何組件實例創建之前調用它們,這些方法不能獲取組件的props 和 state。如果你想在靜態方法中檢查 props 的值,在調用處把 props 作為參數傳入到靜態方法。

displayName

string displayName

displayName 字符串用於輸出調試信息。JSX自動設置該值;參考JSX 深入

生命周期方法

許多方法在組件生命周期中某個確定的時間點執行。

掛載: componentWillMount

componentWillMount()

服務器端和客戶端都只調用一次,在初始化渲染執行之前立刻調用。如果在這個方法內調用setState,render() 將會感知到更新后的state,將會執行僅一次,盡管 state 改變了。

掛載: componentDidMount

componentDidMount()

在初始化渲染執行之后立刻調用一次,僅客戶端有效(服務器端不會調用)。在生命周期中的這個時間點,組件擁有一個DOM 展現,你可以通過 this.getDOMNode() 來獲取相應 DOM 節點。

如果想和其它JavaScript 框架集成,使用 setTimeout 或者 setInterval 來設置定時器,或者發送 AJAX請求,可以在該方法中執行這些操作。

注意:

為了兼容 v0.9,DOM節點作為最后一個參數傳入。你依然可以通過this.getDOMNode() 獲取 DOM 節點。

更新: componentWillReceiveProps

componentWillReceiveProps(object nextProps)

在組件接收到新的props 的時候調用。在初始化渲染的時候,該方法不會調用。

用此函數可以作為react 在 prop 傳入之后, render() 渲染之前更新 state 的機會。老的 props 可以通過 this.props 獲取到。在該函數中調用 this.setState() 將不會引起第二次渲染。

componentWillReceiveProps:function(nextProps){
  this.setState({
    likesIncreasing: nextProps.likeCount> this.props.likeCount
  });
}

注意:

對於state,沒有相似的方法: componentWillReceiveState。將要傳進來的 prop 可能會引起 state 改變,反之則不然。如果需要在state 改變的時候執行一些操作,請使用 componentWillUpdate。

更新: shouldComponentUpdate

booleanshouldComponentUpdate(object nextProps, object nextState)

在接收到新的props 或者 state,將要渲染之前調用。該方法在初始化渲染的時候不會調用,在使用 forceUpdate 方法的時候也不會。

如果確定新的props 和 state 不會導致組件更新,則此處應該 返回 false。

shouldComponentUpdate:function(nextProps,nextState) {
  return nextProps.id!== this.props.id;
}

如果 shouldComponentUpdate 返回false,則 render() 將不會執行,直到下一次 state 改變。(另外,componentWillUpdate 和 componentDidUpdate 也不會被調用。)

默認情況下,shouldComponentUpdate 總會返回true,在 state 改變的時候避免細微的bug,但是如果總是小心地把 state 當做不可變的,在 render() 中只從 props 和state 讀取值,此時你可以覆蓋 shouldComponentUpdate 方法,實現新老 props 和state 的比對邏輯。

如果性能是個瓶頸,尤其是有幾十個甚至上百個組件的時候,使用 shouldComponentUpdate可以提升應用的性能。

更新: componentWillUpdate

componentWillUpdate(object nextProps, object nextState)

在接收到新的props 或者 state 之前立刻調用。在初始化渲染的時候該方法不會被調用。

使用該方法做一些更新之前的准備工作。

注意:

不能在剛方法中使用 this.setState()。如果需要更新 state 來響應某個 prop 的改變,請使用 componentWillReceiveProps。

更新: componentDidUpdate

componentDidUpdate(object prevProps, object prevState)

在組件的更新已經同步到DOM 中之后立刻被調用。該方法不會在初始化渲染的時候調用。

使用該方法可以在組件更新之后操作DOM 元素。

注意:

為了兼容 v0.9,DOM節點會作為最后一個參數傳入。如果使用這個方法,你仍然可以使用 this.getDOMNode() 來訪問 DOM 節點。

移除: componentWillUnmount

componentWillUnmount()

在組件從DOM 中移除的時候立刻被調用。

在該方法中執行任何必要的清理,比如無效的定時器,或者清除在 componentDidMount 中創建的 DOM 元素。

 

四、標簽和屬性支持

支持的標簽

React嘗試支持所用常用的元素。如果你需要的元素沒有在下面列出來,請提交一個問題(issue)。

HTML 元素

下列的HTML 元素是被支持的:

a abbr address area article aside audio b base bdi bdo bigblockquote body br
button canvas caption cite code col colgroup data datalist dd del detailsdfn
dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4h5
h6 head header hr html i iframe img input ins kbd keygen label legend lilink
main map mark menu menuitem meta meter nav noscript object ol optgroupoption
output p param picture pre progress q rp rt ruby s samp script sectionselect
small source span strong style sub summary sup table tbody td textarea tfootth
thead time title tr track u ul var video wbr

SVG 元素

下列的SVG 元素是被支持的:

circle defs ellipse g line linearGradient mask path patternpolygon polyline
radialGradient rect stop svg text tspan

你或許對 react-art 也感興趣,它是一個為 React 寫的渲染到Canvas、SVG 或者 VML(IE8) 的繪圖庫。

支持的屬性

React支持所有 data-* 和 aria-* 屬性,也支持下面列出的屬性。

注意:

所有的屬性都是駝峰命名的,class 屬性和 for 屬性分別改為 className 和htmlFor,來符合 DOM API 規范。

對於支持的事件列表,參考支持的事件

HTML 屬性

這些標准的屬性是被支持的:

accept acceptCharset accessKey action allowFullScreenallowTransparency alt
async autoComplete autoPlay cellPadding cellSpacing charSet checkedclassID
className cols colSpan content contentEditable contextMenu controlscoords
crossOrigin data dateTime defer dir disabled download draggable encTypeform
formAction formEncType formMethod formNoValidate formTarget frameBorderheight
hidden href hrefLang htmlFor httpEquiv icon id label lang list loopmanifest
marginHeight marginWidth max maxLength media mediaGroup method minmultiple
muted name noValidate open pattern placeholder poster preloadradioGroup
readOnly rel required role rows rowSpan sandbox scope scrollingseamless
selected shape size sizes span spellCheck src srcDoc srcSet start stepstyle
tabIndex target title type useMap value width wmode

另外,下面非標准的屬性也是被支持的:

  • autoCapitalize autoCorrect 用於移動端的 Safari。
  • property 用於 Open Graph 原標簽。
  • itemProp itemScope itemType 用於 HTML5 microdata

也有React 特有的屬性 dangerouslySetInnerHTML (更多信息),用於直接插入 HTML字符串到組件中。

SVG 屬性 #

cx cy d dx dy fill fillOpacity fontFamily fontSize fx fygradientTransform
gradientUnits markerEnd markerMid markerStart offsetopacity
patternContentUnits patternUnits points preserveAspectRatio r rxry
spreadMethod stopColor stopOpacity stroke strokeDasharraystrokeLinecap
strokeOpacity strokeWidth textAnchor transform version viewBox x1 x2 x y1 y2 y

 

 

五、事件系統

虛擬事件對象

事件處理器將會傳入虛擬事件對象的實例,一個對瀏覽器本地事件的跨瀏覽器封裝。它有和瀏覽器本地事件相同的屬性和方法,包括 stopPropagation() 和 preventDefault(),但是沒有瀏覽器兼容問題。

如果因為一些因素,需要底層的瀏覽器事件對象,只要使用 nativeEvent 屬性就可以獲取到它了。每一個虛擬事件對象都有下列的屬性:

boolean bubbles
booleancancelable
DOMEventTarget currentTarget
booleandefaultPrevented
number eventPhase
booleanisTrusted
DOMEvent nativeEvent
voidpreventDefault()
voidstopPropagation()
DOMEventTarget target
number timeStamp
string type

注意:

對於v0.12,在事件處理函數中返回 false 將不會阻止事件冒泡。取而代之的是在合適的應用場景下,手動調用 e.stopPropagation() 或者 e.preventDefault()。

支持的事件

React標准化了事件對象,因此在不同的瀏覽器中都會有相同的屬性。

如下的事件處理器在事件冒泡階段觸發。要在捕獲階段觸發某個事件處理器,在事件名字后面追加 Capture 字符串;例如,使用 onClickCapture 而不是 onClick 來在捕獲階段處理點擊事件。

剪貼板事件

事件名:

onCopy onCut onPaste

屬性:

DOMDataTransfer clipboardData

鍵盤事件:

事件名:

onKeyDown onKeyPress onKeyUp

屬性:

boolean altKey
NumbercharCode
booleanctrlKey
functiongetModifierState(key)
Stringkey
NumberkeyCode
Stringlocale
Numberlocation
booleanmetaKey
booleanrepeat
booleanshiftKey
Numberwhich

焦點事件

事件名:

onFocus onBlur

屬性:

DOMEventTarget relatedTarget

表單事件

事件名:

onChange onInput onSubmit

更多關於onChange 事件的信息,參考表單

鼠標事件

事件名:

onClick onDoubleClick onDrag onDragEnd onDragEnter onDragExitonDragLeave
onDragOver onDragStart onDrop onMouseDown onMouseEnteronMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp

屬性:

boolean altKey
Numberbutton
Numberbuttons
NumberclientX
NumberclientY
booleanctrlKey
functiongetModifierState(key)
booleanmetaKey
NumberpageX
NumberpageY
DOMEventTarget relatedTarget
NumberscreenX
NumberscreenY
booleanshiftKey

觸摸事件

為了使觸摸事件生效,在渲染所有組件之前調用 React.initializeTouchEvents(true)。

事件名:

onTouchCancel onTouchEnd onTouchMove onTouchStart

屬性:

booleanaltKey
DOMTouchList changedTouches
booleanctrlKey
functiongetModifierState(key)
booleanmetaKey
booleanshiftKey
DOMTouchList targetTouches
DOMTouchList touches

UI 事件

事件名:

onScroll

屬性:

Numberdetail
DOMAbstractView view

鼠標滾輪滾動事件

事件名:

onWheel

屬性:

Number deltaMode
NumberdeltaX
NumberdeltaY
NumberdeltaZ

 

 

六、與DOM 的差異

React為了性能和跨瀏覽器的原因,實現了一個獨立於瀏覽器的事件和 DOM 系統。利用此功能,可以屏蔽掉一些瀏覽器的 DOM 的粗糙實現。

  • 所有 DOM 的 properties 和 attributes (包括事件處理器)應該都是駝峰命名的,以便和標准的 JavaScript 風格保持一致。我們故意和規范不同,因為規范本身就不一致。然而,data-* 和 aria-* 屬性符合規范,應該僅是小寫的。
  • style 屬性接收一個帶有駝峰命名風格的 JavaScript 對象,而不是一個 CSS 字符串。這與 DOM 中的 style 的 JavaScript 屬性保持一致,更加有效,並且彌補了 XSS 安全漏洞。
  • 所有的事件對象和 W3C 規范保持一致,並且所有的事件(包括提交事件)冒泡都正確地遵循 W3C 規范。參考事件系統獲取更多詳細信息。
  • onChange 事件表現得和你想要的一樣:當表單字段改變了,該事件就被觸發,而不是等到失去焦點的時候。我們故意和現有的瀏覽器表現得不一致,是因為 onChange 是它的行為的一個錯誤稱呼,並且 React 依賴於此事件來實時地響應用戶輸入。參考表單獲取更多詳細信息。

 

七、特殊的非DOM 屬性

除了與 DOM 的差異之外,React 也提供了一些 DOM 里面不存在的屬性。

  • key:可選的唯一的標識器。當組件在渲染過程中被各種打亂的時候,由於差異檢測邏輯,可能會被銷毀后重新創建。給組件綁定一個 key,可以持續確保組件還存在 DOM 中。更多內容請參考這里
  • ref:參考這里
  • dangerouslySetInnerHTML:提供插入純 HTML 字符串的功能,主要為了能和生成 DOM 字符串的庫整合。更多內容請參考這里
  •  

八、Reconciliation

React的關鍵設計目標是使 API 看起來就像每一次有數據更新的時候,整個應用重新渲染了一樣。這就極大地簡化了應用的編寫,但是同時使 React易於駕馭,也是一個很大的挑戰。這篇文章解釋了我們如何使用強大的試探法來將 O(n3) 復雜度的問題轉換成O(n) 復雜度的問題。

動機(Motivation)

生成最少的將一顆樹形結構轉換成另一顆樹形結構的操作,是一個復雜的,並且值得研究的問題。最優算法的復雜度是 O(n3),n 是樹中節點的總數。

這意味着要展示1000個節點,就要依次執行上十億次的比較。這對我們的使用場景來說太昂貴了。准確地感受下這個數字:現今的CPU 每秒鍾能執行大約三十億條指令。因此即便是最高效的實現,也不可能在一秒內計算出差異情況。

既然最優的算法都不好處理這個問題,我們實現一個非最優的O(n) 算法,使用試探法,基於如下兩個假設:

1、擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。

2、可以為元素提供一個唯一的標志,該元素在不同的渲染過程中保持不變。

實際上,這些假設會使在幾乎所有的應用場景下,應用變得出奇地快。

兩個節點的差異檢查(Pair-wise diff)

為了進行一次樹結構的差異檢查,首先需要能夠檢查兩個節點的差異。此處有三種不同的情況需要處理:

不同的節點類型

如果節點的類型不同,React將會把它們當做兩個不同的子樹,移除之前的那棵子樹,然后創建並插入第二棵子樹。

renderA:<div />
renderB: <span />
=> [removeNode <div />], [insertNode<span/>]

該方法也同樣應用於傳統的組件。如果它們不是相同的類型,React甚至將不會嘗試計算出該渲染什么,僅會從 DOM 中移除之前的節點,然后插入新的節點。

renderA:<Header/>
renderB: <Content />
=> [removeNode <Header />], [insertNode<Content/>]

具備這種高級的知識點對於理解為什么React 的差異檢測邏輯既快又精確是很重要的。它對於避開樹形結構大部分的檢測,然后聚焦於似乎相同的部分,提供了啟發。

一個 <Header> 元素與一個 <Content> 元素生成的 DOM結構不太可能一樣。React 將重新創建樹形結構,而不是耗費時間去嘗試匹配這兩個樹形結構。

如果在兩個連續的渲染過程中的相同位置都有一個 <Header> 元素,將會希望生成一個非常相似的DOM 結構,因此值得去做一做匹配。

DOM 節點

當比較兩個DOM 節點的時候,我們查看兩者的屬性,然后能夠找出哪一個屬性隨着時間產生了變化。

renderA:<div id="before"/>
renderB: <div id="after"/>
=> [replaceAttribute id "after"]

React不會把 style 當做難以操作的字符串,而是使用鍵值對對象。這就很容易地僅更新改變了的樣式屬性。

renderA:<div style={{color: 'red'}}/>
renderB: <div style={{fontWeight: 'bold'}}/>
=> [removeStyle color], [addStyle font-weight 'bold']

在屬性更新完畢之后,遞歸檢測所有的子級的屬性。

自定義組件

我們決定兩個自定義組件是相同的。因為組件是狀態化的,不可能每次狀態改變都要創建一個新的組件實例。React利用新組件上的所有屬性,然后在之前的組件實例上調用component[Will/Did]ReceiveProps()。

現在,之前的組件就是可操作了的。它的 render() 方法被調用,然后差異算法重新比較新的狀態和上一次的狀態。

子級優化差異算法(List-wise diff)

問題點(Problematic Case)

為了完成子級更新,React選用了一種很原始的方法。React 同時遍歷兩個子級列表,當發現差異的時候,就產生一次 DOM 修改。

例如在末尾添加一個元素:

renderA:<div><span>first</span></div>
renderB: <div><span>first</span><span>second</span></div>
=> [insertNode <span>second</span>]

在開始處插入元素比較麻煩。React發現兩個節點都是 span,因此直接修改已有 span 的文本內容,然后在后面插入一個新的 span 節點。

renderA:<div><span>first</span></div>
renderB: <div><span>second</span><span>first</span></div>
=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]

有很多的算法嘗試找出變換一組元素的最小操作集合。Levenshtein distance算法能夠找出這個最小的操作集合,使用單一元素插入、刪除和替換,復雜度為O(n2) 。即使使用 Levenshtein算法,不會檢測出一個節點已經移到了另外一個位置去了,要實現這個檢測算法,會引入更加糟糕的復雜度。

鍵(Keys)

為了解決這個看起來很棘手的問題,引入了一個可選的屬性。可以給每個子級一個鍵值,用於將來的匹配比較。如果指定了一個鍵值,React就能夠檢測出節點插入、移除和替換,並且借助哈希表使節點移動復雜度為 O(n)。

renderA:<div><span key="first">first</span></div>
renderB: <div><span key="second">second</span><spankey="first">first</span></div>
=> [insertNode <span>second</span>]

在實際開發中,生成一個鍵值不是很困難。大多數時候,要展示的元素已經有一個唯一的標識了。當沒有唯一標識的時候,可以給組件模型添加一個新的ID 屬性,或者計算部分內容的哈希值來生成一個鍵值。記住,鍵值僅需要在兄弟節點中唯一,而不是全局唯一。

權衡(Trade-offs)

同步更新算法只是一種實現細節,記住這點很重要。React能在每次操作中重新渲染整個應用,最終的結果將會是一樣的。我們定期優化這個啟發式算法來使常規的應用場景更加快速。

在當前的實現中,能夠檢測到某個子級樹已經從它的兄弟節點中移除,但是不能指出它是否已經移到了其它某個地方。當前算法將會重新渲染整個子樹。

由於依賴於兩個預判條件,如果這兩個條件都沒有滿足,性能將會大打折扣。

1、算法將不會嘗試匹配不同組件類的子樹。如果發現正在使用的兩個組件類輸出的DOM 結構非常相似,你或許想把這兩個組件類改成一個組件類。實際上, 這不是個問題。

2、如果沒有提供穩定的鍵值(例如通過Math.random() 生成),所有子樹將會在每次數據更新中重新渲染。通過給開發者設置鍵值的機會,能夠給特定場景寫出更優化的代碼。

 

 

九、Reconciliation

React的關鍵設計目標是使 API 看起來就像每一次有數據更新的時候,整個應用重新渲染了一樣。這就極大地簡化了應用的編寫,但是同時使 React易於駕馭,也是一個很大的挑戰。這篇文章解釋了我們如何使用強大的試探法來將 O(n3) 復雜度的問題轉換成O(n) 復雜度的問題。

動機(Motivation)

生成最少的將一顆樹形結構轉換成另一顆樹形結構的操作,是一個復雜的,並且值得研究的問題。最優算法的復雜度是 O(n3),n 是樹中節點的總數。

這意味着要展示1000個節點,就要依次執行上十億次的比較。這對我們的使用場景來說太昂貴了。准確地感受下這個數字:現今的CPU 每秒鍾能執行大約三十億條指令。因此即便是最高效的實現,也不可能在一秒內計算出差異情況。

既然最優的算法都不好處理這個問題,我們實現一個非最優的O(n) 算法,使用試探法,基於如下兩個假設:

1、擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構。

2、可以為元素提供一個唯一的標志,該元素在不同的渲染過程中保持不變。

實際上,這些假設會使在幾乎所有的應用場景下,應用變得出奇地快。

兩個節點的差異檢查(Pair-wise diff)

為了進行一次樹結構的差異檢查,首先需要能夠檢查兩個節點的差異。此處有三種不同的情況需要處理:

不同的節點類型

如果節點的類型不同,React將會把它們當做兩個不同的子樹,移除之前的那棵子樹,然后創建並插入第二棵子樹。

renderA:<div />
renderB: <span />
=> [removeNode <div />], [insertNode<span/>]

該方法也同樣應用於傳統的組件。如果它們不是相同的類型,React甚至將不會嘗試計算出該渲染什么,僅會從 DOM 中移除之前的節點,然后插入新的節點。

renderA:<Header/>
renderB: <Content />
=> [removeNode <Header />], [insertNode<Content/>]

具備這種高級的知識點對於理解為什么React 的差異檢測邏輯既快又精確是很重要的。它對於避開樹形結構大部分的檢測,然后聚焦於似乎相同的部分,提供了啟發。

一個 <Header> 元素與一個 <Content> 元素生成的 DOM結構不太可能一樣。React 將重新創建樹形結構,而不是耗費時間去嘗試匹配這兩個樹形結構。

如果在兩個連續的渲染過程中的相同位置都有一個 <Header> 元素,將會希望生成一個非常相似的DOM 結構,因此值得去做一做匹配。

DOM 節點

當比較兩個DOM 節點的時候,我們查看兩者的屬性,然后能夠找出哪一個屬性隨着時間產生了變化。

renderA:<div id="before"/>
renderB: <div id="after"/>
=> [replaceAttribute id "after"]

React不會把 style 當做難以操作的字符串,而是使用鍵值對對象。這就很容易地僅更新改變了的樣式屬性。

renderA:<div style={{color: 'red'}}/>
renderB: <div style={{fontWeight: 'bold'}}/>
=> [removeStyle color], [addStyle font-weight 'bold']

在屬性更新完畢之后,遞歸檢測所有的子級的屬性。

自定義組件

我們決定兩個自定義組件是相同的。因為組件是狀態化的,不可能每次狀態改變都要創建一個新的組件實例。React利用新組件上的所有屬性,然后在之前的組件實例上調用component[Will/Did]ReceiveProps()。

現在,之前的組件就是可操作了的。它的 render() 方法被調用,然后差異算法重新比較新的狀態和上一次的狀態。

子級優化差異算法(List-wise diff)

問題點(Problematic Case)

為了完成子級更新,React選用了一種很原始的方法。React 同時遍歷兩個子級列表,當發現差異的時候,就產生一次 DOM 修改。

例如在末尾添加一個元素:

renderA:<div><span>first</span></div>
renderB: <div><span>first</span><span>second</span></div>
=> [insertNode <span>second</span>]

在開始處插入元素比較麻煩。React發現兩個節點都是 span,因此直接修改已有 span 的文本內容,然后在后面插入一個新的 span 節點。

renderA:<div><span>first</span></div>
renderB: <div><span>second</span><span>first</span></div>
=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]

有很多的算法嘗試找出變換一組元素的最小操作集合。Levenshtein distance算法能夠找出這個最小的操作集合,使用單一元素插入、刪除和替換,復雜度為O(n2) 。即使使用 Levenshtein算法,不會檢測出一個節點已經移到了另外一個位置去了,要實現這個檢測算法,會引入更加糟糕的復雜度。

鍵(Keys)

為了解決這個看起來很棘手的問題,引入了一個可選的屬性。可以給每個子級一個鍵值,用於將來的匹配比較。如果指定了一個鍵值,React就能夠檢測出節點插入、移除和替換,並且借助哈希表使節點移動復雜度為 O(n)。

renderA:<div><span key="first">first</span></div>
renderB: <div><span key="second">second</span><spankey="first">first</span></div>
=> [insertNode <span>second</span>]

在實際開發中,生成一個鍵值不是很困難。大多數時候,要展示的元素已經有一個唯一的標識了。當沒有唯一標識的時候,可以給組件模型添加一個新的ID 屬性,或者計算部分內容的哈希值來生成一個鍵值。記住,鍵值僅需要在兄弟節點中唯一,而不是全局唯一。

權衡(Trade-offs)

同步更新算法只是一種實現細節,記住這點很重要。React能在每次操作中重新渲染整個應用,最終的結果將會是一樣的。我們定期優化這個啟發式算法來使常規的應用場景更加快速。

在當前的實現中,能夠檢測到某個子級樹已經從它的兄弟節點中移除,但是不能指出它是否已經移到了其它某個地方。當前算法將會重新渲染整個子樹。

由於依賴於兩個預判條件,如果這兩個條件都沒有滿足,性能將會大打折扣。

1、算法將不會嘗試匹配不同組件類的子樹。如果發現正在使用的兩個組件類輸出的DOM 結構非常相似,你或許想把這兩個組件類改成一個組件類。實際上, 這不是個問題。

2、如果沒有提供穩定的鍵值(例如通過Math.random() 生成),所有子樹將會在每次數據更新中重新渲染。通過給開發者設置鍵值的機會,能夠給特定場景寫出更優化的代碼。

 

 

10React(虛擬)DOM 術語

在React 的術語中,有五個核心類型,區分它們是很重要的:

React 元素

React中最主要的類型就是 ReactElement。它有四個屬性:type,props,key 和ref。它沒有方法,並且原型上什么都沒有。

可以通過 React.createElement 創建該類型的一個實例。

var root= React.createElement('div');

為了渲染一個新的樹形結構到DOM 中,你創建若干個 ReactElement,然后傳給React.render 作為第一個參數,同時將第二個參數設為一個正規的DOM 元素(HTMLElement 或者 SVGElement)。不要混淆 ReactElement 實例和 DOM 元素 實例。一個 ReactElement 實例是一個輕量的,無狀態的,不可變的,虛擬的DOM 元素 的表示。是一個虛擬 DOM。

React.render(root,document.body);

要添加屬性到DOM 元素,把屬性對象作為第二個參數傳入 React.render,把子級作為第三個參數傳給 React.render。

var child= React.createElement('li',null,'Text Content');
var root = React.createElement('ul', { className:'my-list' }, child);
React.render(root, document.body);

如果使用React JSX 語法,這些 ReactElement 實例自動創建。所以,如下代碼是等價的:

var root= <ul className="my-list">
             <li>Text Content</li>
           </ul>;
React.render(root, document.body);

工廠

一個 ReactElement 工廠就是一個簡單的函數,該函數生成一個帶有特殊 type 屬性的ReactElement。React有一個內置的輔助方法用於創建工廠函數。事實上該方法就是這樣的:

functioncreateFactory(type){
  return React.createElement.bind(null, type);
}

該函數能創建一個方便的短函數,而不是總調用 React.createElement('div')。

var div= React.createFactory('div');
var root = div({ className:'my-div' });
React.render(root, document.body);

React已經內置了常用 HTML 標簽的工廠函數:

var root= React.DOM.ul({className:'my-list' },
             React.DOM.li(null,'Text Content')
           );

如果使用JSX 語法,就不需要工廠函數了。JSX 已經提供了一種方便的短函數來創建ReactElement 實例。

React 節點

一個 ReactNode 可以是:

  • ReactElement
  • string (又名 ReactText)
  • number (又名 ReactText)
  • ReactNode 實例數組 (又名 ReactFragment)

這些被用作其它 ReactElement 實例的屬性,用於表示子級。實際上它們創建了一個ReactElement 實例樹。 (These areused as properties of other ReactElements to represent children. Effectivelythey create a tree of ReactElements.)

React 組件

在使用React 開發中,可以僅使用 ReactElement 實例,但是,要充分利用 React,就要使用 ReactComponent 來封裝狀態化的組件。

一個 ReactComponent 類就是一個簡單的JavaScript 類(或者說是“構造函數”)。

var MyComponent=React.createClass({
  render:function(){
    ...
  }
});

當該構造函數調用的時候,應該會返回一個對象,該對象至少帶有一個 render 方法。該對象指向一個 ReactComponent 實例。

var component= newMyComponent(props);// never do this

除非為了測試,正常情況下不要自己調用該構造函數。React幫你調用這個函數。

相反,把 ReactComponent 類傳給 createElement,就會得到一個 ReactElement 實例。

var element=React.createElement(MyComponent);

或者使用JSX:

var element= <MyComponent/>;

當該實例傳給 React.render 的時候,React將會調用構造函數,然后創建並返回一個ReactComponent。

var component=React.render(element,document.body);

如果一直用相同的 ReactElement 類型和相同的DOM 元素容器調用 React.render,將會總是返回相同的實例。該實例是狀態化的。

var componentA= React.render(<MyComponent/>,document.body);
varcomponentB=React.render(<MyComponent/>,document.body);
componentA === componentB;// true

這就是為什么不應該創建你自己的實例。相反,在創建之前,ReactElement 是一個虛擬的ReactComponent。新舊 ReactElement 可以比對,從而決定是創建一個新的ReactComponent 實例還是重用已有的實例。

ReactComponent 的 render 方法應該返回另一個 ReactElement,這就允許組件被組裝。(The render method ofa ReactComponent is expected toreturn another ReactElement. This allows these components to becomposed. Ultimately the render resolves intoReactElement with a string tag whichinstantiates a DOM Element instance and inserts it into the document.)

正式的類型定義

入口點(Entry Point)

React.render = (ReactElement, HTMLElement | SVGElement) =>ReactComponent;

節點和元素(Nodes and Elements)

type ReactNode = ReactElement | ReactFragment | ReactText;

type ReactElement = ReactComponentElement | ReactDOMElement;

type ReactDOMElement = {
  type : string,
  props : {
    children : ReactNodeList,
    className : string,
    etc.
  },
  key : string | boolean | number |null,
  ref : string | null
};

type ReactComponentElement<TProps> = {
  type :ReactClass<TProps>,
  props : TProps,
  key : string | boolean | number |null,
  ref : string | null
};

type ReactFragment = Array<ReactNode | ReactEmpty>;

type ReactNodeList = ReactNode | ReactEmpty;

type ReactText = string | number;

type ReactEmpty = null | undefined | boolean;

類和組件(Classes and Components)

type ReactClass<TProps> = (TProps) =>ReactComponent<TProps>;

type ReactComponent<TProps> = {
  props : TProps,
  render : () => ReactElement
};

 

引用原文:http://blog.csdn.net/limm33/article/details/50942808

 

寫博客是為了記住自己容易忘記的東西,另外也是對自己工作的總結,文章可以轉載,無需版權。希望盡自己的努力,做到更好,大家一起努力進步!

如果有什么問題,歡迎大家一起探討,代碼如有問題,歡迎各位大神指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM