一文帮你理解PReact10.5.13源码


Posted in Javascript onApril 03, 2021

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函数。

一文帮你理解PReact10.5.13源码

所以源码中,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部分比较复杂,整体整理了一张大图

一文帮你理解PReact10.5.13源码

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

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

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

Javascript 相关文章推荐
jQuery Dialog 弹出层对话框插件
Aug 09 Javascript
JavaScript中的this实例分析
Apr 28 Javascript
小结Node.js中非阻塞IO和事件循环
Sep 18 Javascript
js插件设置innerHTML时在IE8下提示“未知运行时错误”解决方法
Apr 25 Javascript
简介JavaScript中的italics()方法的使用
Jun 08 Javascript
在JSP中如何实现MD5加密的方法
Nov 02 Javascript
JS中parseInt()和map()用法分析
Dec 16 Javascript
详解JavaScript对象的深浅复制
Mar 30 Javascript
jQuery代码优化方法总结
Jan 29 jQuery
jQuery实现常见的隐藏与展示列表效果示例
Jun 04 jQuery
js实现点击图片在屏幕中间弹出放大效果
Sep 11 Javascript
Vue是怎么渲染template内的标签内容的
Jun 05 Javascript
JS一分钟在github+Jekyll的博客中添加访问量功能的实现
给原生html中添加水印遮罩层的实现示例
html实现随机点名器的示例代码
如何利用JavaScript实现二叉搜索树
(开源)微信小程序+mqtt,esp8266温湿度读取
Javascript中的解构赋值语法详解
Apr 02 #Javascript
Ajax实现局部刷新的方法实例
You might like
解析yii数据库的增删查改
2013/06/20 PHP
PHP仿博客园 个人博客(1) 数据库与界面设计
2013/07/05 PHP
php内存缓存实现方法
2015/01/24 PHP
PHP错误处理函数
2016/04/03 PHP
浅谈PHP Cookie处理函数
2016/06/10 PHP
Javascript 复制数组实现代码
2009/11/26 Javascript
json数据处理技巧(字段带空格、增加字段、排序等等)
2013/06/14 Javascript
点击显示指定元素隐藏其他同辈元素的方法
2014/02/19 Javascript
js 针对html DOM元素操作等经验累积
2014/03/11 Javascript
jQuery实现折叠、展开的菜单组效果代码
2015/09/16 Javascript
JS中双击和单击事件冲突的解决方法
2018/04/09 Javascript
[04:00]DOTA2解说界神雕侠侣 CJ第四天谷子现场过生日
2013/07/30 DOTA
Python基于select实现的socket服务器
2016/04/13 Python
python 中random模块的常用方法总结
2017/07/08 Python
Python基于动态规划算法解决01背包问题实例
2017/12/06 Python
python互斥锁、加锁、同步机制、异步通信知识总结
2018/02/11 Python
selenium+python实现自动登录脚本
2018/04/22 Python
利用Python如何实现数据驱动的接口自动化测试
2018/05/11 Python
python实现自动发送邮件
2018/06/20 Python
pygame游戏之旅 计算游戏中躲过的障碍数量
2018/11/20 Python
Python3.5 处理文本txt,删除不需要的行方法
2018/12/10 Python
python3.x+pyqt5实现主窗口状态栏里(嵌入)显示进度条功能
2019/07/04 Python
python 字符串常用函数详解
2019/09/11 Python
使用批处理脚本自动生成并上传NuGet包(操作方法)
2019/11/19 Python
基于Python组装jmx并调用JMeter实现压力测试
2020/11/03 Python
意大利文具和办公产品在线商店:Y-Office
2020/02/27 全球购物
一套中级Java程序员笔试题
2015/01/14 面试题
怎样自定义一个异常类
2016/09/27 面试题
个人实用简单的自我评价
2013/10/19 职场文书
中专毕业生自我鉴定范文
2013/11/09 职场文书
《两只鸟蛋》教学反思
2014/02/10 职场文书
小学红领巾广播稿(3篇)
2014/09/13 职场文书
秋季运动会开幕词
2015/01/28 职场文书
企业法人代表证明书
2015/06/18 职场文书
上班旷工检讨书
2015/08/15 职场文书
关于windows server 2012 DC 环境 重启后蓝屏代码:0xc00002e2的问题
2022/05/25 Servers