React直出实现与原理

前一篇文章我们介绍了虚拟DOM的实现与原理,这篇文章我们来讲讲React的直出。 比起MVVM,React比较容易实现直出,那么React的直出是如何实现,有什么值得我们学习的呢?

为什么MVVM不能做直出?

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

<sapn>Hello {name}!</span>

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

<span>Hello world!</span>

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

<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/,下面是其渲染路由的写法:

// 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 />可以发现是:

<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算法实现:
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);
}
  • 如果需要重新渲染,先通过下面简单的差异算法找到差异在哪里,打印出错误:
/**
 * Finds the index of the first character
 * 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;
}

下面是首屏渲染时的主要逻辑,可以发现React对首屏实际上也是通过innerHTML来渲染的:

_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)
    )));

    if (shouldReuseMarkup) {
      var rootElement = getReactRootElementInContainer(container);
      if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
        return;
      } else {
        var checksum = rootElement.getAttribute(
          ReactMarkupChecksum.CHECKSUM_ATTR_NAME
        );
        rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);

        var rootMarkup = rootElement.outerHTML;
        rootElement.setAttribute(
          ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
          checksum
        );

        var diffIndex = firstDifferenceIndex(markup, rootMarkup);
        var difference = ‘ (client) ‘ +
          markup.substring(diffIndex - 20, diffIndex + 20) +
          ‘\n (server) ‘ + rootMarkup.substring(diffIndex - 20, diffIndex + 20);

        ("production" !== process.env.NODE_ENV ? invariant(
          container.nodeType !== DOC_NODE_TYPE,
          ‘You\‘re trying to render a component to the document using ‘ +
          ‘server rendering but the checksum was invalid. This usually ‘ +
          ‘means you rendered a different component type or props on ‘ +
          ‘the client from the one on the server, or your render() ‘ +
          ‘methods are impure. React cannot handle this case due to ‘ +
          ‘cross-browser quirks by rendering at the document root. You ‘ +
          ‘should look for environment dependent code in your components ‘ +
          ‘and ensure the props are the same client and server side:\n%s‘,
          difference
        ) : invariant(container.nodeType !== DOC_NODE_TYPE));

        if ("production" !== process.env.NODE_ENV) {
          ("production" !== process.env.NODE_ENV ? warning(
            false,
            ‘React attempted to reuse markup in a container but the ‘ +
            ‘checksum was invalid. This generally means that you are ‘ +
            ‘using server rendering and the markup generated on the ‘ +
            ‘server was not what the client was expecting. React injected ‘ +
            ‘new markup to compensate which works but you have lost many ‘ +
            ‘of the benefits of server rendering. Instead, figure out ‘ +
            ‘why the markup being generated is different on the client ‘ +
            ‘or server:\n%s‘,
            difference
          ) : null);
        }
      }
    }

    ("production" !== process.env.NODE_ENV ? invariant(
      container.nodeType !== DOC_NODE_TYPE,
      ‘You\‘re trying to render a component to the document but ‘ +
        ‘you didn\‘t use server rendering. We can\‘t do this ‘ +
        ‘without using server rendering due to cross-browser quirks. ‘ +
        ‘See React.renderToString() for server rendering.‘
    ) : invariant(container.nodeType !== DOC_NODE_TYPE));

    setInnerHTML(container, markup);
  }

最后

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

var Test = React.createClass({
  getInitialState: function() {
    return {name: ‘world‘};
  },
  render: function() {
    return (
        <p>Hello</p>
        <p>
            Hello {this.state.name}!
        </p>
    );
  }
});

React.render(
  <Test />,
  document.getElementById(‘content‘)
);

 

时间: 2024-11-08 23:22:29

React直出实现与原理的相关文章

深入理解React(二) —— 数据流和事件原理

版权声明:本文由左明原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/158 来源:腾云阁 https://www.qcloud.com/community 这个,叫做竹笕,是中日传统禅文化中常见的庭院装饰品,它的构造可简单可复杂,但原理很简单,比如这个竹笕,水从竹笕顶部入口流入内部,并按照固定的顺序从上向下依次流入各个小竹筒,然后驱动水轮转动.对于强迫症患者来说,观赏竹笕的绝对是一种很享受的过程的最爱,你会发现这些小玩意

降低首屏时间,“直出”是个什么概念?

早几年前端还处于刀耕火种.JQuery独树一帜的时代,前后端代码的耦合度很高,一个web页面文件的代码可能是这样的: 这意味着后端的工程师往往得负责一部分修改HTML.编写脚本的工作,而前端开发者也得了解页面上存在的服务端代码含义. 有时候某处页面逻辑的变动,鉴于代码的混搭,可能都不确定应该请后端还是前端来改动(即使他们都能处理). 前端框架热潮 有句俗话说的好——“人啊,要是擅于开口‘关我屁事’和‘关你屁事’这俩句,可以节省人生中的大部分时间”. 随着这两年被 angular 牵头带起的各种前

React Native 从入门到原理一

React Native 从入门到原理一 React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通俗的语言解释了相关的名词,重点介绍 React Native 出现的背景和试图解决的问题.适合新手对 React Native 形成初步了解. 下半部分则通过源码(0.27 版本)分析 React Native 的工作原理,适合深入学习理解 React Na

腾讯新闻抢金达人活动node同构直出渲染方案的总结

我们的业务在展开的过程中,前端渲染的模式主要经历了三个阶段:服务端渲染.前端渲染和目前的同构直出渲染方案. 服务端渲染的主要特点是前后端没有分离,前端写完页面样式和结构后,再将页面交给后端套数据,最后再一起联调.同时前端的发布也依赖于后端的同学:但是优点也很明显:页面渲染速度快,同时 SEO 效果好. 为了解决前后端没有分离的问题,后来就出现了前端渲染的这种模式,路由选择和页面渲染,全部放在前端进行.前后端通过接口进行交互,各端可以更加专注自己的业务,发布时也是独立发布.但缺点是页面渲染慢,严重

[译]利用React Router4实现的服务端直出渲染(SSR)

我们已经熟悉React 服务端渲染(SSR)的基本步骤,现在让我们更进一步利用 React RouterV4 实现客户端和服务端的同构.毕竟大多数的应用都需要用到web前端路由器,所以要让SSR能够正常的运行,了解路由器的设置是十分有必要的 基本步骤 路由器配置 前言已经简单的介绍了React SSR,首先我们需要添加ReactRouter4到我们的项目中 $ yarn add react-router-dom # or, using npm $ npm install react-router

React Native 从入门到原理

抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲:"从 Server 获取配置 –> 解析 –> 执行native代码". 很多时候,我们自觉或者不自觉的利用 JSON 文件实现动态配置的效果,它的核心流程是: 通过 HTTP 请求获取 JSON 格式的配置文件. 配置文件中标记了每一个元素的属性,比如位置,颜色,图片 UR

react+redux渲染性能优化原理

大家都知道,react的一个痛点就是非父子关系的组件之间的通信,其官方文档对此也并不避讳: For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillU

Webpack打包进阶

说在前面 由于使用了React直出,页面各项性能指标使人悦目.本篇将深入探讨目前PC部落所采用webpack打包优化策略,以及探讨PC部落并未使用的 webpack Code Splitting 代码分包.异步模块加载特性.看看它们又是如何对PC部落的性能起到进一步的催化作用. 为什么要使用webpack 如果你曾经使用过 Broserify, RequireJS 或类似的打包工具,并注重:代码分包.异步加载.静态资源打包(图片/CSS).那么 webpack 就是帮你构建项目的利器!简单一句话

react 点击空白处隐藏弹出层

点击空白处隐藏弹出层的原理是:在 document 上绑定事件来隐藏弹出层,这样点击任何元素的时候都会冒泡到 document 上,都会执行隐藏弹出层的功能.然后我们在不需要隐藏弹出层的元素上阻止冒泡即可. class Select extends Component { constructor(props) { super(props); this.state = { selected: props.list[0], showList: false }; this.showList = thi