橙汁绿茶

橙汁绿茶 查看完整档案

杭州编辑中国计量学院现代科技学院  |  计算机科学与技术 编辑自由职业  |  前端开发 编辑 github.com/daochouwangu 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

橙汁绿茶 关注了专栏 · 2020-09-26

前端小码农

分享有趣的代码,让编程更有趣;总结自己学习过程中遇到的坑

关注 1505

橙汁绿茶 赞了文章 · 2019-12-24

源码看React---- ref

ref是怎么引用到真实节点的

<input type="text" ref={(node)=>{this.myInput = node}}/>

这是我们在组件中书写的样式。我们知道,在HTML中这是个DOM节点,但是在React会经由jsx转化为React.createElement(...)
所以,这里其实相当于一个函数。

ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
};

巴拉巴拉的细节逻辑就不看了,总之呢就是生成了一个ReactElement对象,而ref会保存为该对象的一个属性。
当这个reactElement开始挂载时会先将他包装成一个ReactComponent。(没错,惊喜不惊喜,以外不意外,你随便写的一div,input标签都是先被封装成组件再挂载。)实现代码如下,当然,这里精简了很多代码。

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;
  console.log(node);
  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    var type = element.type;

    // Special case string values
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
  }
  return instance;
}

生成组件以后就会调用ReactReconciler.mountComponent进行组件挂载。

 mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
  {
  //这里返回的markup就是渲染好的部分虚拟DOM树
    var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
    if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
      transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
    }
    return markup;
  },

从代码中可以看出,当ReactElement生成虚拟DOM节点以后,会进行判断,如果该节点有ref引用。则会把attachRefs函数和该节点传入回调函数序列。
那么这个回调序列又在哪儿触发的呢,原理还是transaction机制。

transaction.getReactMountReady().enqueue(attachRefs, internalInstance);

这段代码里的transaction是ReactReconcileTransaction
ReactReconcileTransaction包裹了三层wrapper


var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   * 返回选中的信息
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection,
};
var EVENT_SUPPRESSION = {
  initialize: function() {
    //先确定当前是否可以进行事件处理。
    var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
    //防止此次事件处理过程中会发生其他transaction里面的事件
    ReactBrowserEventEmitter.setEnabled(false);
    return currentlyEnabled;
  },
  close: function(previouslyEnabled) {
    ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
  },
};


// 通过CallbackQueue回调函数队列机制,即this.reactMountReady
//    执行this.reactMountReady.enqueue(fn)注入componentDidMount、componentDidUpdate方法
// 通过Transaction添加前、后置钩子机制
//    前置钩子initialize方法用于清空回调队列;close用于触发回调函数componentDidMount、componentDidUpdate执行
var ON_DOM_READY_QUEUEING = {
  initialize: function() {
    this.reactMountReady.reset();
  },
  close: function() {
    this.reactMountReady.notifyAll();
  },
};


var TRANSACTION_WRAPPERS = [
  SELECTION_RESTORATION,
  EVENT_SUPPRESSION, 
  ON_DOM_READY_QUEUEING, 
];
var Mixin = {
  getTransactionWrappers: function() {
        return TRANSACTION_WRAPPERS;
    }
  }

其中的ON_DOM_READY_QUEUEING正是子挂载结束后执行回调序列中的回调函数。
ReactReconcileTransaction事务则是在第一进入项目加载第一个节点时开启的。
大概流程如下
图片描述

attachRefs

ref引用主要是ReactRef中的attachRef函数

//ref是我们标签中书写的ref
//component是书写ref的组件。上面提过,哪怕是input也会被封装成组件再挂载。
//owner是component所在的组件
function attachRef(ref, component, owner) {
  if (typeof ref === 'function') {
    ref(component.getPublicInstance());
  } else {
    // Legacy ref
    ReactOwner.addComponentAsRefTo(component, ref, owner);
  }
}

可以看到,正是在这边组件获取到了ref引用。

ref是function

当ref时function时,函数的参数传入的是component.getPublicInstance()
component上面说过,有可能是

  • ReactEmptyComponent 空组件
  • ReactHostComponent 对原生HTML标签的封装
  • ReactCompositeComponent 用户自定义组件

这里不考虑空组件。主要是ReactHostComponentReactCompositeComponent
首先看ReactCompositeComponent。这个简单

getPublicInstance: function () {
    var inst = this._instance;
    return inst;
  },

直接传入了实例化对象。通过这个方法,我们可以通过ref随时调用子组件的内部方法
再看ReactHostComponent

  getPublicInstance: function() {
    return getNode(this);
  },
  
  function getNodeFromInstance(inst) {

      if (inst._hostNode) {
        return inst._hostNode;
      }
    
      var parents = [];
    _prodInvariant('34') : void 0;
        inst = inst._hostParent;
      }
    
      for (; parents.length; inst = parents.pop()) {
        precacheChildNodes(inst, inst._hostNode);
      }
    
      return inst._hostNode;
    }

这里是根据getNode获取的引用。getNode执行的又是ReactDOMComponentTree中的getNodeFromInstance方法。
从这个方法我们不能发现,这里ref获取到的参数就是实例的_hostNode

那么这个_hostNode又是什么?参考源码,发现组件在挂载过程中
执行了这么句代码

 el = ownerDocument.createElement(this._currentElement.type);
 ReactDOMComponentTree.precacheNode(this, el);
 
 //。。。。。我是分隔符
 
 function getRenderedHostOrTextFromComponent(component) {
  var rendered;
  while (rendered = component._renderedComponent) {
    component = rendered;
  }
  return component;
}

function precacheNode(inst, node) {
  var hostInst = getRenderedHostOrTextFromComponent(inst);
  hostInst._hostNode = node;
  node[internalInstanceKey] = hostInst;
}

通过当前元素类型创造节点。接着将节点塞到了组件中,保存的正是_hostNode

ref不是函数

ref不是函数执行的是ReactOwner的attachRef

attachRef: function (ref, component) {
    var inst = this.getPublicInstance();
    var publicComponentInstance = component.getPublicInstance();
    var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs;
    refs[ref] = publicComponentInstance;
  },

而这里传入的component正是有ref属性的组件(前面说过,哪怕写在input上,挂载时也是包装成组件再挂载),此时的ReactOwner(this)是component所在的组件。
可以看到此时将引用赋值给了this.refs的一个属性。属性名正是传入的ref。
很显然,ref可以如果不是function,也不一定是string。只要是合法的object key就可以了。当然,为了便于开发,还是推荐使用有意义的字符

ref拿到的到底是什么

很多文章里面说ref拿到的是真实DOM节点。其实这种说法很笼统。也很让人困惑,上面看到我们拿到的要么是实例(我们自定义组件)要么是component的_hostNode属性,这个好像不是真实DOM 啊
首先,什么叫真实DOM?
之前我以为嵌入DOM树的,在页面上显示的就是真实DOM节点。我们可以打印一下
图片描述
这还只是部分属性。可以看到DOM其实就是拥有大量属性的一个对象。
图片描述
只不过这个对象时塞入了DOM树。
而我们拿到的_hostNode是什么?也是我们通过document.createElement方法创建的element对象啊。只是这时候对象保存在了虚拟DOM中,然后再塞入真实DOM树。
所以说_hostNode和真实DOM树中的DOM的关系就是不同对象的不同属性指向的同一块存储空间,引用着同一个值而已。

我们虽然是通过虚拟DOM的_hostNode拿到这个值,但是对他的操作会体现在真实DOM节点上。说白了就是对象的引用赋值。
所以,ref拿到的是真实DOM的引用这个说法更准确。

查看原文

赞 8 收藏 6 评论 0

橙汁绿茶 赞了回答 · 2019-10-25

解决国内服务器连接国外数据库慢怎么办?

阿里云服务器加个缓存服务器就解决了,用户访问的时候你程序优先读取缓存,有缓存就返回,没有就读取美国数据库,写入缓存。
可以解决你大部分的问题

关注 5 回答 4

橙汁绿茶 回答了问题 · 2019-10-25

怎么样才能画出这种关系图?或者是有这种插件?

你可以使用canvas直接画图,你要的功能比较特殊,建议你自己实现一个

关注 4 回答 4

橙汁绿茶 回答了问题 · 2019-10-25

解决Cannot read property 'then' of undefined 提问

.then是Promise对象的用法,你这里提示对象是undefined,在第一张图里看到你调用了login().then 在第二张图里看到你的login方法并没有返回promise对象,你可以参考_login函数来对login()进行改造,使它返回一个promise对象

关注 2 回答 1

橙汁绿茶 回答了问题 · 2019-10-25

解决Object.create()创建的对象使用原型上的方法无效

其实我觉得这是个适合初学者的好问题,不过你没表述清楚
你的意思是o 和 res 的方法明明一样,为什么o却不能做res的事情
这里要理解一个概念,js里的函数传参,如果传的是对象,那么传的就是对象的引用(地址),比如

function add(array) {
    array.push(4)
}
let arr = [1,2,3]
add(arr)
console.log(arr) //1,2,3,4

也就是说,你写在createServer函数里的这个res,是调用的人传给你的,你的res怎么变,外面的res也跟着变,这就比传统的return赋值方便的多。你现在建了一个新的对象o,在上面调用再多方法也是没有用的,因为调用你的人不知道你在里面干啥,他只关注传给你的那个res怎么变,结果你复制了个新的对象,没管res,res没变,服务器自然也没有反应。

关注 3 回答 2

橙汁绿茶 回答了问题 · 2019-10-25

git clone fatal: error in sideband demultiplexer 有没有哪位知道什么原因呢

相关问题[
输入下面这个指令后再试
git config --global http.postBuffer 1048576000

关注 3 回答 2

橙汁绿茶 回答了问题 · 2019-10-25

解决关于copy函数

console虽然不在ECMAScript的列表里,但是每个浏览器都实现了它,这是相关回答为什么console不被视为标准的内置对象?
这是console标准协议
copy函数是chrome浏览器自己实现的,参考官方API
一句话总结:console是通用工具,控制台指令是独家特供
服了,这没采纳

关注 3 回答 2

橙汁绿茶 发布了文章 · 2017-11-30

前端论坛、博客及公众号汇总

赞 75 收藏 168 评论 2

认证与成就

  • 获得 284 次点赞
  • 获得 16 枚徽章 获得 1 枚金徽章, 获得 6 枚银徽章, 获得 9 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2015-03-29
个人主页被 1.9k 人浏览