先附上github地址 react-dom-faster
react-dom提供的几个同构接口:
1.renderToString:将React Component转化为HTML字符串,生成的HTML的DOM会带有额外属性:各个DOM会有data-react-id属性,第一个DOM会有data-checksum属性。
2.renderToStaticMarkup:同样是将React Component转化为HTML字符串,但是生成HTML的DOM不会有额外属性,从而节省HTML字符串的大小。
3.renderToNodeStream: 以流的形式输出html, 不用像renderToString生成整个html才发送给客户端。相对于renderToString能更快的响应客户端,提升页面渲染速度。
4.renderToStaticNodeStream:和renderToNodeStream一样,也是输出流,但是html中不带data-reactroot等属性。
在最近hybrid业务开发中,采用ssr方案。由于我们不是页面直出,是ajax数据直出,renderToNodeStream/renderToStaticNodeStream不适合我们的业务形态。所以选择了更快的 renderToStaticMarkup。本文的主题也是从renderToStaticMarkup入手来提升ssr渲染速度。
SSR基本原理
1.jsx通过babel转化后调用的React.createElement(这里可以修改@babel/plugin-transform-react-jsx的pragma
来自定义调用函数名)
2.React.createElement生成就是vdom对象
{
type: 'ul',
props: {
class: 'container'
},
children: [
{type: 'li', props: {}, children: ['1']},
{type: 'li', props: {}, children: ['2']},
{type: 'li', props: {}, children: ['3']}
]
}
复制代码
3.renderToStaticMarkup调用component方法得到vdom,遍历这棵vdom树递归生成html进行拼接。
对于renderToStaticMarkup而言,不需要生成data-checksum、data-react-id等属性,所以vdom是多余且消耗性能的一步。
优化方向
可以拦截React.createElement提供自定义的render函数,让React.createElement生成的就是html,我们只需要对html进行拼接即可
export default function fastRenderToStaticMarkup (renderComponent) {
React.createElement = h;
const html = renderComponent();
React.createElement = oldH;
if (!html) {
return '';
} else {
return html.hasOwnProperty('html') ? html.html : html;
}
}
复制代码
h函数实现如下:
const h = function (type, attrs, ...children) {
attrs = attrs || {};
// dom element
if (typeof type === 'string') {
let html = '<' + type + getStaticMarkupAttrStr(attrs);
if (!emptyTags[type]) {
html += '>';
if (attrs.hasOwnProperty('dangerouslySetInnerHTML')) {
html += attrs.dangerouslySetInnerHTML.__html;
}
html += hChildren(children);
html += '</' + type + '>';
} else {
html += '/>';
}
return { html };
}
// support React.Fragment
if (type === React.Fragment) {
return { html: hChildren(children) };
}
// support class component
if (isReactComponent(type)) {
const props = {
...(type.defaultProps || {}),
...attrs,
children: children
};
const instance = new type(props);
instance.props = props;
instance.componentWillMount && instance.componentWillMount();
instance.UNSAFE_componentWillMount && instance.UNSAFE_componentWillMount();
return instance.render();
}
// support function component
if (typeof type === 'function') {
const props = {
...(type.defaultProps || {}),
...attrs,
children: children
};
return type(props);
}
};
复制代码
通过拦截,使用自定的h函数保证React.createElement生成是string, 省去了vdom的中间产物。这个模块已经在业务中落地,相比react-dom renderToStaticMarkup渲染时间提升了6倍,极大的提升了node的吞吐量。
具体实现可以参考react-dom-faster