React 项目中动态加载 Mathjax

做一个 React 项目的时候,想在网页上渲染数学公式,不想用别人封装好的 React 组件,采用动态加载的方式直接渲染 DOM。

[email protected] 做了很大的一个更新,使用方式也和 2.x 版本不同。

React 动态加载 js

首先需要知道的一件事就是如何动态加载 js,用 js 将一个新的 script 节点挂载到 dom 上即可。

创建一个 dom 元素 script,设置 src 属性为要加载的链接,然后将节点添加到 body 元素内。

封装成函数如下,只需要传入需要加载的 js 链接,然后使用 .then 方法进行后续操作。

export const loadJS = (url: string) =>
  new Promise(function (resolve, reject) {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    document.body.appendChild(script);
    script.onload = function () {
      resolve(`success: ${url}`);
    };
    script.onerror = function () {
      reject(Error(`${url} load error!`));
    };
  });

在需要使用的地方:

loadJS(url).then(() => {
  // do something
}).catch(() => {
  // do something
});

[email protected] 版本

先给出参考的官方链接,本文讲的可能不太清楚。

加载了 mathjax 之后,我们只要在合适的时机让 mathjax 渲染 dom 即可。

举个例子:

componentDidMount() {
  const mathjaxUrl = 'https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-AMS_CHTML';
  loadJS(mathjaxUrl).then(() => {
    this.showMathjax();
  });
}

componentDidUpdate () {
  if (!(window as any).MathJax) {
    (window as any).MathJax.Hub.Queue(['Typeset', (window as any).MathJax.Hub, ReactDOM.findDOMNode(this)]);
  }
}

showMathjax = () => {
  if ((window as any).MathJax) {
    (window as any).MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [['$', '$']],
        displayMath: [['$$', '$$']],
        skipTags: ['script', 'noscript', 'style', 'textarea', 'code', 'a'],
      },
      CommonHTML: {
        scale: 120,
        linebreaks: { automatic: true },
      },
      'HTML-CSS': { linebreaks: { automatic: true } },
      SVG: { linebreaks: { automatic: true } },
      TeX: { noErrors: { disabled: true } },
    });
  } else {
    setTimeout(this.showMathjax, 1000);
  }
};

2.x 的版本,我们要使用 Mathjax.Hub.Config 来配置,这几个配置看起来也很好懂,tex2jax 中设置了在什么字符包裹的时候渲染数学公式。

设置了行内公式用 $...$ 包裹,多行公式用 $$...$$ 来包裹,对于什么什么标签内的则不渲染。
然后就是设置好几种模式下渲染的表现怎么样,上面我们加载的 js 时候后面有一个 query ?config=TeX-AMS_CHTML,说明我们加载的是 CHTML 的配置,各种配置显示效果不同。参见 https://docs.mathjax.org/en/v2.7-latest/config-files.html

Mathjax 加载好之后就会渲染页面,但是对于单页面应用来说, Mathjax 并不会在页面 DOM 更新的时候重新渲染,我们需要使用 MathJax.Hub.Queue(['Typeset', MathJax.Hub, ReactDOM.findDOMNode(this)]); 来让 Mathjax 手动渲染 DOM。添加在 componentDidUpdate 中即可。

[email protected] 版本

同样给出官方文档链接:

3 的版本 Mathjax 的加载,配置方式都有不同。

Upgrading from v2 to v3: http://docs.mathjax.org/en/latest/upgrading/v2.html

加载时可以直接加载不同配置的 js,不需要再使用 ?config=xxx 了,比如说加载 https://cdn.jsdelivr.net/npm/[email protected]/es5/tex-mml-chtml.js

Mathjax.Hub 方法被移除,现在设置配置只需要给 window.Mathjax 赋值一个配置对象即可。

官方还提供一个一键转换配置的链接: MathJax Configuration Converter

还是给出我的配置:

(window as any).MathJax = {
  tex: {
    inlineMath: [['$', '$']],
    displayMath: [['$$', '$$']],
  },
  options: {
    skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'code', 'a'],
  },
  chtml: {
    scale: 1.2,
  },
  startup: {
    ready: () => {
      (window as any).MathJax.startup.defaultReady();
      (window as any).MathJax.startup.promise.then(() => {
        console.log('MathJax initial typesetting complete');
      });
    },
  },
};

Mathjax 脚本加载好之后会读取 window.Mathjax 为配置并且替换为 Mathjax 对象,然后你就可以调用相关函数了。

Mathjax 初始化时会调用配置中的 startup.ready 方法,你可以在里面做一下提示或者其他配置。

还是那个问题,Mathjax 在 DOM 更新时不会重新渲染,需要使用 MathJax.typesetPromise() 方法。也在 componentDidUpdate 中设置即可。

componentDidUpdate() {
  const MathJax = (window as any).MathJax;
  if (MathJax) {
    MathJax.typesetPromise && MathJax.typesetPromise();
  }
}

这个方法是异步方法,还有一个相同功能的同步方法: MathJax.typeset()

总得有个结尾吧