React源码看过几次,每次都没有坚持下来,索性学习一下PReact部分,网上讲解源码的不少,但是基本已经过时,所以自己来梳理下

render.js部分

import { EMPTY_OBJ, EMPTY_ARR } from './constants';
import { commitRoot, diff } from './diff/index';
import { createElement, Fragment } from './create-element';
import options from './options';

/**
 * Render a Preact virtual node into a DOM element
 * @param {import('./internal').ComponentChild} vnode The virtual node to render
 * @param {import('./internal').PreactElement} parentDom The DOM element to
 * render into
 * @param {import('./internal').PreactElement | object} [replaceNode] Optional: Attempt to re-use an
 * existing DOM tree rooted at `replaceNode`
 */
export function render(vnode, parentDom, replaceNode) {
 if (options._root) options._root(vnode, parentDom);

 // We abuse the `replaceNode` parameter in `hydrate()` to signal if we are in
 // hydration mode or not by passing the `hydrate` function instead of a DOM
 // element..
 let isHydrating = typeof replaceNode === 'function';

 // To be able to support calling `render()` multiple times on the same
 // DOM node, we need to obtain a reference to the previous tree. We do
 // this by assigning a new `_children` property to DOM nodes which points
 // to the last rendered tree. By default this property is not present, which
 // means that we are mounting a new tree for the first time.
  // 为了支持多次在一个dom节点上调用render函数,需要在dom节点上添加一个饮用,用来获取指向上一次渲染的虚拟dom树。
  // 这个属性默认是指向空的,也意味着我们第一次正在装备一颗新的树
  // 所以开始时这里的oldVNode是空(不论isHydrating的值),但是如果重复在这个节点上调用render那oldVNode是有值的
 let oldVNode = isHydrating
  ? null
  : (replaceNode && replaceNode._children) || parentDom._children;

 // 用Fragment包裹一下vnode,同时给replaceNode和parentDom的_children赋值
  vnode = (
  (!isHydrating && replaceNode) ||
  parentDom
 )._children = createElement(Fragment, null, [vnode]);

 // List of effects that need to be called after diffing.
  // 用来放置diff之后需要进行各种生命周期处理的Component,比如cdm、cdu;componentWillUnmount在diffChildren的unmount函数中执行不在commitRoot时执行
 let commitQueue = [];
 diff(
  parentDom, // 这个使用parentDom的_children属性已经指向[vnode]了
  // Determine the new vnode tree and store it on the DOM element on
  // our custom `_children` property.
  vnode,
  oldVNode || EMPTY_OBJ, // 旧的树
  EMPTY_OBJ,
  parentDom.ownerSVGElement !== undefined,
    // excessDomChildren,这个参数用来做dom复用的作用
  !isHydrating && replaceNode
   ? [replaceNode]
   : oldVNode
   ? null
   : parentDom.firstChild // 如果parentDom有子节点就会把整个子节点作为待复用的节点使用
   ? EMPTY_ARR.slice.call(parentDom.childNodes)
   : null,
  commitQueue,
    // oldDom,在后续方法中用来做标记插入位置使用
  !isHydrating && replaceNode
   ? replaceNode
   : oldVNode
   ? oldVNode._dom
   : parentDom.firstChild,
  isHydrating
 );

 // Flush all queued effects
  // 调用所有commitQueue中的节点_renderCallbacks中的方法
 commitRoot(commitQueue, vnode);
}

/**
 * Update an existing DOM element with data from a Preact virtual node
 * @param {import('./internal').ComponentChild} vnode The virtual node to render
 * @param {import('./internal').PreactElement} parentDom The DOM element to
 * update
 */
export function hydrate(vnode, parentDom) {
 render(vnode, parentDom, hydrate);
}

create-context.js部分

Context的使用:

Provider的props中有value属性

Consumer中直接获取传值

import { createContext, h, render } from 'preact';

const FontContext = createContext(20);

function Child() {
 return <FontContext.Consumer>
 {fontSize=><div style={{fontSize:fontSize}}>child</div>}
 </FontContext.Consumer>
}
function App(){
 return <Child/>
}
render(
 <FontContext.Provider value={26}>
 <App/>
 </FontContext.Provider>,
 document.getElementById('app')
);

看一下源码:

import { enqueueRender } from './component';

export let i = 0;

export function createContext(defaultValue, contextId) {
 contextId = '__cC' + i++; // 生成一个唯一ID

 const context = {
  _id: contextId,
  _defaultValue: defaultValue,
  /** @type {import('./internal').FunctionComponent} */
  Consumer(props, contextValue) {
   // return props.children(
   //  context[contextId] ? context[contextId].props.value : defaultValue
   // );
   return props.children(contextValue);
  },
  /** @type {import('./internal').FunctionComponent} */
  Provider(props) {
   if (!this.getChildContext) { // 第一次调用时进行一些初始化操作
    let subs = [];
    let ctx = {};
    ctx[contextId] = this;
       
       // 在diff操作用,如果判断一个组件在Comsumer中,会调用sub进行订阅;
       // 同时这个节点后续所有diff的地方都会带上这个context,调用sub方法进行调用
       // context具有层级优先级,组件会先加入最近的context中
    this.getChildContext = () => ctx; 

    this.shouldComponentUpdate = function(_props) {
     if (this.props.value !== _props.value) {
      // I think the forced value propagation here was only needed when `options.debounceRendering` was being bypassed:
      // https://github.com/preactjs/preact/commit/4d339fb803bea09e9f198abf38ca1bf8ea4b7771#diff-54682ce380935a717e41b8bfc54737f6R358
      // In those cases though, even with the value corrected, we're double-rendering all nodes.
      // It might be better to just tell folks not to use force-sync mode.
      // Currently, using `useContext()` in a class component will overwrite its `this.context` value.
      // subs.some(c => {
      //  c.context = _props.value;
      //  enqueueRender(c);
      // });

      // subs.some(c => {
      //  c.context[contextId] = _props.value;
      //  enqueueRender(c);
      // });
            // enqueueRender最终会进入renderComponent函数,进行diff、commitRoot、updateParentDomPointers等操作
      subs.some(enqueueRender);
     }
    };

    this.sub = c => {
     subs.push(c);// 进入订阅数组,
     let old = c.componentWillUnmount;
     c.componentWillUnmount = () => { // 重写componentWillUnmount
      subs.splice(subs.indexOf(c), 1);
      if (old) old.call(c);
     };
    };
   }

   return props.children;
  }
 };

 // Devtools needs access to the context object when it
 // encounters a Provider. This is necessary to support
 // setting `displayName` on the context object instead
 // of on the component itself. See:
 // https://reactjs.org/docs/context.html#contextdisplayname
 // createContext最终返回的是一个context对象,带着Provider和Consumer两个函数
 // 同时Consumber函数的contextType和Provider函数的_contextRef属性都指向context
 return (context.Provider._contextRef = context.Consumer.contextType = context);
}

所以对于Provider组件,在渲染时会判断有没有getChildContext方法,如果有的话调用得到globalContext并一直向下传递下去

if (c.getChildContext != null) {
    globalContext = assign(assign({}, globalContext), c.getChildContext());
   }

   if (!isNew && c.getSnapshotBeforeUpdate != null) {
    snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
   }

   let isTopLevelFragment =
    tmp != null && tmp.type === Fragment && tmp.key == null;
   let renderResult = isTopLevelFragment ? tmp.props.children : tmp;

   diffChildren(
    parentDom,
    Array.isArray(renderResult) ? renderResult : [renderResult],
    newVNode,
    oldVNode,
    globalContext,
    isSvg,
    excessDomChildren,
    commitQueue,
    oldDom,
    isHydrating
   );

当渲染遇到Consumer时,即遇到contextType属性,先从Context中拿到provider,然后拿到provider的props的value值,作为组件要获取的上下文信息。同时这时候会调用provider的sub方法,进行订阅,当调用到Provider的shouldComponentUpdate中发现value发生变化时就会将所有的订阅者进入enqueueRender函数。

所以源码中,globalContext对象的每一个key指向一个Context.Provider;componentContext代表组件所在的Consumer传递的上下文信息即配对的Provider的props的value;

同时Provider的shouldComponentUpdate方法中用到了 ·this.props.value !== _props.value· 那么这里的this.props是哪来的?Provider中并没有相关属性。

主要是下面这个地方,当判断没有render方法时,会先用Compoent来实例化一个对象,并将render方法设置为doRender,并将constructor指向newType(当前函数),在doRender中调用this.constructor方法

// Instantiate the new component
    if ('prototype' in newType && newType.prototype.render) {
     // @ts-ignore The check above verifies that newType is suppose to be constructed
     newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
    } else {
     // @ts-ignore Trust me, Component implements the interface we want
     newVNode._component = c = new Component(newProps, componentContext);
     c.constructor = newType;
     c.render = doRender;
    }
/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
 return this.constructor(props, context);
}

diff部分

diff部分比较复杂,整体整理了一张大图

真是不得不吐槽,博客园的编辑器bug太多了,尤其是mac上使用,比如第二次上传代码提交不了;赋值粘贴用不了。。。

只有情怀让我继续在这里更新

到此这篇关于一文帮你理解PReact10.5.13源码的文章就介绍到这了,更多相关PReact10.5.13源码内容请搜索程序员的世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员的世界!

帮你理解PReact10.5.13源码的更多相关文章

  1. 原生js使用面向对象的方法开发选项卡实例教程

    本教程通过js面向对象的方法来封装一个选项卡的实例,在实例中讲解js的面向对象如何实现功能。一般封装好的选项卡程序,只需要一个div元素即可。其它元素都是通过json数据来生成,所以封装好的选项卡实例,调用非常方便。先创建一个div元素,如下所示:<div class="tab_bo......

  2. js中获得当前时间是年份和月份

    js中获得当前时间是年份和月份,形如:201208 //获取完整的日期 var date=new Date; var year=date.getFullYear(); var month=date.getMonth()+1; month ......

  3. jQuery中toggle与slideToggle以及fadeToggle的显示、隐藏方法的比较

    1、区别 ①动画效果的比较: toggle:直接显示、隐藏,如果有【时间参数】且【匹配的元素有宽度属性】,则动态效果为左上角-右下角拉卷效果,透明度0-1之间的变化;若有时间参数但是【匹配的元素没有宽度属性】,则动态效果为上下拉卷的效果,且没有透明度的变化。 slideToggle:切换上下拉卷滚效......

  4. Jquery 格式化时间

    我们常常会通过datetime得到时间,但是网页前台往往会显示不同的时间如:2013-12-152013年12月23日2013 12 15等多种显示效果,这就需要我们把时间格式化一下。下面是通过Jquery格式化时间:$(function () { var time =......

  5. jquery表格插件Datatables使用、快速上手

    Datatables使用一、简介官网:https://datatables.net/ 中文官网:http://datatables.club/Datatables是一款jquery表格插件。它是一个高度灵活的工具,可以将任何HTML表格添加高级的交互功能。操作DOM的分页,即时搜索和排序,几乎支持任......

  6. javascript中的内存管理

    目录简介内存生命周期JS中的垃圾回收器引用计数垃圾回收算法Mark-and-sweep回收算法调试内存问题闭包Closures中的内存泄露javascript中的内存管理简介在c语言中,我们需要手动分配和释放对象的内存,但是在java中,所有的内存管理都交给了java虚拟机,程序员不需要在手动进程内......

  7. JavaScript 中的六种循环方法

    Javascript中的遍历循环1.for循环对于数值索引的数组来说,可以使用标准的for循环来遍历值const arr=[1,2,3,4];for(let i=0;i2.for...in循环for...in循环可以用来遍历对象的可枚举属性列表(包括原型链上的属性)const myObject={}......

  8. JavaScript this关键字的深入详解

    一、前言this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。对于那些没有投入时间学习this机制的JavaScript开发者来说,this的绑定一直是一件非常令人困惑的事。二、了解this学习this的第一步是明白this既不指向函数自身也......

  9. 原生javascript制作省市区三级联动详细教程

    多级联动下拉菜单是前端常见的效果,省市区三级联动又属于其中最典型的案例。多级联动一般都是与数据相关联的,根据数据来生成和修改联动的下拉菜单。完成一个多级联动效果,有助于增强对数据处理的能力。本实例以省市区三级联动为例,来说明具体是如何使用javascript来关联数据,实现联动下拉菜单。学习本教程之......

  10. 帮你理解PReact10.5.13源码

    React源码看过几次,每次都没有坚持下来,索性学习一下PReact部分,网上讲解源码的不少,但是基本已经过时,所以自己来梳理下render.js部分import { EMPTY_OBJ, EMPTY_ARR } from './constants';import { commitRoot, dif......

随机推荐

  1. MySQL大库搭建主从的一种思路分享

    这个周忙的就像打仗一样,感觉有点被别人牵着鼻子走了,每天都是早出晚归,干不完的活儿,有时候感觉DBA这碗饭真的不好吃,要有强大的抗压能力和心理承受能力。今天下午吃饭的时候,真的感觉整个人快要垮掉了,吃完饭就依然决然的下班了,走在路上,看着下班的人群,心想这不就是正常的下班时间么,为什么我还有种早走惭......

  2. C#处理医学图像(一):基于Hessian矩阵的血管肺纹理骨骼增强对比

    在医院实际环境中,经常遇到有问题的患者,对于一些特殊的场景,比如骨折,肺结节,心脑血管问题需要图像对比增强来更为清晰的显示病灶助于医生确诊,先看效果:肺纹理增强:肺结节增强:血管对比增强:骨骼对比增强:根据参考资料:MATLAB版本:https://ww2.mathworks.cn/matlabce......

  3. 用python批量移动文件

    我是用来移动图片的,其他格式的文档也是可以的,改下后缀列表就可以了import os,shutilimport datetime #将文件夹里的图片全部移动到新文件夹中#revised by Stephen Shen 2020-3-10 09:28:50 def renameFile(dst......

  4. Python爬虫实现selenium处理iframe作用域问题

    项目场景:在使用selenium模块进行数据爬取时,通常会遇到爬取iframe中的内容。会因为定位的作用域问题爬取不到数据。问题描述:我们以菜鸟教程的运行实例为案例。按照正常的定位会以文本块生成xpath为/html/body/text()。这样的话根据xpath进行如下代码编写。#!/user/b......

  5. 详解python函数传参传递dict/list/set等类型的问题

    传参时传递可变对象,实际上传的是指向内存地址的指针/引用这个标题是我的结论,也是我在做项目过程查到的。学过C的都知道,函数传参可以传值,也可以传指针。指针的好处此处不再赘述。先上代码看看效果:def trans(var):return varsource = {1: 1}dist = trans(s......

  6. Vue实现摇一摇功能(兼容ios13.3以上)

    最近做了个摇一摇类似的功能,使用的是shake.js,但在ios13.3之前的版本中可以触发摇一摇,之后的版本需要兼容,需要制作一个让用户能手动点击的弹框,才能使用户授权动作与方向的权限。(需使用https协议)温馨提示由于ios系统需要手动获取访问动作与方向的权限,为保障游戏的正常进行,请在访问提......

  7. R语言ggplot2x轴顺序设置自定义颜色

    先声明一下所用的数据集第一个图如下这个图主要在于x轴的顺序设置上,如果按不做任何处理的话>3那个就会在2之前,解决方法是b[,1]<-factor(b[,1],levels=c('2','3',">3")),这句代码可以重新设置因子的级别完整代码如下:a[,1]......

  8. js面向对象封装级联下拉菜单列表

    本实例开发的级联下拉菜单是根据已有json数据创建的DOM元素。点击文本框后,显示一级菜单。如果菜单中包含子菜单,菜单右侧会有指示箭头。点击菜单之后,会再显示下一级菜单,以此类推。当菜单下无子菜单时,选择菜单后会在文本框中显示。本实例开发的级联下拉菜单是根据已有json数据创建的DOM元素。点击文本......

  9. 在.NET Core 中使用Quartz.NET

    Quartz.NET是功能齐全的开源作业调度系统,可用于最小的应用程序到大型企业系统。Quartz.NET具有三个主要概念:job:运行的后台任务trigger:控制后台任务运行的触发器。scheduler:协调job和triggerASP.NET Core通过托管服务对运行“后台任务”具有良好的支......

  10. python爬虫-数据解析(xpath)

    文章目录xpath基本概念xpath解析原理环境安装如何实例化一个etree对象:xpath(‘xpath表达式’)xpath爬取58二手房实例爬取网址完整代码效果图xpath图片解析下载实例爬取网址完整代码效果图xpath爬取全国城市名称实例爬取网址完整代码效果图xpath爬取简历模板实例爬取网址......