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 依赖于此事件来实时地响应用户输入。参考表单获取更多详细信息。
- 表单输入属性,例如 value 和 checked,以及 textarea。这里有更多相关信息。
七、特殊的非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() 生成),所有子树将会在每次数据更新中重新渲染。通过给开发者设置键值的机会,能够给特定场景写出更优化的代码。
10、React(虚拟)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
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,文章可以转载,无需版权。希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!
React生命周期及事件详解的更多相关文章
- React生命周期执行顺序详解
文章内容转载于https://www.cnblogs.com/faith3/p/9216165.html 一.组件生命周期的执行次数是什么样子的??? 只执行一次: constructor.compo ...
- Android四大组件之——Activity的生命周期(图文详解)
转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai 联系方式:JohnTsai.Work@gmail.com [Andro ...
- java多线程并发(二)--线程的生命周期及方法详解
上篇随笔介绍了线程的相关基础知识以及新启线程的几种方法,本片将继续介绍线程的生命周期及方法详解. 一.线程的生命周期 在Thread代码中,线程的状态被分为6种 public enum State { ...
- react第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制)
第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制) 课程目标 深入理解和掌握事件的冒泡及捕获机制 理解react中的合成事件的本质 在react组件中合理的使用原生事件 ...
- maven生命周期和插件详解
生命周期 什么是生命周期? maven的生命周期就是对所有的构建过程进行抽象和统一.maven从大量项目和构建工具中总结了一套高度完善的.易扩展的生命周期.这个生命周期包含项目的清理.初始化.编译.测 ...
- Maven系列第6篇:生命周期和插件详解,此篇看过之后在maven的理解上可以超越同级别90%的人!
maven系列目标:从入门开始开始掌握一个高级开发所需要的maven技能. 这是maven系列第6篇. 整个maven系列的内容前后是有依赖的,如果之前没有接触过maven,建议从第一篇看起,本文尾部 ...
- IOS应用程序生命周期的AppDelegate详解
IOS 中的 AppDelegate.m/h 文件是很重要的呢,因为它是对 Application 的整个生命周期进行管理的. 先明白,每个iPhone应用程序都有一个UIApplication,UI ...
- vue生命周期钩子函数详解
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_35585701/article/ ...
- react生命周期,中间件、性能优化、数据传递、mixin的使用
https://github.com/lulujianglab/blog/issues/34 一.生命周期 1,初始化的执行顺序,初始生命周期执行过程详解 class initSate extends ...
随机推荐
- IOS7--javascriptcore中jscontext使用要注意的一点
在公司一个项目中,用到了highchart做图表显示的组件,这就要用到了javascriptcore,代码就不上了,说说原理. 需求是这样的,通过http请求server csv格式的数据,然后解析, ...
- .Net framework 的浏览器定义文件
.net framework4.5.1之前的版本有一个非常愚蠢的设定, 它为每个浏览器设置了一个浏览器定义文件, 通过正则表达式来匹配浏览器的userAgent, 然后来定义一些功能集. 这种做法有一 ...
- hdu 1159 Common Subsequence 【LCS 基础入门】
链接: http://acm.hdu.edu.cn/showproblem.php?pid=1159 http://acm.hust.edu.cn/vjudge/contest/view.action ...
- java获取地址全路径
String basePath = request.getScheme()+"://"+request.getServerName()+":"+reques ...
- python基础之类的静态方法和类方法
一 静态方法 通常情况下,在类中定义的所有函数都是对象的绑定方法,对象再调用绑定方法时会自动将自己作为参数传递给方法的第一个参数.除此之外还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,但 ...
- flannel源码分析---初始化部分
// main.go 1.func main() ..... 首先调用sm, err := newSubnetManager()创建subnet manager .... 调用ctx, cancel ...
- redis 字符串和集合操作
字符串 redis中的String在在内存中按照一个name对应一个value来存储 set() #在Redis中设置值,默认不存在则创建,存在则修改 r.set('name', 'zhangsan' ...
- python常用方法详解
1,讲序列分解为单独的变量 p=(4,5) x,y=p print(x,y) 如果在分解中想丢弃某些特定的值,可以采用_来进行 data=['A','B','c','d'] _,name,age,_= ...
- python基础教程_学习笔记19:标准库:一些最爱——集合、堆和双端队列
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/signjing/article/details/36201499 标准库:一些最爱 集合.堆和双端队 ...
- Linux运维工程师:30道面试题整理
1.linux 如何挂在 windows 下的共享目录 mount.cifs //192.168.1.3/server /mnt/server -o user=administrator,pass=1 ...