react源码学习(1)

jsx

jsx会被转为createElement方法,看下源码这个方法


export function createElement(type, config, children) {
}

接受三个参数:

  • type元素类型,对于普通元素,就是div p ,对于函数组件,就是 函数本身, 对于类组件,就是类本身
  • config 配置属性,比如ref, key, style…
  • children 子元素
    这个方法的主要作用就是:
  • 1 分离props属性和特殊属性
  let propName;
  const props = {}; // 存储普通元素属性
  // 待提取属性,react内部为了实现某些功能而存在的属性
  let key = null;
  let ref = null;
  let self = null;
  let source = null;


  // 分离props属性和特殊属性
  if (config != null) {
    // 判断是否是合法的ref
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    // 判断是否是合法的key
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    // Remaining properties are added to a new props object
    // 将其他属性存入props中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        // 不是key self ref source的其中一个
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  • 2 将子元素挂载到porps.children中
// 将子元素挂载到porps.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;
  }

  • 3 为Props属性赋默认值
// 3 为Props属性赋默认值
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;

    // 如果通过props去获取key或者ref,就会给你报错。
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }

如果通过props获取Key和ref,在开发环境的时候还会报错。

  • 4 创建并返回ReactElement
// 4 创建并返回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
    // 组建的类型,十六进制数值或者是SYmbol值
    // React在最终渲染DOM的时候,需要确保元素的类型是REACT_ELEMENT_TYPE,需要此属性作为判断的依据
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    // 元素的具体类型,span, div, class A{} , function App(){}...
    type: type,
    // key值, 元素的唯一标识,用做内部vdom比对提升dom操作性能。
    key: key,
    // ref值 存储元素DOM对象或者组件实例对象
    ref: ref,
    // 存储向组件内部传递的数据
    props: props,

    // Record the component responsible for creating this element.
    // 记录当前元素所属组件(记录当前元素是哪个组件创建的)
    _owner: owner,
  };

返回一个vdom对象。

isValidElement

react内部提供了判断是否是标准ReactELemtn的方法


/**
 * 验证object参数是否是ReactElement,返回布尔值
 * 验证成功的条件:
 * 是对象
 * 部位null
 * 并且$$typeof === React_ELEMNT_TYPE
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

验证成功的条件:

  • 是对象
  • 部位null
  • 并且$$typeof === React_ELEMNT_TYPE(如果支持SYmbol就是一个SYmbol,如果不支持就是一个十六进制数)

React架构

React16版本的架构可以分为三层,调度层,协调层,渲染层。

Scheduler调度层
  • react15的版本中,采用了循环加递归的方式进行了vdom的比对,由于递归使用js自身的执行栈,一旦开始就无法停止。直到任务执行完成。如果dom树的层级较深,就容易出现长期占用js主线程,导致gui渲染线程无法得到工作,造成页面卡顿。
  • 在16的版本中,放弃了js递归方式进行vdom比对,而是采用了循环模拟递归,而且比对的过程是利用浏览器的空闲时间完成的,不会占用主线程,这就解决了vdom对比造成页面卡顿原因。
  • 在window中提供了requestIdCallback的api,它可以利用浏览器空闲的时间执行任务,但是他自设能触发频率不稳定,并且不是所有浏览器都支持他。
  • react自己实现了任务调度库,叫做Scheduler,如果浏览器支持postMessage,那么他就会采用postMessage来进行调度,不支持再使用setTimoute,setTimeout的缺点是连续调用setTimeout(()=>{},0),最后会发现他的触发频率变成了4ms一次。并且Scheduler还采用了小顶堆算法,实现了任务优先级的概念。
Reconciler协调层
  • react15的版本中,协调器和渲染器交替执行,找到了差异就更新差异,这也是15无法中断的原因,因为会造成页面渲染不完全。
  • react16中,则是Scheduler和Reconciler交替工作,Scheduler负责调度,Reconciler负责找出差异,打上标记。等所有差异找完之后,才会交给Renderer统一进行DOM更新。这也是为什么react16可以实现可中断的异步更新的原因。
Renderer渲染层
  • 渲染层工作的时候,是同步的,也就是无法中断的。可中断的异步更新的概念是描述Scheduler和Reconciler,他们是在内存中完成的。而Renderer是无法被中断的。
  • 既然无法被中段,那么就不会出现dom渲染不完全的情况,因为渲染器的工作是一气呵成的,从0到1。

Fiber数据结构

fiber本质就是一个js对象。

export type Fiber = {


  /**-----------------实例相关---------------- */
  // 标记不同的组件类型 ,比如函数组件是0,类组件是1....
  tag: WorkTag,
  key: null | string,
  elementType: any,
  // div p span class A{} function A(){} .....
  type: any,
  //实例 实例对象,比如类组件的实例,原生元素就是dom, funciton没有实例 rootFiber的stateNode是FiberRoot
  stateNode: any,

  /**-------- fiber相关--------- */
  return: Fiber | null, //指向自己的父级fiber
  child: Fiber | null, //指向大儿子fiber
  sibling: Fiber | null, //指向兄弟节点fiber
  index: number,
  // fiber工作一般在workInprogress fiber,为了实现复用, alternate指向当前current Fiber得对应的fiber
  // 等到工作完毕,workInprogress fiber就变成current Fiber,以此循环
  alternate: Fiber | null,

  ref:
    | null
    | (((handle: mixed) => void) & { _stringRef: ?string, ... })
    | RefObject,

  /**----------- 状态数据相关-------------- */
  pendingProps: any, //  即将更新的Props
  memoizedProps: any, // 旧的props
  memoizedState: any, // 旧的state

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,

 
  mode: TypeOfMode,  //当前组件及子组件处于何种渲染模式, createRoot/render

  /**-------- Effect副作用相关-------------- */
  updateQueue: mixed, //该Fiber对应的组件产生的状态会存放到这个队列,比如update对象
  flags: Flags,  // effectTag标记,用来记录当前fiber要执行得DOM操作
  subtreeFlags: Flags, 
  deletions: Array<Fiber> | null, 
  nextEffect: Fiber | null, // 单链表用来快速查找下一个sied effect
  firstEffect: Fiber | null,// 子树中第一个side effect
  lastEffect: Fiber | null, // 子树中最后一个last Effect
  lanes: Lanes, // 优先级
  childLanes: Lanes,
 }

这里主要列了几种比较常用的,比如instance实例相关的属性,作为fiber相关的属性,状态数据相关得属性以及副作用相关的属性。
flags对应的标记是

export type Flags = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoFlags = /*                      */ 0b00000000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000000001;

// You can change the rest (and add more).
export const Placement = /*                    */ 0b00000000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000000100;
export const PlacementAndUpdate = /*           */ Placement | Update;
export const Deletion = /*                     */ 0b00000000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000000010000;
export const ContentReset = /*                 */ 0b00000000000000000000100000;
export const Callback = /*                     */ 0b00000000000000000001000000;
export const DidCapture = /*                   */ 0b00000000000000000010000000;
export const ForceClientRender = /*            */ 0b00000000000000000100000000;
export const Ref = /*                          */ 0b00000000000000001000000000;
export const Snapshot = /*                     */ 0b00000000000000010000000000;
export const Passive = /*                      */ 0b00000000000000100000000000;
export const Hydrating = /*                    */ 0b00000000000001000000000000;
export const HydratingAndUpdate = /*           */ Hydrating | Update;
export const Visibility = /*                   */ 0b00000000000010000000000000;
export const StoreConsistency = /*             */ 0b00000000000100000000000000;

双缓存技术介绍

  • 在react中,DOM得更新采用了双缓存技术,双缓存技术致力于更快速得DOM更新。
  • 举个例子,canvas绘制动画的时候,绘制每一帧动画之前就需要清除上一帧得动画,如果当前帧动画计算较长,就会导致出现替换白屏出现。为了解决这个问题,可以现在内存中构建当前帧动画,绘制完毕后直接替换上一帧,这样就不会出现白屏问题,这种在内存中构建并直接替换的技术叫做双缓存。
  • React使用双缓存技术完成Fiber树的构建和替换,实现DOM的快速更新。
  • 在react中最多同时存在两颗fiber树,当前屏幕显示的是current Fiber,发生更新的时候,react在内存中构建workInporgress fiber。并且在两颗中之间对应的fiber节点有一个alternate指针,通过这个指针,workInporgress的构建就可以最大化的复用current fiber。进而更快速的构建玩workInporgress fiber。

第一个mount的时候,会先创建FiberRottNode,他的current指针指向rootFiber,然后拷贝一份rootFiber,他就是workInprogress fiber的rootFiber了,然后将alternate指针指向这个复制的节点。通过这个复制的节点继续创建fiber。如
在这里插入图片描述
然后将右边构建完毕的workInprogress fiber树更新为current fiber。
在这里插入图片描述
update的时候,
在这里插入图片描述
workInporgress fiber可以通过alternate属性并且通过diff决定要不要进行复用。
最终构建完新的workInporegss fiber 树,然后再替换成current Fiber树。
在这里插入图片描述

区分FiberRoot和rootFiber

  • FIberRoot表示Fiber数据结构对象,是Fiber数据结构的最外层对象
  • rootFiber表示组件挂载点对应的fiber对象,比如React应用中默认组件挂载点就是id为root的div
  • FiberRoot.current => rootFiber
  • rootFiber.stateNode = FiberRoot
  • 在react应用中,FiberRoot只有一个,而rootFiber可以有多个。因为render方法可以调用多次。
  • fiberRoot会记录应用的更新信息,比如Reconciler完成工作之后,会将工作结果存储在FiberRoot中。

render方法

/**
 * 
 * @param {*} element ReactElement, createElement的返回值
 * @param {*} container  容器
 * @param {*} callback  渲染后执行的回调函数
 * @returns 
 */
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  if (__DEV__) {
    console.error(
      'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
        'instead. Until you switch to the new API, your app will behave as ' +
        "if it's running React 17. Learn " +
        'more: https://reactjs.org/link/switch-to-createroot',
    );
  }

  //判断是否是合法的容器
  if (!isValidContainerLegacy(container)) {
    throw new Error('Target container is not a DOM element.');
  }

  // 初始化FiberRoot和rootFiber
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

render方法主要接受三个参数

  • element : ReactElement, createElement的返回值
  • container 容器
  • callback 渲染后执行的回调函数
    然后判断是否是合法的container,最后调用legacyRenderSubtreeIntoContainer,他会创建FiberRoot和rootFiber并且开启调度。
    react18之后使用render会报错。
### 回答1: 学习 React 源码是一项很有益的学习活动,可以帮助你加深对 React 的理解,并且为你提供有关如何实现组件化和声明式编程的知识。 在学习 React 源码时,我有几个建议: 1. 先从官方文档入手,了解 React 的基本概念和使用方法。这将有助于你更好地理解 React 源码。 2. 认真阅读源码注释。React源码非常详细,并且有很多注释,这些注释很有帮助,可以帮助你理解源码的目的和工作原理。 3. 使用调试工具,以帮助你更好地理解源码的工作流程。比如,可以使用浏览器的开发者工具调试 React 代码,或者使用断点调试。 4. 尝试实现一些自己的功能,并结合源码来调试和理解。这将有助于你更好地理解 React 的工作原理,并且能够让你在实际使用中更加熟练。 5. 可以尝试加入 React 开源社区,与其他 React 开发者交流经验,也可以参与 React 的开源项目,为 React 贡献代码。这将有助于你不断学习和提升。 ### 回答2: 学习React源码是一个非常有挑战性但也非常有价值的任务。首先,我建议你先熟悉React的基本概念和使用方法,理解React的核心思想和设计理念,然后再深入研究源码。以下是一些具体的建议: 1. 掌握JavaScript和ES6的基础知识:React源码是用JavaScript编写的,因此对于JavaScript的理解是必要的。确保你对JavaScript中ES6的新特性例如模块导入导出、箭头函数、解构赋值等有一定的了解。 2. 阅读官方文档和源码注释:React官方提供了非常详细的文档和源码注释,阅读它们可以帮助你快速了解React的内部工作原理和实现细节。 3. 从顶层开始分析:从React的顶层入手,逐步深入源码。首先阅读React的入口文件,了解React是如何初始化和渲染组件的。然后再深入了解React组件的生命周期和更新机制。 4. 使用工具进行调试:使用Chrome DevTools等工具进行调试可以帮助你更好地理解和分析React的运行时行为。通过在源码中设置断点并观察变量的值,可以深入理解React的执行流程。 5. 理解虚拟DOM和协调算法:React的核心是虚拟DOM和协调算法,学习和理解它们对于深入理解React源码非常重要。阅读相关的源码实现,将有助于你理解React如何高效地更新DOM。 6. 参考社区资源和开源项目:React有一个非常庞大的社区,许多优秀的开源项目可以帮助你更好地理解React源码。与其他人一起学习和讨论,参与到React相关的开源项目中,将加快你的学习进度。 总结起来,学习React源码需要坚实的JavaScript基础和对React的理解。通过仔细阅读官方文档、源码注释和调试工具的使用,以及参考社区资源和开源项目,你将能够逐渐深入了解React的实现细节,提高自己的技术水平。 ### 回答3: 学习React源码的过程可以相当艰巨,但也是丰富而值得的。以下是几些建议帮助你更好地学习React源码: 1. 先理解React的核心概念:在深入研究源码前,先确保你对React的基本理念和工作方式有了清晰的理解。了解虚拟DOM、组件生命周期、状态管理和数据流等核心概念将有助于更好地理解源码。 2. 创建一个简单的React应用:在学习React源码时,建议你尝试创建一个简单的React应用并深入研究它的实现细节。这样可以帮助你更好地理解React的工作原理和内部机制。 3. 阅读React官方文档和源码注释:React源码中有丰富的注释,它们会对源码的实现细节进行解释和说明。同时,React官方文档也提供了很多有价值的内容,包括API文档和设计原则。阅读这些内容能够帮助你更好地理解React的思想和设计理念。 4. 利用调试工具:学习源码的过程中,调试工具是非常有用的辅助资源。使用断点和调试器来观察源码的执行路径和状态变化,可以帮助你更好地理解代码的运行机制。 5. 参考优秀的源码解析和开源项目:很多开发者在学习React源码后,会产生一些优秀的博客文章、视频教程和开源项目,其中包含了他们对源码的解析和实践。阅读这些资源可以帮助你更好地理解React源码并从中获取灵感。 学习React源码需要耐心和持续努力,希望以上建议能够帮助你更好地进行这个过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值