一、创建DOM组件
React中Virtual DOM几乎涵盖了所有的原生DOM。React大部分工作都是在Virtual DOM完成的。 ReactDOMComponent针对Virturl DOM主要进行了一下处理:
- 属性的操作,事件的处理
- 子节点的更新
二、如何更新属性
当执行mountComponent时,ReactDOMComponent首先会生成标记和标签。通过createOpenTagMarkupAndPutListeners(transaction) 来处理DOM节点的属性和事件。
- 如果存在事件,则对当前的节点添加事件代理,调用enqueuePutListener(this, propKey, propValue, transaction)
- 如果存在样式,首先对样式合并Object.assign({}, prop.style),然后再调用CSSPropertyOperations.createMarkupForStyles(propValue, this) 创建样式。
- 通过DOMPropertyOperations.createMarkupForProperty(propKey, propValue) 创建属性
- 通过DOMPropertyOperations.crateMarkupForId(this._domID) 创建唯一标识。
源码如下:
//ReactDOMComponent.js
//_createOpenTagMarkupAndPutListeners处理DOM节点的属性和事件
_createOpenTagMarkupAndPutListeners: function(transaction, props) {//声明ret变量,保存一个标签var ret = '<' + this._currentElement.type;//循环拼凑出属性for (var propKey in props) {//判断属性是否是props的实例属性,如果不是则跳出if (!props.hasOwnProperty(propKey)) {continue;}var propValue = props[propKey];if (propValue == null) {continue;}if (registrationNameModules.hasOwnProperty(propKey)) {if (propValue) {//通过enqueuePutListener添加事件代理enqueuePutListener(this, propKey, propValue, transaction);}} else {if (propKey === STYLE) {//如果是样式属性的话则合并样式if (propValue) {propValue = this._previousStyleCopy = Object.assign({}, props.style);}//这里调用CSSPropertyOperations.createMarkupForStylespropValue = CSSPropertyOperations.createMarkupForStyles(propValue, this);}//这里创建属性,var markup = null;if (this._tag != null && isCustomComponent(this._tag, props)) {if (!RESERVED_PROPS.hasOwnProperty(propKey)) {markup = DOMPropertyOperations.createMarkupForCustomAttribute(propKey, propValue);}} else {markup = DOMPropertyOperations.createMarkupForProperty(propKey, propValue);}if (markup) {ret += ' ' + markup;}}}// For static pages, no need to put React ID and checksum. Saves lots of// bytes.//对于静态页,不需要设置react-idif (transaction.renderToStaticMarkup) {return ret;}//这里设置唯一标识if (!this._nativeParent) {ret += ' ' + DOMPropertyOperations.createMarkupForRoot();}ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);return ret;},
复制代码
当执行recevieComponent方法时,ReactDOMComponent会通过this.updateComponent来更新DOM节点属性。
- 先删除不需要的旧属性。
- 如果不需要旧样式,则遍历旧样式集合,并对每个样式进行置空删除
- 如果不需要事件,则将其事件监听的属性去掉,即针对当前的节点取消事件代理:deleteListener(this, propKey)
- 如果旧属性不在新属性集合里时,则需要删除旧属性:DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey)
- 再更新属性
- 如果存在新样式,则将新样式进行合并。
- 如果在旧样式中但是不在新样式中,则清除该样式.
- 如果既在旧样式中也在新样式中,且不相同,则更新该样式styleUpdates[styleName] = nextProp[styleName]
- 如果在新样式中,但不在旧样式中,则直接更新为新样式styleUpdates = nextProp
- 如果存在事件更新,则添加事件监听的属性enqueuePutListener(this, propKey, nextProp, transaction)
- 如果存在新属性,则添加新属性, 或者更新旧的同名属性DOMPropetyOperations.setValueForAttribute(node, propKey, nextProp)
//更新属性_updateDOMProperties: function(lastProps, nextProps, transaction) {var propKey;var styleName;var styleUpdates;//循环遍历旧属性,当旧属性不在新属性集合里面的时候则要删除for (propKey in lastProps) {//如果新属性实例对象上有这个propKey 或者 propKey在旧属性的原型上,则直接跳过,这样剩下的都是不在//新属性集合里面的,则都要删除if (nextProps.hasOwnProperty(propKey) ||!lastProps.hasOwnProperty(propKey) ||lastProps[propKey] == null) {continue;}//删除不需要的样式if (propKey === STYLE) {var lastStyle = this._previousStyleCopy;for (styleName in lastStyle) {if (lastStyle.hasOwnProperty(styleName)) {styleUpdates = styleUpdates || {};styleUpdates[styleName] = '';}}this._previousStyleCopy = null;} else if (registrationNameModules.hasOwnProperty(propKey)) {if (lastProps[propKey]) {//这里额事件监听属性需要去掉监听,针对当前的节点取消事件代理。deleteListener(this, propKey);}} else if (DOMProperty.properties[propKey] ||DOMProperty.isCustomAttribute(propKey)) {//从DOM上删除不必要的属性DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);}}//针对新属性,需要加到DOM节点上for (propKey in nextProps) {var nextProp = nextProps[propKey];var lastProp =propKey === STYLE ? this._previousStyleCopy :lastProps != null ? lastProps[propKey] : undefined;//不在新属性中,或者与旧属性相同,则跳过if (!nextProps.hasOwnProperty(propKey) ||nextProp === lastProp ||nextProp == null && lastProp == null) {continue;}//DOM上写入新样式if (propKey === STYLE) {if (nextProp) {if (__DEV__) {checkAndWarnForMutatedStyle(this._previousStyleCopy,this._previousStyle,this);this._previousStyle = nextProp;}nextProp = this._previousStyleCopy = Object.assign({}, nextProp);} else {this._previousStyleCopy = null;}if (lastProp) {// Unset styles on `lastProp` but not on `nextProp`.//在旧样式中且不在新样式中,且不相同,则更新该样式for (styleName in lastProp) {if (lastProp.hasOwnProperty(styleName) &&(!nextProp || !nextProp.hasOwnProperty(styleName))) {styleUpdates = styleUpdates || {};styleUpdates[styleName] = '';}}// Update styles that changed since `lastProp`.for (styleName in nextProp) {if (nextProp.hasOwnProperty(styleName) &&lastProp[styleName] !== nextProp[styleName]) {styleUpdates = styleUpdates || {};styleUpdates[styleName] = nextProp[styleName];}}} else {//不存在旧样式,则直接写入新样式// Relies on `updateStylesByID` not mutating `styleUpdates`.styleUpdates = nextProp;}} else if (registrationNameModules.hasOwnProperty(propKey)) {if (nextProp) {//添加事件监听属性enqueuePutListener(this, propKey, nextProp, transaction);} else if (lastProp) {//deleteListener(this, propKey);}//添加新的属性,或者更新旧的同名属性} else if (isCustomComponent(this._tag, nextProps)) {if (!RESERVED_PROPS.hasOwnProperty(propKey)) {DOMPropertyOperations.setValueForAttribute(getNode(this),propKey,nextProp);}} else if (DOMProperty.properties[propKey] ||DOMProperty.isCustomAttribute(propKey)) {var node = getNode(this);// If we're updating to null or undefined, we should remove the property// from the DOM node instead of inadvertently setting to a string. This// brings us in line with the same behavior we have on initial render.if (nextProp != null) {DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);} else {//如果更新为null或者undefined,则执行删除属性操作DOMPropertyOperations.deleteValueForProperty(node, propKey);}}}//如果styleUpdates不为空,则设置新样式if (styleUpdates) {CSSPropertyOperations.setValueForStyles(getNode(this),styleUpdates,this);}},
复制代码
三、如何更新子节点
在mountComponent的时候,ReactComponent通过this.createContentMarkup处理DOM节点。 首先,获取节点内容props.dangerourslySetInnerHTML,如果存在子节点,则通过this.mountChildren对子节点进行初始化渲染。
//ReactComponent.js
//_createContentMarkup_createContentMarkup: function(transaction, props, context) {var ret = '';//获取子节点渲染出内容var innerHTML = props.dangerouslySetInnerHTML;if (innerHTML != null) {if (innerHTML.__html != null) {ret = innerHTML.__html;}} else {var contentToUse =CONTENT_TYPES[typeof props.children] ? props.children : null;var childrenToUse = contentToUse != null ? null : props.children;if (contentToUse != null) {// TODO: Validate that text is allowed as a child of this noderet = escapeTextContentForBrowser(contentToUse);} else if (childrenToUse != null) {//对子节点进行初始化渲染var mountImages = this.mountChildren(childrenToUse,transaction,context);ret = mountImages.join('');}}//是否需要换行if (newlineEatingTags[this._tag] && ret.charAt(0) === '\n') {return '\n' + ret;} else {return ret;}},
复制代码
当receiveComponent,ReactComponet会通过updateDOMChildren来更新DOM内容和节点。
预览地址