React直出实现与原理
2015-05-30 22:39
561 查看
为什么MVVM不能做直出?
对于MVVM,HTML片段即为配置,而直出后的HTML无法还原配置,所以问题不是MVVM能否直出,而是在于直出后的片段能否还原原来的配置。下面是一个简单的例子:<sapn>Hello{name}!</span>
上面这段HTML配置和数据在一起,直出后会变成:
<span>Helloworld!</span>
这时候当我们失去了name的值改变的时候会导致页面渲染这个细节。当然,如果为了实现MVVM直出我们可能有另外的方法来解决,例如直出结果变成这样:
<span>Hello<spanq-text="name">world</span>!</span>
这时候我们是可以把丢失的信息找回来的,当然结构可能和我们想象的有些差别。当然还有其他问题,例如直出HTML不一定能反向还原数据,由于篇幅问题,这里不展开讨论。
React如何直出?
如图:
React的虚拟DOM的生成是可以在任何支持Javascript的环境生成的,所以可以在NodeJS或Iojs环境生成
虚拟DOM可以直接转成String
然后插入到html文件中输出给浏览器便可
具体例子可以参考,
//https://github.com/DavidWells/isomorphic-react-example/blob/master/app/routes/coreRoutes.js varReact=require('react/addons'); varReactApp=React.createFactory(require('../components/ReactApp').ReactApp); module.exports=function(app){ app.get('/',function(req,res){ //React.renderToStringtakesyourcomponent //andgeneratesthemarkup varreactHtml=React.renderToString(ReactApp({})); //Outputhtmlrenderedbyreact //console.log(myAppHtml); res.render('index.ejs',{reactOutput:reactHtml}); }); };
OK,我们现在知道如何利用React实现直出,以及如何前后端代码复用。
但还有下面几个问题有待解决:
如何渲染文字节点,每个虚拟DOM节点是需要对应实际的节点,但无法通过html文件生成相邻的TextNode,例如下面例子应当如何渲染:
React.createClass({ render:function(){ return( <p> Hello{name}! </p> ); } })
如何避免直出的页面被React重新渲染一遍?或者直出的页面和前端的数据是不对应的怎么办?
相邻的TextNode,想多了相邻的span而已
通过一个简单的例子,我们可以发现,实际上React根本没用TextNode,而是使用span来代替TextNode,这样就可以实现虚拟DOM和直出DOM的一一映射关系。
重复渲染?没门
刚刚的例子,如果我们通过React.renderToString拿到<Test/>可以发现是:
<pdata-reactid=".0"data-react-checksum="-793171045"><spandata-reactid=".0.0">Hello</span><spandata-reactid=".0.1">world</span><spandata-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算法实现:
varMOD=65521; //Thisisaclean-roomimplementationofadler32designedfordetecting //ifmarkupisnotwhatweexpectittobe.Itdoesnotneedtobe //cryptographicallystrong,onlyreasonablygoodatdetectingifmarkup //generatedontheserverisdifferentthanthatontheclient. functionadler32(data){ vara=1; varb=0; for(vari=0;i<data.length;i++){ a=(a+data.charCodeAt(i))%MOD; b=(b+a)%MOD; } returna|(b<<16); }
如果需要重新渲染,先通过下面简单的差异算法找到差异在哪里,打印出错误:
/** *Findstheindexofthefirstcharacter *that'snotcommonbetweenthetwogivenstrings. * *@return{number}theindexofthecharacterwherethestringsdiverge */ functionfirstDifferenceIndex(string1,string2){ varminLen=Math.min(string1.length,string2.length); for(vari=0;i<minLen;i++){ if(string1.charAt(i)!==string2.charAt(i)){ returni; } } returnstring1.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(...):Targetcontainerisnotvalid.' ):invariant(container&&( (container.nodeType===ELEMENT_NODE_TYPE||container.nodeType===DOC_NODE_TYPE) ))); if(shouldReuseMarkup){ varrootElement=getReactRootElementInContainer(container); if(ReactMarkupChecksum.canReuseMarkup(markup,rootElement)){ return; }else{ varchecksum=rootElement.getAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME ); rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); varrootMarkup=rootElement.outerHTML; rootElement.setAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum ); vardiffIndex=firstDifferenceIndex(markup,rootMarkup); vardifference='(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\'retryingtorenderacomponenttothedocumentusing'+ 'serverrenderingbutthechecksumwasinvalid.Thisusually'+ 'meansyourenderedadifferentcomponenttypeorpropson'+ 'theclientfromtheoneontheserver,oryourrender()'+ 'methodsareimpure.Reactcannothandlethiscasedueto'+ 'cross-browserquirksbyrenderingatthedocumentroot.You'+ 'shouldlookforenvironmentdependentcodeinyourcomponents'+ 'andensurethepropsarethesameclientandserverside:\n%s', difference ):invariant(container.nodeType!==DOC_NODE_TYPE)); if("production"!==process.env.NODE_ENV){ ("production"!==process.env.NODE_ENV?warning( false, 'Reactattemptedtoreusemarkupinacontainerbutthe'+ 'checksumwasinvalid.Thisgenerallymeansthatyouare'+ 'usingserverrenderingandthemarkupgeneratedonthe'+ 'serverwasnotwhattheclientwasexpecting.Reactinjected'+ 'newmarkuptocompensatewhichworksbutyouhavelostmany'+ 'ofthebenefitsofserverrendering.Instead,figureout'+ 'whythemarkupbeinggeneratedisdifferentontheclient'+ 'orserver:\n%s', difference ):null); } } } ("production"!==process.env.NODE_ENV?invariant( container.nodeType!==DOC_NODE_TYPE, 'You\'retryingtorenderacomponenttothedocumentbut'+ 'youdidn\'tuseserverrendering.Wecan\'tdothis'+ 'withoutusingserverrenderingduetocross-browserquirks.'+ 'SeeReact.renderToString()forserverrendering.' ):invariant(container.nodeType!==DOC_NODE_TYPE)); setInnerHTML(container,markup); }
最后
尝试一下下面的代码,想想React为啥认为这是错误的?varTest=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') );
相关文章推荐
- 两种高效的服务器设计模型:Reactor和Proactor模型
- react及flux架构范例Todomvc分析
- react-webpack 学习笔记~~第一步~环境
- ReactiveCocoa框架菜鸟入门(四)——信号(Signal)详解
- Netty那点事(四)Netty与Reactor模式
- Functional Reactive Programming<1>
- SaltStack的Reactor System
- ReactiveCocoa框架菜鸟入门(三)——信号(Signal)与订阅者(Subscriber)
- 关于ReactiveCocoa的RACObserve的一些研究
- 使用ReactiveCocoa初探MVVM
- ReactiveCocoa框架菜鸟入门(二)——MVVM架构与ReactiveCocoa框架
- React.js 学习
- reactor/proactor模型简介
- ReactJS入门指南
- 在界面布局中使用ShareActionProvider
- textView中输入后即时显示在tableView-即时通信聊天界面
- Spark-futureAction
- DPC分析 基于ReactOS0.33
- React 入门实例教程
- React-Native学习指南