React源码研习(一)

610 阅读7分钟

入口文件 packages/react/src/React.js

自用笔记,如需浏览请自行思考。内容未补完。

shared/ReactVersion.js

输出react当前版本

shared/ReactSymbols.js

  • react常量命名:REACT_ELEMENT_TYPE 大写字母命名。这种方式也可以作为自己项目的规范。
  • 素数,只能被1和他自身整除的数叫做素数。用js代码实现一个素数生成器
  • Symbol
    • 虽然symbol为es6引入的新数据类型,但值通过Symbol函数生成。因此typeof Symbol === 'function'
    • Symbol.for() 重用symbol值
    • Symbol.iterable对象的Symbol.iterator属性,指向该对象的默认遍历器方法
    • @@iterator 属性的初始值是和 Array.prototype.values 属性的初始值相同的对象。
    • Array.prototype.valuesArray.prototype[Symbol.iterator]的默认实现。

ReactBaseClasses.js

class 基础

class Point{}
typeof Point === 'object'

类中声明所有的方法都定义在prototype

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。es6 class内部申明的属性是不可枚举的

实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。在react中,以下写法有何区别:

class Test extends Component {
    state = { a: '1'}
}
class Test1 extends Component {
    constructor(props){
        super(props)
        this.state = {a: '1'}
    }
}

阮一峰给出的解释是:这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。在实际开发过程中,我们可以自行斟酌使用。

在开发过程中,我们通过__proto__获取实例的原型,但该属性是非标准的,实际开发中,可以使用Object.getPrototypeOf来获取对象的原型

对象内部的get和set是设置在属性的 Descriptor 对象上的Object.getOwnPropertyDescriptor(o, key)

new.target 返回new命令作用于的那个构造函数,该属性一般用在构造函数之中,用于判断构造函数是否通过new方法调用的,如果不是,new.target将会返回undefined

super关键字,调用父类的constructor方法。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

super作为super()函数调用时,返回子对象的实例,等同于Parent.prototype.constructor.call(this),其中的this为子对象的实例。作为super对象调用时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。

Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

回到源码

console可以使用%s作为占位符,输出后续传入的内容。在日常项目的底层代码中,也可以通过此功能提示开发者对应的信息。

...
console.warn(
  '%s(...) is deprecated in plain JavaScript React classes. %s',
  info[0],
  info[1],
);
...

以下代码和react update有关,放到后续分析,可以先看看

function Component(props, context, updater) {
  ...
  this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function(partialState, callback) {
  ...
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

PureComponent 的实现

es5经典的继承实现方式,先声明一个ComponentDummy,并将Component的原型赋给ComponentDummy

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

然后声明PureComponent函数,和Component的实现方式一致

/**
 * Convenience component with default shallow equality check for sCU.
 */
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;
}

将PureComponent的prototype指向一个ComponentDummy的实例

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

注意这里有一个优化点:

Object.assign(pureComponentPrototype, Component.prototype);

这里直接通过Object.assign方法,把Component.prototype上的属性的引用赋值给了PureComponent.prototype。这样在PureComponent内部获取setState这样的方法时,不需要通过原型链(__proto__.__proto__)进行查找

ReactCreateRef.js

这里可以看到createRef的实现:

// RefObject 为对应的type类型
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  if (__DEV__) {
    Object.seal(refObject);
  }
  return refObject;
}

Object.seal()方法封闭一个对象,即refObject,经过 Object.seal的对象,引用不变,但是不能够添加或删除属性,但是可以给current赋值

ReactChildren.js

isArray 封装了Array.isArray方法 hasOwnProperty 封装了Object.prototype.hasOwnProperty方法 可以看到react把一些方法与原生js做了解耦处理,方便后续的维护

ReactElement.js

预设config

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};

react 特殊属性ref和key

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。 返回如下对象:

{
    value, // any
    writable, // true/false
    configurable, // true/false
    enumerable, // true/false
    get, // getter function 访问器函数/ undefined
    set, // setter function 设置器函数/ undefined
    
}

通过defineKeyPropWarningGetterdefineRefPropWarningGetter我们可以看到,key和ref属性的getter访问器函数被重新设置了,因此我们访问这两个属性会得到undefined

安全的转字符串的方式:

const toBeString = '' + o
function jsx(type, config, maybeKey) {
 .... 
 return ReactElement(
    type,
    key,
    ref,
    undefined,
    undefined,
    ReactCurrentOwner.current,
    props,
 )
}
// self and source are DEV only properties.
const ReactElement = function(type, key, ref, self, source, owner, props) {
    const element = {
    // This tag allows us to uniquely identify this as a React Element
    // 用于校验是否是一个reactElement
    $$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;
}

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
function createElement(type, config, children){
    ...
    return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

ReactCurrentOwner.js

当前组建的父组件

const ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Fiber),
};

ReactContext.js

reactContext的工作原理?

function createContext<T>(defaultValue: T): ReactContext<T> {
    const context: ReactContext<T> = {
        $$typeof: REACT_CONTEXT_TYPE,
        // As a workaround to support multiple concurrent renderers, we categorize
        // some renderers as primary and others as secondary. We only expect
        // there to be two concurrent renderers at most: React Native (primary) and
        // Fabric (secondary); React DOM (primary) and React ART (secondary).
        // Secondary renderers store their context values on separate fields.
        _currentValue: defaultValue,
        _currentValue2: defaultValue,
        // Used to track how many concurrent renderers this context currently
        // supports within in a single renderer. Such as parallel server rendering.
        _threadCount: 0,
        // These are circular
        Provider: (null: any),
        Consumer: (null: any),
      };

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

ReactLazy.js

// 自定义promise类型常量
...

function lazyInitializer<T>(payload: Payload<T>): T {
  if (payload._status === Uninitialized) { 
    const ctor = payload._result;
    // 执行import()
    const thenable = ctor();
    // 变更状态为pending
    // Transition to the next state.
    const pending: PendingPayload = (payload: any);
    pending._status = Pending;
    pending._result = thenable;
    // 执行then(onFullfilled, onRejected)方法
    thenable.then(
      moduleObject => {
        if (payload._status === Pending) {
          const defaultExport = moduleObject.default;
          if (__DEV__) {
            if (defaultExport === undefined) {
              console.error(
                'lazy: Expected the result of a dynamic import() call. ' +
                  'Instead received: %s\n\nYour code should look like: \n  ' +
                  // Break up imports to avoid accidentally parsing them as dependencies.
                  'const MyComponent = lazy(() => imp' +
                  "ort('./MyComponent'))",
                moduleObject,
              );
            }
          }
          // Transition to the next state.
          const resolved: ResolvedPayload<T> = (payload: any);
          resolved._status = Resolved;
          resolved._result = defaultExport;
        }
      },
      error => {
        if (payload._status === Pending) {
          // Transition to the next state.
          const rejected: RejectedPayload = (payload: any);
          rejected._status = Rejected;
          rejected._result = error;
        }
      },
    );
  }
  if (payload._status === Resolved) {
    return payload._result;
  } else {
    throw payload._result;
  }
}
function lazyfunction lazy<T>(
  ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
    const payload: Payload<T> = {
    // We use these fields to store the result.
    _status: -1,
    _result: ctor,
  };

  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    // init函数,接受payload作为参数
    _init: lazyInitializer,
  };
  return lazyType
}

ReactForwardRef.js

React.forwardRef 接受渲染函数作为参数。React 将使用 props 和 ref 作为参数来调用此函数。此函数应返回 React 节点。

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

获取函数的参数个数:function.length

  • 使用memo的方式memo(forwardRef(...))
  • forwardRef render functions接受两个参数:props and ref
  • render 必须是一个函数,返回一个React 节点
  • 不支持propTypes or defaultProps

ReactMemo.js

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
    const elementType = {
        $$typeof: REACT_MEMO_TYPE,
        type,
        compare: compare === undefined ? null : compare,
      };
   return elementType;
}

ReactHooks.js

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  if (__DEV__) {
    if (dispatcher === null) {
      console.error(
        'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
          ' one of the following reasons:\n' +
          '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
          '2. You might be breaking the Rules of Hooks\n' +
          '3. You might have more than one copy of React in the same app\n' +
          'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
      );
    }
  }
  // Will result in a null access error if accessed outside render phase. We
  // intentionally don't throw our own error because this is in a hot path.
  // Also helps ensure this is inlined.
  return ((dispatcher: any): Dispatcher);

useContext

 export function useContext<T>(Context: ReactContext<T>): T {
      const dispatcher = resolveDispatcher();
      ...
      return dispatcher.useContext(Context);
  }

useState

  export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 dispatch。

useRef

export function useRef<T>(initialValue: T): {|current: T|} {
  const dispatcher = resolveDispatcher();
  return dispatcher.useRef(initialValue);
}

useEffect

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

  • 同步执行,阻塞渲染
  • 在render前执行
export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useLayoutEffect(create, deps);
}

useCallback

export function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useCallback(callback, deps);
}

useMemo

export function useMemo<T>(
  create: () => T,
  deps: Array<mixed> | void | null,
): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useMemo(create, deps);
}

useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用

export function useImperativeHandle<T>(
  ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
  create: () => T,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useImperativeHandle(ref, create, deps);
}

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

export function useDebugValue<T>(
  value: T,
  formatterFn: ?(value: T) => mixed,
): void {
  if (__DEV__) {
    const dispatcher = resolveDispatcher();
    return dispatcher.useDebugValue(value, formatterFn);
  }
}

useTransition

export function useTransition(): [boolean, (() => void) => void] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useTransition();
}

useDeferredValue

export function useDeferredValue<T>(value: T): T {
  const dispatcher = resolveDispatcher();
  return dispatcher.useDeferredValue(value);
}

ReactCurrentDispatcher.js

const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};

ReactElementValidator.js

开发环境的__DEV__一些校验方法

其他知识点

在 CSS 中,如果你不希望节点成为布局的一部分,则可以使用 display: contents 属性。