前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React直出实现与原理

React直出实现与原理

作者头像
IMWeb前端团队
发布2019-12-03 17:15:00
7560
发布2019-12-03 17:15:00
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb 杨文坚 原文出处:IMWeb社区 未经同意,禁止转载 前一篇文章我们介绍了虚拟DOM的实现与原理,这篇文章我们来讲讲React的直出。 比起MVVM,React比较容易实现直出,那么React的直出是如何实现,有什么值得我们学习的呢?

为什么MVVM不能做直出?

对于MVVM,HTML片段即为配置,而直出后的HTML无法还原配置,所以问题不是MVVM能否直出,而是在于直出后的片段能否还原原来的配置。下面是一个简单的例子:

代码语言:javascript
复制
<sapn>Hello {name}!</span>

上面这段HTML配置和数据在一起,直出后会变成:

代码语言:javascript
复制
<span>Hello world!</span>

这时候当我们失去了name的值改变的时候会导致页面渲染这个细节。当然,如果为了实现MVVM直出我们可能有另外的方法来解决,例如直出结果变成这样:

代码语言:javascript
复制
<span>Hello <span q-text="name">world</span>!</span>

这时候我们是可以把丢失的信息找回来的,当然结构可能和我们想象的有些差别。当然还有其他问题,例如直出HTML不一定能反向还原数据,由于篇幅问题,这里不展开讨论。

React如何直出?

如图:

  • React的虚拟DOM的生成是可以在任何支持Javascript的环境生成的,所以可以在NodeJS或Iojs环境生成
  • 虚拟DOM可以直接转成String
  • 然后插入到html文件中输出给浏览器便可

具体例子可以参考,https://github.com/DavidWells/isomorphic-react-example/,下面是其渲染路由的写法:

代码语言:javascript
复制
// https://github.com/DavidWells/isomorphic-react-example/blob/master/app/routes/coreRoutes.js

var React = require('react/addons');
var ReactApp = React.createFactory(require('../components/ReactApp').ReactApp);

module.exports = function(app) {

    app.get('/', function(req, res){
        // React.renderToString takes your component
        // and generates the markup
        var reactHtml = React.renderToString(ReactApp({}));
        // Output html rendered by react
        // console.log(myAppHtml);
        res.render('index.ejs', {reactOutput: reactHtml});
    });

};

OK,我们现在知道如何利用React实现直出,以及如何前后端代码复用。

但还有下面几个问题有待解决:

  • 如何渲染文字节点,每个虚拟DOM节点是需要对应实际的节点,但无法通过html文件生成相邻的Text Node,例如下面例子应当如何渲染:React.createClass({ render: function () { return ( <p> Hello {name}! </p> ); } })
  • 如何避免直出的页面被React重新渲染一遍?或者直出的页面和前端的数据是不对应的怎么办?

相邻的Text Node,想多了相邻的span而已

通过一个简单的例子,我们可以发现,实际上React根本没用Text Node,而是使用span来代替Text Node,这样就可以实现虚拟DOM和直出DOM的一一映射关系。

重复渲染?没门

刚刚的例子,如果我们通过React.renderToString拿到<Test />可以发现是:

代码语言:javascript
复制
<p data-reactid=".0" data-react-checksum="-793171045"><span data-reactid=".0.0">Hello </span><span data-reactid=".0.1">world</span><span data-reactid=".0.2">!</span></p>

我们可以发现一个有趣的属性data-react-checksum,这是啥?实际上这是上面这段HTML片段的adler32算法值。实际上调用React.render(<MyComponent />, container);时候做了下面一些事情:

  • 看看container是否为空,不为空则认为有可能是直出了结果。
  • 接下来第一个元素是否有data-react-checksum属性,如果有则通过React.renderToString拿到前端的,通过adler32算法得到的值和data-react-checksum对比,如果一致则表示,无需渲染,否则重新渲染,下面是adler32算法实现: `javascript var MOD = 65521;

// This is a clean-room implementation of adler32 designed for detecting // if markup is not what we expect it to be. It does not need to be // cryptographically strong, only reasonably good at detecting if markup // generated on the server is different than that on the client. function adler32(data) { var a = 1; var b = 0; for (var i = 0; i < data.length; i++) { a = (a + data.charCodeAt(i)) % MOD; b = (b + a) % MOD; } return a | (b << 16); }

代码语言:javascript
复制
* 如果需要重新渲染,先通过下面简单的差异算法找到差异在哪里,打印出错误:
```javascript/**
  • Finds the index of the first character下面是首屏渲染时的主要逻辑,可以发现React对首屏实际上也是通来渲染的: ```javascript _mountImageIntoNode: function(markup, container, shouldReuseMarkup) { ("production" !== process.env.NODE_ENV ? invariant( container && ( (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) ), 'mountComponentIntoNode(...): Target container is not valid.' ) : invariant(container && ( (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) )));
  • that's not common between the two given strings.
  • @return {number} the index of the character where the strings diverge */ function firstDifferenceIndex(string1, string2) { var minLen = Math.min(string1.length, string2.length); for (var i = 0; i < minLen; i++) { if (string1.charAt(i) !== string2.charAt(i)) { return i; } } return string1.length === string2.length ? -1 : minLen; }
代码语言:txt
复制
if (shouldReuseMarkup) {
代码语言:txt
复制
  var rootElement = getReactRootElementInContainer(container);
代码语言:txt
复制
  if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
代码语言:txt
复制
    return;
代码语言:txt
复制
  } else {
代码语言:txt
复制
    var checksum = rootElement.getAttribute(
代码语言:txt
复制
      ReactMarkupChecksum.CHECKSUM_ATTR_NAME
代码语言:txt
复制
    );
代码语言:txt
复制
    rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
代码语言:txt
复制
    var rootMarkup = rootElement.outerHTML;
代码语言:txt
复制
    rootElement.setAttribute(
代码语言:txt
复制
      ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
代码语言:txt
复制
      checksum
代码语言:txt
复制
    );
代码语言:txt
复制
    var diffIndex = firstDifferenceIndex(markup, rootMarkup);
代码语言:txt
复制
    var difference = ' (client) ' +
代码语言:txt
复制
      markup.substring(diffIndex - 20, diffIndex + 20) +
代码语言:txt
复制
      '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);
代码语言:txt
复制
    ("production" !== process.env.NODE_ENV ? invariant(
代码语言:txt
复制
      container.nodeType !== DOC_NODE_TYPE,
代码语言:txt
复制
      'You\'re trying to render a component to the document using ' +
代码语言:txt
复制
      'server rendering but the checksum was invalid. This usually ' +
代码语言:txt
复制
      'means you rendered a different component type or props on ' +
代码语言:txt
复制
      'the client from the one on the server, or your render() ' +
代码语言:txt
复制
      'methods are impure. React cannot handle this case due to ' +
代码语言:txt
复制
      'cross-browser quirks by rendering at the document root. You ' +
代码语言:txt
复制
      'should look for environment dependent code in your components ' +
代码语言:txt
复制
      'and ensure the props are the same client and server side:\n%s',
代码语言:txt
复制
      difference
代码语言:txt
复制
    ) : invariant(container.nodeType !== DOC_NODE_TYPE));
代码语言:txt
复制
    if ("production" !== process.env.NODE_ENV) {
代码语言:txt
复制
      ("production" !== process.env.NODE_ENV ? warning(
代码语言:txt
复制
        false,
代码语言:txt
复制
        'React attempted to reuse markup in a container but the ' +
代码语言:txt
复制
        'checksum was invalid. This generally means that you are ' +
代码语言:txt
复制
        'using server rendering and the markup generated on the ' +
代码语言:txt
复制
        'server was not what the client was expecting. React injected ' +
代码语言:txt
复制
        'new markup to compensate which works but you have lost many ' +
代码语言:txt
复制
        'of the benefits of server rendering. Instead, figure out ' +
代码语言:txt
复制
        'why the markup being generated is different on the client ' +
代码语言:txt
复制
        'or server:\n%s',
代码语言:txt
复制
        difference
代码语言:txt
复制
      ) : null);
代码语言:txt
复制
    }
代码语言:txt
复制
  }
代码语言:txt
复制
}
代码语言:txt
复制
("production" !== process.env.NODE_ENV ? invariant(
代码语言:txt
复制
  container.nodeType !== DOC_NODE_TYPE,
代码语言:txt
复制
  'You\'re trying to render a component to the document but ' +
代码语言:txt
复制
    'you didn\'t use server rendering. We can\'t do this ' +
代码语言:txt
复制
    'without using server rendering due to cross-browser quirks. ' +
代码语言:txt
复制
    'See React.renderToString() for server rendering.'
代码语言:txt
复制
) : invariant(container.nodeType !== DOC_NODE_TYPE));
代码语言:txt
复制
se(container, markup);

}

代码语言:txt
复制
### 最后

尝试一下下面的代码,想想React为啥认为这是错误的?

```javascript

var Test = React.createClass({

getInitialState: function() {

代码语言:txt
复制
return {name: 'world'};

},

render: function() {

代码语言:txt
复制
return (
代码语言:txt
复制
    <p>Hello</p>
代码语言:txt
复制
    <p>
代码语言:txt
复制
        Hello {this.state.name}!
代码语言:txt
复制
    </p>
代码语言:txt
复制
);

}

});

React.render(

<Test />,

document.getElementById('content')

);

代码语言:txt
复制
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2015-05-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么MVVM不能做直出?
  • React如何直出?
  • 相邻的Text Node,想多了相邻的span而已
  • 重复渲染?没门
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档