您的位置:首页 > Web前端 > React

React直出实现与原理

2015-05-30 22:39 561 查看

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


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

//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')
);


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航