React源码学习笔记---基本Api的源码

createElement

使用过react开发的都知道,咱们直接写的react element会被babel转换为react.createElement,具体以下图所示,那么先来看一下createElement的源码react

dom元素标签直接显示是字符串数组

而自定义组件显示的是变量 bash

  • createElement传递三个参数分别为type(元素类型)、config(也就是props)、children(子元素)
export function createElement(type, config, children){}
复制代码
  • 首先是对config的处理
if (config != null) {
    // key和ref单独处理,所以他们不会出如今props上
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    // 赋值操做
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    // 遍历配置,把内建的几个属性剔除后赋值到 props 中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
复制代码
  • 而后是对children的处理

若是子元素只有一个,直接props.children赋值,若是大于一个,用一个数组存储,而后赋值到props.childrenbabel

const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
      }
复制代码
  • 接下来返回一个ReactElement
return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props,
     );
复制代码
  • ReactElement的源码
const ReactElement = function(type, key, ref, self, source, owner, props) {
      const element = {
        // This tag allows us to uniquely identify this as a React Element
        $$typeof: REACT_ELEMENT_TYPE, // 元素标识
        // Built-in properties that belong on the element
        type: type,
        key: key,
        ref: ref,
        props: props,
        // Record the component responsible for creating this element.
        _owner: owner,
      };
      // ...
      
      return element
    }  
复制代码

React-component(ReactBaseClasses.js)

这里面主要是Component和pureComponent的实现app

Component

  • 首先是Component源码

函数Component有三个参数,props、context、updater(ReactDOM里面用于更新状态等的方法),props和context平时都是能够用到的,this.refs当咱们使用最先的字符串ref的时候能够这么获取到。dom

function Component(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      // We initialize the default updater but the real one gets injected by the
      this.updater = updater || ReactNoopUpdateQueue;
    }
复制代码
  • Component原型上的setState方法
Component.prototype.setState = function(partialState, callback) {
      // 警告的 没啥用        
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      // 把state和callback回调函数 交给updater.enqueueSetState来处理
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    // 强制更新
    /*
        默认状况下,当组件的state或props改变时,组件将从新渲染。若是你的render()方法依赖于一些其余的数据,你能够告诉React组件须要经过调用forceUpdate()从新渲染。
        调用forceUpdate()会致使组件跳过shouldComponentUpdate(),直接调用render()。这将触发组件的正常生命周期方法,包括每一个子组件的shouldComponentUpdate()方法。
        forceUpdate就是从新render。有些变量不在state上,当时你又想达到这个变量更新的时候,刷新render;或者state里的某个变量层次太深,更新的时候没有自动触发render。这些时候均可以手动调用forceUpdate自动触发render
    */
    Component.prototype.forceUpdate = function(callback) {
      this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
    };
复制代码

pureComponent

pureComponent是继承自Component,只不过多了一个用于区分的isPureReactComponent属性ide

// 几乎是和Component是一致的
    
    function PureComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    // 这里是相似于Object.create方式的继承
    function ComponentDummy() {}
    ComponentDummy.prototype = Component.prototype;
    const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
    pureComponentPrototype.constructor = PureComponent;
    
    // 赋值prototype上的属性
    // Avoid an extra prototype jump for these methods.
    Object.assign(pureComponentPrototype, Component.prototype);
    
    // 经过这个变量区别下普通的 Component
    pureComponentPrototype.isPureReactComponent = true;
复制代码

refs

refs的三种建立方式:函数

  • 字符串方式(不推荐)
  • ref={input => this.input = input}
  • React.createRef

React.createRef的源码

返回一个对象,经过对象的current来获取refoop

import type {RefObject} from 'shared/ReactTypes';
    
    // an immutable object with a single mutable value
    
    export function createRef(): RefObject {
      const refObject = {
        current: null,
      };
      if (__DEV__) {
        Object.seal(refObject); // 封闭一个对象,阻止添加新属性并将全部现有属性标记为不可配置
      }
      return refObject;
    }
复制代码

React.forwardRef的源码

render函数多传了一个ref,注意这里的$$typeof又不一样了。ui

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {

  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
复制代码

ReactChildren

这部分的源码主要是针对React.Children的,而且里面有一个对象池的概念。主要看一下React.children.map的处理

  • mapChildren
function mapChildren(children, func, context) {
      if (children == null) {
        return children;
      }
      // 遍历出来的元素会存放到result中,最终返回
      const result = [];
      mapIntoWithKeyPrefixInternal(children, result, null, func, context);
      return result;
    }
复制代码
  • mapIntoWithKeyPrefixInternal
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
      // 处理 key
      let escapedPrefix = '';
      if (prefix != null) {
        escapedPrefix = escapeUserProvidedKey(prefix) + '/';
      }
      // getPooledTraverseContext 和 releaseTraverseContext 是配套的函数
      // 用处其实很简单,就是维护一个大小为 10 的对象重用池
      // 每次从这个池子里取一个对象去赋值,用完了就将对象上的属性置空而后丢回池子
      const traverseContext = getPooledTraverseContext(
        array,
        escapedPrefix,
        func,
        context,
      );
      // 遍历全部children节点
      traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
      // 释放对象池
      releaseTraverseContext(traverseContext);
    }
复制代码
  • getPooledTraverseContext / releaseTraverseContext
const POOL_SIZE = 10;
    const traverseContextPool = [];
    function getPooledTraverseContext(
      mapResult,
      keyPrefix,
      mapFunction,
      mapContext,
    ) {
      if (traverseContextPool.length) { // 数组中存在 直接取出来一个 赋值
        const traverseContext = traverseContextPool.pop();
        traverseContext.result = mapResult;
        traverseContext.keyPrefix = keyPrefix;
        traverseContext.func = mapFunction;
        traverseContext.context = mapContext;
        traverseContext.count = 0;
        return traverseContext;
      } else { // 没有直接返回对象
        return {
          result: mapResult,
          keyPrefix: keyPrefix,
          func: mapFunction,
          context: mapContext,
          count: 0,
        };
      }
    }
    function releaseTraverseContext(traverseContext) { // 属性值置为null,而后放入到池子里面
      traverseContext.result = null;
      traverseContext.keyPrefix = null;
      traverseContext.func = null;
      traverseContext.context = null;
      traverseContext.count = 0;
      if (traverseContextPool.length < POOL_SIZE) {
        traverseContextPool.push(traverseContext);
      }
    }
复制代码
  • traverseAllChildren / traverseAllChildrenImpl

callback为mapSingleChildIntoContext

function traverseAllChildren(children, callback, traverseContext) {
      if (children == null) {
        return 0;
      }
      return traverseAllChildrenImpl(children, '', callback, traverseContext);
    }
复制代码
  • traverseAllChildrenImpl
function traverseAllChildrenImpl(
      children,
      nameSoFar,
      callback,
      traverseContext,
    ) {
      // 这个函数核心做用就是经过把传入的 children 数组经过遍历摊平成单个节点
      // 而后去执行 mapSingleChildIntoContext
    
      // 开始判断 children 的类型
      const type = typeof children;
    
      if (type === 'undefined' || type === 'boolean') {
        // All of the above are perceived as null.
        children = null;
      }
    
      let invokeCallback = false;
    
      if (children === null) {
        invokeCallback = true;
      } else {
        switch (type) {
          case 'string':
          case 'number':
            invokeCallback = true;
            break;
          case 'object':
            switch (children.$$typeof) {
              case REACT_ELEMENT_TYPE:
              case REACT_PORTAL_TYPE:
                invokeCallback = true;
            }
        }
      }
      // 若是 children 是能够渲染的节点的话,就直接调用 callback
      // callback 是 mapSingleChildIntoContext
      if (invokeCallback) {
        callback(
          traverseContext,
          children,
          // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows.
          nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
        );
        return 1;
      }
    
      // nextName 和 nextNamePrefix 都是在处理 key 的命名
      let child;
      let nextName;
      let subtreeCount = 0; // Count of children found in the current subtree.
      const nextNamePrefix =
        nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
    
      // 节点是数组的话,就开始遍历数组,而且把数组中的每一个元素再递归执行 traverseAllChildrenImpl
      // 这一步操做也用来摊平数组的
      // React.Children.map(this.props.children, c => [[c, c]])
      // c => [[c, c]] 会被摊平为 [c, c, c, c]
      
      if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
          child = children[i];
          nextName = nextNamePrefix + getComponentKey(child, i);
          subtreeCount += traverseAllChildrenImpl(
            child,
            nextName,
            callback,
            traverseContext,
          );
        }
      } else {
        // 不是数组的话,就看看 children 是否能够支持迭代
        // 就是经过 obj[Symbol.iterator] 的方式去取
        const iteratorFn = getIteratorFn(children);
        // 只有取出来对象是个函数类型才是正确的
        if (typeof iteratorFn === 'function') {
          // 而后就是执行迭代器,重复上面 if 中的逻辑了
          const iterator = iteratorFn.call(children);
          let step;
          let ii = 0;
          while (!(step = iterator.next()).done) {
            child = step.value;
            nextName = nextNamePrefix + getComponentKey(child, ii++);
            subtreeCount += traverseAllChildrenImpl(
              child,
              nextName,
              callback,
              traverseContext,
            );
          }
复制代码
  • mapSingleChildIntoContext
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
      const {result, keyPrefix, func, context} = bookKeeping;
      // func 就是咱们在 React.Children.map(this.props.children, c => c)
      // 中传入的第二个函数参数
      let mappedChild = func.call(context, child, bookKeeping.count++);
      // 判断函数返回值是否为数组
      // React.Children.map(this.props.children, c => [c, c])
      // 对于 c => [c, c] 这种状况来讲,每一个子元素都会被返回出去两次
      // 也就是说假若有 2 个子元素 c1 c2,那么经过调用 React.Children.map(this.props.children, c => [c, c]) 后
      // 返回的应该是 4 个子元素,c1 c1 c2 c2
      if (Array.isArray(mappedChild)) {
        // 是数组的话就回到最早调用的函数中
        // 而后回到以前 traverseAllChildrenImpl 摊平数组的问题
        // 假如 c => [[c, c]],当执行这个函数时,返回值应该是 [c, c]
        // 而后 [c, c] 会被当成 children 传入
        // traverseAllChildrenImpl 内部逻辑判断是数组又会从新递归执行
        // 因此说即便你的函数是 c => [[[[c, c]]]]
        // 最后也会被递归摊平到 [c, c, c, c]
        mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
      } else if (mappedChild != null) {
        // 不是数组且返回值不为空,判断返回值是否为有效的 Element
        // 是的话就把这个元素 clone 一遍而且替换掉 key
        if (isValidElement(mappedChild)) {
          mappedChild = cloneAndReplaceKey(
            mappedChild,
            // Keep both the (mapped) and old keys if they differ, just as
            // traverseAllChildren used to do for objects as children
            keyPrefix +
              (mappedChild.key && (!child || child.key !== mappedChild.key)
                ? escapeUserProvidedKey(mappedChild.key) + '/'
                : '') +
              childKey,
          );
        }
        result.push(mappedChild);
      }
    }
复制代码
  • cloneAndReplaceKey

替换新key,返回元素

export function cloneAndReplaceKey(oldElement, newKey) {
      const newElement = ReactElement(
        oldElement.type,
        newKey,
        oldElement.ref,
        oldElement._self,
        oldElement._source,
        oldElement._owner,
        oldElement.props,
      );
      return newElement;
    }
复制代码

ReactChildren的流程图

相关文章
相关标签/搜索