如何干掉知乎的全部DIV

7,477 阅读3分钟

欢迎订阅React技术揭秘

这是个无聊的问题吗

当提出这个问题时,我的第一反应是——我还真是无聊 🤷‍♂️

转念一想,em~,好像事情没有这么简单🤔

如果直接选择所有div再遍历删除的话,div的子孙节点也被删除了。有什么办法能在保留整棵DOM树层级关系的前提下,只删除div节点呢?

我陷入了沉思。。。。。

知乎是React写的,React用JSX来表示页面层次结构,JSX在编译时会被babel转换为React.createElement

在代码运行时,React获取的其实是React.createElement()的返回值。

办法来了!

我们只需要覆写一下React.createElement方法,当遇到type === 'div',我们将type修改为React.Fragment,即我们把所有div节点都变成Fragment,那不就能在保持树的层级关系的同时去掉div了?

让我们开始吧!!

如何拿到React对象

这时候遇到了第一个问题:知乎没把React暴露到全局(废话,当然不会😠),怎么获取React对象呢?

好在当我们使用React Dev Tools时,Dev Tools会向页面注入全局变量__REACT_DEVTOOLS_GLOBAL_HOOK__,这个变量会成为链接ReactDev Tools的桥梁。

其中的renderers属性指页面使用的渲染器,在网页端就是我们熟悉的React-DOM(在客户端当然就是React-Native啦)

我们发现一个方法findFiberByHostInstance

方法名居然出现了Fiber字样!!

我们知道,Fiber是React的最小可调度单元(别问为什么,问就是安利我写的React技术揭秘

findFiberByHostInstance方法所在文件一定有React相关定义。我们右键跳转到定义函数的文件,

在文件内搜.createElement

果然让我们找到了。打上断点,刷新页面试试~

果然进来了,事情越发有趣了 😊😊😊

看看o.a是什么,em~~一个对象,内部有ChildrencreateElement。。。。。。

看来这就是React对象了

赶忙把来之不易的React对象保存在全局,顺便把React.createElement也保存一份。

现在放开断点,window.React已经指向知乎首页内部使用的React啦。

修改React.createElement方法

我们知道,React.createElement方法第一个参数为type(别问为什么,问又是一波安利React技术揭秘),我们再到React文档中找来React.Fragment的定义。

if (typeof Symbol === 'function' && Symbol.for) {
  const symbolFor = Symbol.for;
  REACT_ELEMENT_TYPE = symbolFor('react.element');
  REACT_PORTAL_TYPE = symbolFor('react.portal');
  REACT_FRAGMENT_TYPE = symbolFor('react.fragment');
  REACT_STRICT_MODE_TYPE = symbolFor('react.strict_mode');
  REACT_PROFILER_TYPE = symbolFor('react.profiler');
  REACT_PROVIDER_TYPE = symbolFor('react.provider');
  // ...
}

接下来,修改全局变量

React.createElement = (type, ...args) => {
    if (type === 'div') {
        type = Symbol.for('react.fragment');
    }
    // originCreateElement才是正经的React.createElement
    return originCreateElement(type, ...args);
}

让我们康康此时的页面结构

ok~~~ 满屏的div套div(嫌弃脸),接着我们轻轻的点一下关注按钮,触发随便啥组件的setState

接下来,就是见证奇迹的时刻。。。

除了根节点,其他div都消失啦,终于恢复了往日的清爽界面(大误)

总结

通过这篇无聊的文章,我们认识到:

  1. React每次触发setState更新,都会重新遍历整棵Fiber树。(否则也不会点击一个按钮整个页面的div都消失)
  2. React.createElement的深度应用。
  3. 知乎真是有太多div

PS:如果你用Chrome将被压缩后的代码formatted后打上断点,刷新页面进入断点后浏览器卡死,不要怀疑,这是Chrome的bug,截止版本 81.0.4044.138(正式版本) (64 位)还未修复,建议使用chrome开发者版本 Chrome Canary