使用VDom的几大理由
1.Dom操作非常昂贵,真实Dom含有的信息非常多
var div = document.createElement('div');
var result = '';
for(let item in div){ result += '|' + item};
console.log(result)
// "|align|title|lang|translate|dir|dataset|hidden|tabIndex|accessKey|draggable|spellcheck|autocapitalize|contentEditable|isContentEditable|inputMode|offsetParent|offsetTop|offsetLeft|offsetWidth|offsetHeight|style|innerText|outerText|oncopy|oncut|onpaste|onabort|onblur|oncancel|oncanplay|oncanplaythrough|onchange|onclick|onclose|oncontextmenu|oncuechange|ondblclick|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|ondurationchange|onemptied|onended|onerror|onfocus|oninput|oninvalid|onkeydown|onkeypress|onkeyup|onload|onloadeddata|onloadedmetadata|onloadstart|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|onmousewheel|onpause|onplay|onplaying|onprogress|onratechange|onreset|onresize|onscroll|onseeked|onseeking|onselect|onstalled|onsubmit|onsuspend|ontimeupdate|ontoggle|onvolumechange|onwaiting|onwheel|onauxclick|ongotpointercapture|onlostpointercapture|onpointerdown|onpointermove|onpointerup|onpointercancel|onpointerover|onpointerout|onpointerenter|onpointerleave|onselectstart|onselectionchange|nonce|click|focus|blur|namespaceURI|prefix|localName|tagName|id|className|classList|slot|attributes|shadowRoot|assignedSlot|innerHTML|outerHTML|scrollTop|scrollLeft|scrollWidth|scrollHeight|clientTop|clientLeft|clientWidth|clientHeight|attributeStyleMap|onbeforecopy|onbeforecut|onbeforepaste|onsearch|previousElementSibling|nextElementSibling|children|firstElementChild|lastElementChild|childElementCount|onwebkitfullscreenchange|onwebkitfullscreenerror|setPointerCapture|releasePointerCapture|hasPointerCapture|hasAttributes|getAttributeNames|getAttribute|getAttributeNS|setAttribute|setAttributeNS|removeAttribute|removeAttributeNS|hasAttribute|hasAttributeNS|toggleAttribute|getAttributeNode|getAttributeNodeNS|setAttributeNode|setAttributeNodeNS|removeAttributeNode|closest|matches|webkitMatchesSelector|attachShadow|getElementsByTagName|getElementsByTagNameNS|getElementsByClassName|insertAdjacentElement|insertAdjacentText|insertAdjacentHTML|requestPointerLock|getClientRects|getBoundingClientRect|scrollIntoView|scrollIntoViewIfNeeded|animate|computedStyleMap|before|after|replaceWith|remove|prepend|append|querySelector|querySelectorAll|webkitRequestFullScreen|webkitRequestFullscreen|onfullscreenchange|onfullscreenerror|scroll|scrollTo|scrollBy|createShadowRoot|getDestinationInsertionPoints|requestFullscreen|ELEMENT_NODE|ATTRIBUTE_NODE|TEXT_NODE|CDATA_SECTION_NODE|ENTITY_REFERENCE_NODE|ENTITY_NODE|PROCESSING_INSTRUCTION_NODE|COMMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE|DOCUMENT_FRAGMENT_NODE|NOTATION_NODE|DOCUMENT_POSITION_DISCONNECTED|DOCUMENT_POSITION_PRECEDING|DOCUMENT_POSITION_FOLLOWING|DOCUMENT_POSITION_CONTAINS|DOCUMENT_POSITION_CONTAINED_BY|DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|nodeType|nodeName|baseURI|isConnected|ownerDocument|parentNode|parentElement|childNodes|firstChild|lastChild|previousSibling|nextSibling|nodeValue|textContent|hasChildNodes|getRootNode|normalize|cloneNode|isEqualNode|isSameNode|compareDocumentPosition|contains|lookupPrefix|lookupNamespaceURI|isDefaultNamespace|insertBefore|appendChild|replaceChild|removeChild|addEventListener|removeEventListener|dispatchEvent"
2.将Dom的比较放在js里,提高效率
可以把js和dom看作两个独立的岛屿,通过架桥来联接沟通,但架桥的成本非常昂贵,所以采取虚拟DOM,将Dom的比较放在js里,来提高效率。
准备工作
去github下载源码
目录介绍
react和react-dom
我们主要学习的部分是 react和react-dom。看看react的文件;其index.js就是引入了src/react;而src/React里的代码不超过100行。react本身源码是非常少的,但是react和react-dom代码加起来 压缩以后都有将近100k;所以大部分代码量在react-dom上;react本身是一个定义节点以及表现行为的包,具体dom如何渲染,更新,都是和平台相关的,在react-dom和react-native里不一样,所以这部分代码都是放在平台相关的逻辑里面。
const React = require('./src/React');
module.exports = React.default || React;
Flow Type
Facebook 开源的静态检查方案 Flow,类似TypeScript,来指定类型,提升代码可读性,稳定性,在编译的时候就能暴露出的bug。
import type {ReactNodeList} from 'shared/ReactTypes';
JSX到JS的转换
这个网站可以让你清晰看到jsx到js的转换babeljs.io/repl
大写-->变量;小写-->标签
看源码
ReactElement
主要看 createElement 和 ReactElement;
ReactElement
通过createElement
创建,createElement该方法需要传入三个参数:
- type:用来标识 我们ele的类型,用于判断如何创建节点
- config : 4个保留名称(key,ref,__self,__source ),其余的都会被加到props里面
- children:可以有不止一个节点,这些节点会被转移到新分配的props对象,props.children
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
// 用来标识 我们ele的类型,我们在创建element的时候 都是用 React.createElement 那么所有的ele都是REACT_ELEMENT_TYPE
// 大部分都是 REACT_ELEMENT_TYPE,但也有不是的(感兴趣的自己研究)
// ReactDOM.createPortal的时候是REACT_PORTAL_TYPE,不过他不是通过createElement创建的,所以他应该也不属于ReactElement
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type, //节点类型,原生标签,class Component 还是 function Component,还是react提供的 Fragment: REACT_FRAGMENT_TYPE,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
retrun element
}
React.Component
- component
看了ReactBaseClasses.js里的component;觉得没啥内容;核心的setState 是调用的 react-dom里的enqueueSetState;是因为react-dom和react-native的更新方式不一样;所以采取传参的方式 来接受平台的逻辑;核心内容还得继续看react-dom
- PureComponent
PureComponent就是继承了Component;但加上了isPureReactComponent = true;
创建Ref的三种的方式
只有有实例的,才能有效,ref的对象可以是 dom节点的实例,可以class component的实例,如果是 function component 是会失败的,拿到是undefined,但不会报错。
- string ref,
<p ref="stringRef">span1</p>
// 废弃 - function,
{ele => (this.methodRef = ele)}
- createRef,
this.objRef = React.createRef()
创建{ current: null },等实例渲染以后,就会挂载到current上
这三个方法,只有createRef是原生的,在源码里如下图所示:创建了{ current: null },并return出来 ;这里我们保留一个疑问?创建的ref如何挂载,后期我们继续看。
const refObject = { current: null };
return refObject
export default class RefDemo extends React.Component {
constructor() {
super()
this.objRef = React.createRef()
// this.objRef:{ current: null },等实例渲染以后,就会挂载到current上
}
componentDidMount() {
// console.log(`span1: ${this.refs.ref1.textContent}`)
// console.log(`span2: ${this.ref2.textContent}`)
// console.log(`span3: ${this.ref3.current.textContent}`)
setTimeout(() => {
this.refs.stringRef.textContent = 'string ref got'
this.methodRef.textContent = 'method ref got'
this.objRef.current.textContent = 'obj ref got'
}, 1000)
}
render() {
return (
<>
<p ref="stringRef">span1</p>
<p ref={ele => (this.methodRef = ele)}>span3</p>
<p ref={this.objRef}>span3</p>
</>
)
}
}
forward-ref
对于 funciton component是没办法用ref的;function component是没有 this实例的,forwardRef里的ref只能指向dom,指向组件是无法生效的
在react16之前,通常获取子组件的DOM的方式
- 父组件定义一个ref函数,将ref赋值给Child component
- Child Component中用ref获取到需要的DOM元素
- Child Component中定义一个getElement的方法,然后将该DOM元素返回
- 父组件通过获取Child component再调用getElement即可
//Comp.js
class Comp extends Component { constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.div = React.createRef()
}
render() {
return (
<div>
<Button ref={this.div}/>
</div>
);
}
}
//TargetComponent.js
import React, {Component} from 'react';
export default class TargetComponent extends Component { constructor(props) {
super(props);
this.button = React.createRef();
this.getButton = this.getButton.bind(this);
}
getButton() {
return this.button
}
render() {
return (
<div>
<button ref={this.button}>this is a button</button>
</div>
);
}
}
在react16之后,使用
import React from 'react'
export default class Comp extends React.Component {
constructor() {
super()
this.ref = React.createRef()
}
componentDidMount() {
this.ref.current.value = 'ref get input'
}
render() {
return <TargetComponent ref={this.ref} />
}
}
const TargetComponent = React.forwardRef((props, ref) => (
<input type="text" ref={ref} />
))
forwardRef 应用到React组件的错误示例:
const A=React.forwardRef((props,ref)=><B {...props} ref={ref}/>)
这就是我之前经常犯的错误, 这里的ref是无法生效的。
前面提到ref必须指向dom元素,那么正确方法就应用而生:
const A=React.forwardRef((props,ref)=>(
<div ref={ref}>
<B {...props} />
</div>
))
介绍了那么多 forward的用途,我们看一下源码,返回了一个对象,其中?typeof: REACT_FORWARD_REF_TYPE, 指定ReactElement 的type;而不是ReactElement的?typeof,?typeof依旧是REACT_ELEMENT_TYPE
Context
- childContextTypes
- createContext