【React源码笔记1】- React.js初探

React | 2020-06-27 11:16:58 595次 2次

在此之前可以先了解 React16 模拟实现系列文章,对 React16 的重构做一个简单梳理,理解其中的调度和 Fiber 架构模式,这样再阅读起来源码就有一个大致的反向,当然源码中要复杂很多。先从 React.js 入手,根据它暴露出的 API 先进行分析。


一、createElement

JSX 代码会被 Babel 编译为 React.createElement,它接收三个参数,分别是 type, config, childrentype 指的是 ReactElement 的类型。

  • 1.字符串 div 或 p 等代表原生 DOM,称为 HostComponent;

  • 2.class 类型继承 Component 或 PureComponent 组件的称为 ClassComponent; 方法就是 FunctionalComponent;

  • 3.原生提供的 Fragment、AsyncMode 等是 Symbol,会特殊处理;

  • 4.其他;

ReactElement.js
/**
 * 
 * React.createElement(
      'div',
      {
        id: '1'
      },
      '1'
    )
    1.字符串 div 或 p 等代表原生 DOM,称为 HostComponent;
    2.class 类型继承 Component 或 PureComponent 组件的称为 ClassComponent; 方法就是 FunctionalComponent;
    3.原生提供的 Fragment、AsyncMode 等是 Symbol,会特殊处理;
    4.其他;
 */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    //是否包含 ref 属性
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    //是否包含 key 属性
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    /**
     * @babel/plugin-transform-react-jsx-self
       @babel/plugin-transform-react-jsx-source
       编译后的两个属性
     */
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        /**
         * const hasOwnProperty = Object.prototype.hasOwnProperty;
         * 特定的自身(非继承)属性
         * 并且排除 key ref __self __source属性
         */
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  //子节点
  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;
  }

  // 组件上的默认属性设置 XXX.defaultProps = {}
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  ...
 
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}


二、ReactElement

createElement 方法中返回一个 ReactElement 方法,这个方法最终创建一个虚拟 dom 对象出来:

ReactElement.js
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    /**
     * react的数据是一个对象,用户可随意构造 dangerouslySetInnerHTML
     * let expectedTextButGotJSON = {
        type: 'div',
        props: {
          dangerouslySetInnerHTML: {
            __html: ' put your exploit here '
          }
        },
      };
      let message = { text: expectedTextButGotJSON };
      <p>
        {message.text}
      </p>
     * 因为如果服务器有一个漏洞,允许用户存储任意JSON对象则可能会有 XSS 攻击,
     * 而json是无法传递 Symbol 格式数据
     * 不支持此属性的则设置为 0xeac7 因为看起来有点像“React”。
     */
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  ...

  return element;
};


三、Component

我们在编写组件时候一般会继承自 Component(函数组件除外),提供给我们 props、state、setState 等属性方法,也可以使用 React 中的生命周期函数,其实它的实现很简单:

ReactBaseClasses.js
/**
 * 基类组件
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  //初始是一个空对象
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
//判断类组件标识
Component.prototype.isReactComponent = {};

//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.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

//强制更新,调用forceUpdate()会导致组件跳过shouldComponentUpdate(),直接调用render()。
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

...

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

//会自动浅比较当前组件是否需要更新
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
//PureComponent 继承自 Component
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
//修正PureComponent中的constructor指向
pureComponentPrototype.constructor = PureComponent;
/**
 * pureComponentPrototype此时自身没有,__proto__ 上才有Component.prototype的方法,所以需要拷贝一下
 * 补充: Object.assing 是不能拷贝到继承或原型上的方法的 
 * 实现:var c2 = Object.create(
 *                  Object.getPrototypeOf(c),
 *                  Object.getOwnPropertyDescriptors(c)
 *       );
 */
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

export {Component, PureComponent};

这里面重要的一件事就是创建了 updater 这个对象,后续再分析这个,其余的就是使得 PureComponent 继承自 Component 方法并暴露出去。


四、createRef & forwardRef

//暴露出的就是一个对象而已
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  ...
  return refObject;
}

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  ...
  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}

这里的 $$typeof 和上面的 ReactElement 中创建的并不是同一个,而是给这个组件挂载上一个此属性,放在了 type 里面去。


五、createContext

这里只是简单看下如何创建的,真正的实现需要等后面更新调度时候才会执行。

/**
 * calculateChangedBits
 * 当 changedBits = 0,将不再触发更新。
 * 而在 Consumer 中有一个不稳定的 props,unstable_observedBits,
 * 若 Provider 的changedBits & observedBits = 0,也将不触发更新 
 */
export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
  if (calculateChangedBits === undefined) {
    calculateChangedBits = null;
  } else {
    ...
  }

  const context: ReactContext<T> = {
    $$typeof: REACT_CONTEXT_TYPE,
    _calculateChangedBits: calculateChangedBits,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: (null: any),
    Consumer: (null: any),
  };

  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };

  ...

  if (__DEV__) {
    ...
  } else {
    //指向自己,这样渲染时候会取到上面定义的 _currentValue,作为回调参数传给组件使用
    context.Consumer = context;
  }
  ...
  return context;
}


六、concurrent & flushSync

concurrent  是利用 fiber 的结构,可以进行任务的高低优先级分类执行且可中断可恢复,默认它的所有子节点都是最低优先级的,可以通过 flushSync 来控制为最高优先级被执行。flushSync 来自 react-dom

legacy 模式(默认)

blocking 模式

concurrent 模式

ReactDOM.createBlockingRoot(rootNode).render(<App />)  //blocking 
ReactDOM.createRoot(document.getElementById('root')).render(<App />); //

调用:控制下 tag 标识就可以了,后面判断区分不同模式
this._internalRoot = createRootImpl(container, tag, options);

//render方法和普通的render有区别,具体看第三节的render方法
ReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render = function(
  children: ReactNodeList,
): void {
  const root = this._internalRoot;
  ...
  //这里直接调用了updateContainer,普通render使用 unbatchedUpdates包裹进行up
  updateContainer(children, root, null, null);
};


七、Suspense & Fragment

Suspence 使用时注意如果结果是 promise 则使用 throw 来抛出,其他的直接 return 即可,这些东西都是一堆 symbol 标识。

const React = {
  ...
 REACT_FRAGMENT_TYPE as Fragment,
  REACT_PROFILER_TYPE as Profiler,
  REACT_STRICT_MODE_TYPE as StrictMode,
  REACT_SUSPENSE_TYPE as Suspense,
  ...
};


八、memo & lazy

export default function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  ...
  return {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
}
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
  return {
    $$typeof: REACT_LAZY_TYPE,
    _ctor: ctor,
    // thenable对象返回的状态 | pendding -> -1
    _status: -1,
    //记录thenable对象 resolve之后的结果
    _result: null,
  };
}

可以看到某些方法直接就是一个对象,这证明 react 中只是单纯的定了节点类型,用不同的 typeof 区分,那么如何是作为组件被使用的呢?这个问题需要留到后面调度渲染时候进行再次分析。

本片介绍了一些常用的 api,还有一部分不常用的没有一一列举,其中还有一个不常用但非常有意思的 children 方法,等下一篇再进行分析。


2人赞

分享到: