3 行代码能不能给 React 文档写个导航目录

1,775 阅读7分钟

背景

众所周知 React 文档一言难尽,那属于是谁看谁知道了。且不说文档内容和组织如何,那不是咱能改变的,咱也没那个实力去评价。有个关键问题是:没有文档导航目录,导致有些长文档只看滚动条都能把人(我)劝退。

image.png

React现役文档

没有目录的长文档容易把人劝退的原因有哪些呢?

  • 内容结构不直观 一般文档内容高度超过一屏,就没法一眼看出文档的大体结构和内容

  • 导航不方便 点进某个文档想先看看结论怎么办,疯狂拉扯滚动条;看到结尾想回到顶部怎么办,疯狂拉扯滚动条;文档引用前面或后面某个主题怎么办,疯狂拉扯滚动条

  • 容易忘记主题 一些比较长的段落,特别是有大段代码那些,可能看着看着就忘记这一段的主题

  • 查找内容不方便 有时候明明记得有些东西在文档里看过,回去找的时候就是找不到(能搜到的另说),主要还是前两点引起的

React新版文档

相比之下新版文档体验就好很多,左侧是章节目录右侧是文档目录,内容结构清晰很多,导航也很方便,目录里面的标题都可以点击跳转。

image.png

React新版文档

虽然新版文档好很多,但距离完成应该还要些时间,目前进度是:Learn React 70%,API Reference 20%。想看中文版得更久。。。

image.png

如何解决

咋解决,这还不简单,给他写个文档目录就完事了!没错,当时我心里就是这么想的!(往往产品锦鲤听到这话都会蜜汁微笑、笑而不语。。。)

没用多久,我就写出下面3行代码

const linksHtml = [...document.querySelectorAll('h2,h3')].map(n => `<p style="padding:10px;"><a href="#${n.id}">${n.textContent}</a></p>`).join('');
const containerStyle = 'position: fixed;width: 260px;max-height:calc(100vh - 100px);top: 80px;right: 20px;font-size: 14px;border-radius: 5px;background: #fff;overflow-y:auto;';
document.body.insertAdjacentHTML('beforeend', `<div style="${containerStyle}">${linksHtml}</div>`);
  • 第一行代码是提取文章中的标题节点(标签名为h2h3),并一一生成导航链接(<a> 标签节点),最后拼接成 htmllinksHtml
  • 第二行代码是声明导航目录容器的样式
  • 第三行代码是将 linksHtml 内嵌到导航目录容器的 html,再把整串 html 追加到 document.body 末尾

在浏览器控制台运行看看效果

image.png

功能基本满足需求,有目录大纲也能导航跳转,看着还行哦~

如何使用

代码写好之后平常怎么使用呢?可以借助 DevToolsSnippets 存储代码,需要的时候手工执行。如何用 Snippets 执行代码可以看下面这张图,更详细的说明可以查看Edge文档

image.png

如何易用

把代码做成 snippet 只是能用,但每次新打开文档页面都得手工执行这段代码,是不是有点麻烦?能做成自动的吗?

当然能!只要把上述代码自动注入到 React 文档网站的 JS 文件里面就OK啦!

由于服务器不是咱们的,想动服务器是不可能了!除非你有高超的黑客技术,还有不怕劳改的觉悟!实际上我们只需要篡改本地的 React 文档页面就行,因此,可以从下述4个方向考虑:

  • 1、使用抓包工具(Fiddler、Charles)拦截请求,篡改静态资源文件,将代码写入
  • 2、借助已有插件,通过插件操作 DOM,将代码写入页面(Custom Style Script
  • 3、自己写个插件,插件的 content scripts 会被注入到页面并自动执行
  • 4、借助 DevToolsLocal Overrides 功能,使用本地文件覆盖线上文件

浏览器插件也能拦截http请求和响应,理论上也能篡改静态资源文件,只是常规拦截方式无法直接获取和修改响应内容,需要借助 DevTools 实现,略为复杂,篇幅有限这里不做深入探讨。本文咱们只介绍 Local Overrides 方法。

Local Overrides 通常可以用来调试线上代码,这里算是一个实际例子!

Local Overrides

使用 Local Overrides 功能覆盖线上文件的具体步骤如下:

  • 1、找到 Overrides 面板,点击 Select forlder for overrides image.png

  • 2、选择任意文件夹,用于存储要覆盖的文件,这里选了名为 test 的目录 image.png

  • 3、允许弹出的权限提示 image.png

  • 4、至此 test 文件夹就已经准备就绪,可以用于存储要覆盖的文件 image.png

  • 5、切换到 Page 面板,选择任意 JS 文件进行覆盖,这里我们选择 app-e6edaaf1f610273f88f3.js 这个文件 image.png

  • 6、往选择的 JS 文件写入代码 image.png

  • 7、刷新页面看看效果 image.png

搞定!之后每次进页面或刷新页面,右侧的导航目录都会自动渲染!

这里一番折腾,实际只是想使用本地的 app-e6edaaf1f610273f88f3.js 文件覆盖线上的同名文件,然后通过修改本地文件达到篡改页面逻辑的目的。

Local Overrides 的详细介绍可以查看Edge文档

兼容SPA

本以为可以到此结束,没想到使用一会后又出现新的问题!由于 React 文档网站是个单页面应用(SPA),切换文章时不会刷新页面,因此导航目录不会自动更新!比如从 API REFERENCE 下的 React 切换到 ReactDOM 这篇文章,目录不会自动更新。

这里有4个思路可以解决问题:

  • 1、切换文章的时候 document.title 会变化,可以用 MutationObserver 监听 title 变化,在回调函数中重新生成目录或刷新页面
  • 2、重写 history.pushStatehistory.replaceState,另外还要监听 popstate 事件,在历史记录变化时重新生成目录或刷新页面
  • 3、事件代理监听文章标题的点击事件,在回调函数中重新生成目录或刷新页面
  • 4、setInterval 轮询检查页面链接,链接变化时重新生成目录或刷新页面

这里只用方法1监听 title 变化实现,话不多说直接上代码:

  • title 变化时刷新页面,只需添加下述2行代码
const target = document.querySelector('title');
new MutationObserver(() => location.reload()).observe(target, {childList: true});
  • title 变化时重新生成目录的完整代码如下
function renderToc() {
    const linksHtml = [...document.querySelectorAll('h2,h3')].map(n => `<p style="padding:10px;"><a href="#${n.id}">${n.textContent}</a></p>`).join('');
    const containerStyle = 'position: fixed;width: 260px;max-height:calc(100vh - 100px);top: 80px;right: 20px;font-size: 14px;border-radius: 5px;background: #fff;overflow-y:auto;';
    document.body.insertAdjacentHTML('beforeend', `<div style="${containerStyle}">${linksHtml}</div>`);
    return document.body.lastElementChild;
}

let tocNode = renderToc();

const target = document.querySelector('title');
new MutationObserver(() => {
    if (tocNode) tocNode.remove();
    tocNode = renderToc();
}).observe(target, {
    childList: true
});

注意:实现过程中发现,在React文档页面使用方法2重写 pushStatereplaceState 的操作不能永久生效,切换到其他文章后重写的方法会被还原

调试之后发现可以通过重写 gatsbynavigation.js 中声明的全局函数 window.___navigate 达到目的,猜测被还原的原因也可能与此有关

具体原因留给有兴趣的小伙伴去探究,有结果可以在评论区告诉我

至此,咱们算是完成了一个基本可用的导航目录 Demo,还有很多视觉和交互问题需要改进,比如:标题链接没有高亮、标题链接没有分层级缩进、标题过长出现横向滚动条等等。进一步的优化会在下一篇文章中细说,如果有兴趣也可以直接体验完成版 OneToc 浏览器插件。

OneToc

OneTocTocTable Of Content 的缩写)的主要功能是为技术文档、技术博客等网站添加导航目录,提供更好的阅读体验。代码仓库地址是:github.com/Whilconn/on…

使用说明

使用效果图

image.png

image.png

欢迎大家安装体验以及反馈提问!