前一篇文章咱們介紹了虛擬DOM的實現與原理,這篇文章咱們來說講React的直出。 比起MVVM,React比較容易實現直出,那麼React的直出是如何實現,有什麼值得咱們學習的呢?php
對於MVVM,HTML片斷即爲配置,而直出後的HTML沒法還原配置,因此問題不是MVVM可否直出,而是在於直出後的片斷可否還原原來的配置。下面是一個簡單的例子:html
<sapn>Hello {name}!</span>
上面這段HTML配置和數據在一塊兒,直出後會變成:前端
<span>Hello world!</span>
這時候當咱們失去了name的值改變的時候會致使頁面渲染這個細節。固然,若是爲了實現MVVM直出咱們可能有另外的方法來解決,例如直出結果變成這樣:node
<span>Hello <span q-text="name">world</span>!</span>
這時候咱們是能夠把丟失的信息找回來的,固然結構可能和咱們想象的有些差異。固然還有其餘問題,例如直出HTML不必定能反向還原數據,因爲篇幅問題,這裏不展開討論。react
如圖:git
具體例子能夠參考,https://github.com/DavidWells/isomorphic-react-example/,下面是其渲染路由的寫法:github
// 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實現直出,以及如何先後端代碼複用。算法
但還有下面幾個問題有待解決:後端
React.createClass({
render: function () { return ( <p> Hello {name}! </p> ); } })
經過一個簡單的例子,咱們能夠發現,實際上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);
時候作了下面一些事情:
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') );