React18.2.0源码解析1:React核心API

作者:renzp94
时间:2023-06-06 11:40:13

一般使用React常用的两个包:react、react-dom,其中react是核心包,无关平台,react-dom是web平台的render包。

react包中导出的API如下:

export {
  /** 
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, React内部使用,禁止外部使用
  act as unstable_act
  Children
  Component
  */
  Fragment,
  /** 
  Profiler,
  PureComponent
  StrictMode,
  */
  Suspense,
  /** 
  SuspenseList,
  cloneElement,
  */
  createContext,
  /** 
  createElement,
  createFactory
  createMutableSource,
  createRef,
  createServerContext,
  */
  forwardRef,
  /** 
  isValidElement,
  */
  lazy,
  memo,
  startTransition,
  /** 
  unstable_Cache,
  unstable_DebugTracingMode,
  unstable_LegacyHidden,
  unstable_Offscreen,
  unstable_Scope,
  unstable_TracingMarker,
  unstable_getCacheSignal,
  unstable_getCacheForType,
  unstable_useCacheRefresh,
  useId,
  */
  useCallback,
  useContext,
  /** 
  useDebugValue,
  */
  useDeferredValue,
  useEffect,
  useImperativeHandle,
  useInsertionEffect,
  useLayoutEffect,
  useMemo,
  useMutableSource,
  useSyncExternalStore,
  useReducer,
  useRef,
  useState,
  useTransition,
  /** 
  version,
  */
} from './src/React';

上述代码注释掉的API不是重要的,会在最后大概说明用法。

Fragment

用于包裹没有父节点的多个元素,简写:<></>

<Fragment>
  <div>hello</div>
  <div>react</div>
</Fragment>
// 或者
<>
  <div>hello</div>
  <div>react</div>
</>
源码
/** 
 packages/react/src/React.js 
*/
export { 
  ...
  REACT_FRAGMENT_TYPE as Fragment,
  ...
}

/** 
 packages/shared/ReactSymbols.js
*/
export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');

从源码中可以看出,Fragment只是一个类型,用于后续在reconciler(协调器)Fiber(常说的虚拟DOM)判断是否为Fragment从而进行相应的操作。

Suspense

用于组件懒加载展示loading,通常与React.lazy一起使用

<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>
源码
/** 
 packages/react/src/React.js 
*/
export { 
  ...
  REACT_SUSPENSE_TYPE as Suspense,
  ...
}

/** 
 packages/shared/ReactSymbols.js
*/
export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');

createContext

创建一个context(上下文),用于组件间传递数据,通常和useContext一起使用

const ThemeContext = createContext('dark')

const App = () => {
  return (
    <ThemeContext.Provider value="dark">
      <SomeComponent />
    </ThemeContext.Provider>
  )
}

const SomeComponent = () => {
  const theme = useContext(ThemeContext)

  return <div>SomeComponent:{theme}</div>
}
源码
export function createContext<T>(defaultValue: T): ReactContext<T> {

  const context: ReactContext<T> = {
    // fiber类型
    $$typeof: REACT_CONTEXT_TYPE,
    // 作为支持多个并发渲染器的解决方法
    // 一个渲染器为主渲染器,另一个渲染器为辅渲染器
    // 最多有两个并发渲染器:
    // React Native (primary) 和 Fabric (secondary)、
    // React DOM (primary) 和 React ART (secondary)
    // 主渲染器将上下文值存储到_currentValue中
    // 辅渲染器将上下文值存储到_currentValue2中
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    // 用于跟踪此上下文有多少并发渲染器
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
    _defaultValue: (null: any),
    _globalName: (null: any),
  };

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

  // 用于警告判断
  let hasWarnedAboutUsingNestedContextConsumers = false;
  let hasWarnedAboutUsingConsumerProvider = false;
  let hasWarnedAboutDisplayNameOnConsumer = false;
  // 开发环境做了一些处理
  if(__DEV__){
    ...
  }
  else {
    context.Consumer = context;
  }

  if (__DEV__) {
    ...
  }

  return context;
}

创建一个ReactContext对象,提供ProviderConsumer组件

__DEV__代码主要是用法错误提醒:<Context.Consumer.Provider> ➡️ <Context.Provider><Context.Consumer.Consumer> ➡️ <Context.Consumer>

forwardRef

用于透传ref,也通过useImperativeHandle将ref将组件内部的方法暴露。

const App = () => {
  const ref = useRef()

  useEffect(() => {
    ref.current.onTest()
  },[])

  return <SomeComponent ref={ref} />
}

const SomeComponent = forwardRef((props,ref) => {
  const onTest = () => {
    console.log('SomeComponent test')
  }

  useImperativeHandle(ref,() => {
    return {
      onTest
    }
  },[])

  return <div>SomeComponent</div>
})
源码
export function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  if (__DEV__) {
    ...
  }

  const elementType = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
  if (__DEV__) {
    ...
  }
  return elementType;
}

创建一个REACT_FORWARD_REF_TYPE类型对象

__DEV__代码主要是用法错误提醒:forwardRef(memo(...)) ➡️ memo(forwardRef(...))必须一个render function如果只有一个参数会提示是否遗忘了ref参数不支持propTypes或defaultProps属性

lazy

用于懒加载组件

源码
export function lazy<T>(
  ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
  const payload: Payload<T> = {
    // We use these fields to store the result.
    _status: Uninitialized,
    _result: ctor,
  };

  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer,
  };

  if (__DEV__) {
    ...
  }

  return lazyType;
}

创建一个REACT_LAZY_TYPE类型对象

__DEV__代码主要是用法错误提醒:不支持propTypes或defaultProps属性

memo

props未更改时跳过重新渲染组件,用于性能优化。

const SomeComponent = memo(() => {
  return <div>SomeComponent</div>
})
源码
export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  if (__DEV__) {
    ...
  }
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
  if (__DEV__) {
    ...
  }
  return elementType;
}

创建一个REACT_MEMO_TYPE类型对象

__DEV__代码主要是用法错误提醒:memo接受的参数必须时一个组件

startTransition

不阻塞UI的前提下更新state

const Counter = () => {
  const [count,setCount] = useState(0)
  const onAdd = () => {
    startTransition(() => {
      setCount(val => val + 1)
    })
  }
  const onSub = () => {
    startTransition(() => {
      setCount(val => val - 1)
    })
  }

  return (
    <button onClick={onSub}>-</button>
    <div>{count}</div>
    <button onClick={onAdd}>+</button>
  )
}
源码
/**
 const ReactCurrentBatchConfig: BatchConfig = {
  transition: null,
 };
 */
import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
/**
 * export const enableTransitionTracing = false;
 */
import {enableTransitionTracing} from 'shared/ReactFeatureFlags';

export function startTransition(
  scope: () => void,
  options?: StartTransitionOptions,
) {
  const prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = {};
  const currentTransition = ReactCurrentBatchConfig.transition;

  if (__DEV__) {
    ReactCurrentBatchConfig.transition._updatedFibers = new Set();
  }

  if (enableTransitionTracing) {
    if (options !== undefined && options.name !== undefined) {
      ReactCurrentBatchConfig.transition.name = options.name;
      ReactCurrentBatchConfig.transition.startTime = -1;
    }
  }

  try {
    scope();
  } finally {
    ReactCurrentBatchConfig.transition = prevTransition;

    if (__DEV__) {
      if (prevTransition === null && currentTransition._updatedFibers) {
        const updatedFibersCount = currentTransition._updatedFibers.size;
        if (updatedFibersCount > 10) {
          console.warn(
            'Detected a large number of updates inside startTransition. ' +
              'If this is due to a subscription please re-write it to use React provided hooks. ' +
              'Otherwise concurrent mode guarantees are off the table.',
          );
        }
        currentTransition._updatedFibers.clear();
      }
    }
  }
}

代码可能看不太懂,怎么会在不阻塞UI的情况下更新state的,后面看到reconciler就会明白的。

React hooks

  • useCallback: 在组件重新渲染时缓存
  • useContext: 读取和订阅组件的context
  • useDebugValue: 调试值,仅用于开发环境
  • useDeferredValue: 延迟更新UI,
  • useEffect: 用于执行副作用,常用作函数组件的生命周期。
  • useImperativeHandle: 可通过ref将函数内部数据和函数暴露
  • useInsertionEffect: 适用于CSS-in-JS库使用
  • useLayoutEffect: 在浏览器重新绘制屏幕前触发。此hooks会影响性能,最好使用useEffect
  • useMemo: 在重新渲染组件时缓存计算结果
  • useMutableSource: 创建一个可变源,参考: rfc147
  • useSyncExternalStore: 用于订阅外部数据源,参考:useSyncExternalStore
  • useReducer: 使用reducer
  • useRef: 创建一个ref,可以引用一个不需要渲染的值
  • useState: 创建一个状态
  • useTransition: 不阻塞UI的情况下更新state

上述所有的hooks源码都在packages/react/src/ReactHooks.js下,其基本一致,都是封装ReactFiberHooks的语法糖,仅列举useState

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

其他

Profiler

测试React的渲染性能

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>
源码
/** 
 packages/react/src/React.js 
*/
export { 
  ...
  REACT_PROFILER_TYPE as Profiler,
  ...
}

/** 
 packages/shared/ReactSymbols.js
*/
export const REACT_PROFILER_TYPE = Symbol.for('react.profiler');

StrictMode

开启严格模式

<StrictMode>
  <App />
</StrictMode>
源码
/** 
 packages/react/src/React.js 
*/
export { 
  ...
  REACT_STRICT_MODE_TYPE as StrictMode,
  ...
}

/** 
 packages/shared/ReactSymbols.js
*/
export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');

SuspenseList

用于React devtools,具体参考:#19684

源码
/** 
 packages/react/src/React.js 
*/
export { 
  ...
  REACT_SUSPENSE_LIST_TYPE as SuspenseList,
  ...
}

/** 
 packages/shared/ReactSymbols.js
*/
export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');

createMutableSource

用于创建可变源,但是未在官网或CHANGELOG中找到任何描述,看着像应该删除的API。参考:#19948

export function createMutableSource<Source: $NonMaybeType<mixed>>(
  source: Source,
  getVersion: MutableSourceGetVersionFn,
): MutableSource<Source> {
  const mutableSource: MutableSource<Source> = {
    _getVersion: getVersion,
    _source: source,
    _workInProgressVersionPrimary: null,
    _workInProgressVersionSecondary: null,
  };

  if (__DEV__) {
    ...
  }

  return mutableSource;
}

createServerContext

创建服务端上下文,但是未在官网或CHANGELOG中找到任何描述。

export function createServerContext<T: ServerContextJSONValue>(
  globalName: string,
  defaultValue: T,
): ReactServerContext<T> {
  if (!enableServerContext) {
    throw new Error('Not implemented.');
  }
  let wasDefined = true;
  if (!ContextRegistry[globalName]) {
    wasDefined = false;
    const context: ReactServerContext<T> = {
      $$typeof: REACT_SERVER_CONTEXT_TYPE,
      _currentValue: defaultValue,
      _currentValue2: defaultValue,
      _defaultValue: defaultValue,
      _threadCount: 0,
      Provider: (null: any),
      Consumer: (null: any),
      _globalName: globalName,
    };

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

    if (__DEV__) {
      ...
    }
    ContextRegistry[globalName] = context;
  }

  const context = ContextRegistry[globalName];
  if (context._defaultValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
    context._defaultValue = defaultValue;
    if (
      context._currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
    ) {
      context._currentValue = defaultValue;
    }
    if (
      context._currentValue2 === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
    ) {
      context._currentValue2 = defaultValue;
    }
  } else if (wasDefined) {
    throw new Error(`ServerContext: ${globalName} already defined`);
  }
  return context;
}

弃用/过时 API

其中,有一部分已经弃用的API:createFactory

其中,在React最新文档中标记为过时API的有:

  • Child: 用于传递组件内渲染的元素,可以使用children属性进行传递,详情参见:备选方案
  • cloneElement: 基于元素创建新元素,可以使用类似renderItem方法传入到组件内,详情参见:备选方案
  • Component:通过类创建React组件,可以使用函数组件,详情参见:备选方案
  • createElement: 用于创建React元素,一般不会使用此方法,都是使用JSX创建的。
  • createRef: 创建ref对象,一般用于类组件,如果是函数组件请使用useRef,详情参见:备选方案
  • isValidElement:用于判断是否为React元素,通常配合createElement
  • PureComponent: 用于类组件优化性能用的,跳过相同的props重复渲染

还有其中使用unstable_开头的都是表示不稳定的API,也不建议使用。

jsx

React17之前jsx文件需要导入React其很大一部分原因就是因为jsx函数,此函数是将JSX代码转换成jsx函数,在React17之前叫做createElement

源码
/**
 * https://github.com/reactjs/rfcs/pull/107
 */
export function jsx(type, config, maybeKey) {
  let propName;

  const props = {};

  let key = null;
  let ref = null;

  // 将key转化成string
  if (maybeKey !== undefined) {
    if (__DEV__) {
      checkKeyStringCoercion(maybeKey);
    }
    key = '' + maybeKey;
  }

  // 将key转化成string
  if (hasValidKey(config)) {
    if (__DEV__) {
      checkKeyStringCoercion(config.key);
    }
    key = '' + config.key;
  }

  if (hasValidRef(config)) {
    ref = config.ref;
  }

  // 将config对象的属性转化到props对象
  for (propName in config) {
    if (
      hasOwnProperty.call(config, propName) &&
      !RESERVED_PROPS.hasOwnProperty(propName)
    ) {
      props[propName] = config[propName];
    }
  }

  // 加载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,
    undefined,
    undefined,
    ReactCurrentOwner.current,
    props,
  );
}

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,
  };

  if (__DEV__) {
    ...
  }

  return element;
};

从源码中可以看出,最终实际只是创建一个REACT_ELEMENT_TYPE类型的对象,其最终创建的代码是在render包中完成的,web平台的是react-dom

总体上看,React的源码参考价值不是特别大,因为基本上都是没有核心逻辑,都是封装其他包的代码.