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

209 阅读5分钟

createElement

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

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

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

  • 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.children

      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的实现

Component

  • 首先是Component源码

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

    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属性

    // 几乎是和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来获取ref

    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又不同了。

    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的流程图