react源码解析

559 阅读5分钟

使用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的类型,用于判断如何创建节点
       对type里的defaultProps的处理,转到props上
  • config : 4个保留名称(key,ref,__self,__source ),其余的都会被加到props里面
  • children:可以有不止一个节点,这些节点会被转移到新分配的props对象,props.children
返回ReactElement ,ReactElement含有以下信息
这些信息对于后期构建应用的树结构是非常重要的,而React通过提供这种类型的数据,来脱离平台的限制

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;

通过isPureReactComponent来判断PureComponent

创建Ref的三种的方式

只有有实例的,才能有效,ref的对象可以是 dom节点的实例,可以class component的实例,如果是 function component 是会失败的,拿到是undefined,但不会报错。

  1. string  ref<p ref="stringRef">span1</p>   // 废弃
  2. function,  {ele => (this.methodRef = ele)}
  3. createRefthis.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

ReactElement 的type用来标识 我们ele的类型,在后续的挂载,解析中非常重要


Context

  • childContextTypes
  • createContext