react map break.js中能使用break么,用什么退出当前程序

[React.js]自己用React和Material UI写的博客程序 - CNode技术社区
这家伙很懒,什么个性签名都没有留下。
个人博客的地址是 源码地址是:
… 最近帮人做一个项目源码过段时间我维护一下,起码加一下评论功能啥的。欢迎大家提出建议告诉我怎么写更好啦。也顺便赚赚博客访问量。哈哈
不错 不错 哈哈
我博客也是React + Material-UI
看到要填密码的地方我就。。。
查了下看了看。。。囧,都放在github上设置什么密码。。。
惭愧,虽然工作中接触到了react,继而material-ui这个优美的ui库,但是自己却没有坚持深入下去,做出属于自己项目的这个意识
CNode 社区为国内最专业的 Node.js 开源技术社区,致力于 Node.js 的技术研究。
服务器赞助商为
,存储赞助商为
,由提供应用性能服务。
新手搭建 Node.js 服务器,推荐使用无需备案的&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!请教以下这段代码用jsx 怎么写,主要是处理不好for 循环部分.
// 界面的拆分--------------------------------------------------------------------------------------------------
getRowDraw : function() {
var str = "";
str = "&div
class=\"horizontal-line-container\"&&div
class=\"horizontal-line-container2\"&";
for (var i = 0; i & 24; i++) {
str += "&div style='height:"+(this.hourHeight-1)+"' class=\"horizontal-line\"&&div style='height:"+(this.hourHeight/2-1)+"' &&/div&&/div&";
str += "&/div&&/div&";
的东西,还没看懂。
不要把react当成模板
用es5的 forEach方法
var arr = [...];
arr.forEach(function(value, index, array) {
var doms = [];
for (var i = 0; i & 24; i++) {
doms.push(&a&{i}&/a&);
我一般结合 underscore.js 的 map 方法:
return _.map(persons, function (p) {
return &div&{p.name} | {p.age}&/div&;
你可以匿名填写信息 - 所填内容将在登录之后发布
[禁用预览]
社区 wiki(无积分奖励,很多其他用户也能编辑 wiki 文章)
已被浏览: 223 次
最后更新时间: Jun 20 '15
相似的问题
Please note: Lefern requires javascript to work properly, please enable javascript in your browser,中国领先的IT技术网站
51CTO旗下网站
如何利用React.js开发出强大Web应用
利用React.js,我们能够非常轻松地开发出强大且稳定的Web应用程序。这主要是因为大家需要使用的全部功能都能够由该框架自行提供,而且其在初始设计之时就充分考虑到创建高复杂性应用程序的种种需要。
作者:核子可乐译来源:51CTO| 10:14
在开发强大的Web应用程序领域,React.js无疑可算当之无愧的赢家。首先,最令人振奋的就是它为开发人员带来了多年以来在Web开发框架当中始终无法找到的各类要素。很多Web开发人员之所以对其青眼有加,正是因为它能够帮助我们开发出不同于以往且极具交互特性的Web应用。它几乎无所不包,因此我们也应当对其加以关注,但利用它在未来的开发工作当中打造出更为出色的成果。
关于React.js,首先需要留意的就是它在默认情况下允许大家保证动态站点能够始终拥有强大的稳定性,这意味着我们能够充分运用各类灵活的交互设计效果而不必担心给站点运行带来影响。这绝对是一项伟大的壮举,事实上我们甚至很难在利用React.js进行应用程序开发时造成任何故障。
在利用React.js开发强大Web应用时,另外还有几项提示必须加以强调&&这些提示绝对值得一试而且极具实用性,特别是对于那些刚刚上手这套框架的朋友来说。它们将帮助大家更好地处理未来开发工作中可能面对的各种问题。再有,大家还能够以远超想象的速度实现应用开发。
如果大家打算利用React.js创建一款应用程序,那么各位无需完成整套工具设置即可在自己的浏览器当中轻松便捷地搞定原型设计。是的,其实际操作过程与我们的表述同样简单。举例来说,如果大家决定利用可选JSX语法自行编写组件,其编写方式以及代码内容看起来几乎与HTML如出一辙。
作为开发第一步,我们首先需要制作一份简单的文档,其中同时包含React.js与JSX转换工具。以下列代码作为范例:
&script&src=&http://fb.me/react-0.13.0.js&&&/script&&&script&src=&http://fb.me/JSXTransformer-0.13.0.js&&&/script&&&/head&&&body&&&script&type=&text/jsx&&&&&/script&&&/body&&&/html&&
React.js当中的应用程序必须通过已经在层级结构当中布置完成的组件加以构成。如果大家希望在开发工作当中轻松使用应用程序的每个组成部分,那么必须首先拿出时间弄清楚其在层级结构中的具体作用并以此为基础勾勒应用原型。这意味着,每个组件都负责解决一项特定任务。而在某些复杂组件当中,我们还需要将其拆分成数个简单组件,从而确保一次只解决一个问题。这也是我们充分发挥React.js强大能力的必要前提。
属性与状态
React.js当中的数据主要分为两种类型:
&属性:这类数据会在不同组件之间往来传递
&状态:这类数据会始终被保存在某组件当中
组件的属性(即往来于不同组件间的信息)不可修改与变更,但组件的状态却能够随时加以调整(即组件内部的信息)。这代表着React.js中的一切都具备与之对应的真实源。
因此,当我们利用React.js创建一款应用程序时,必须要在Web应用开发中做出一项决策&&各组件拥有怎样的数据,这些数据的主来源又是什么。一旦解决了这个问题,大家就能够轻松完成应用创建的其它工作。
在这种情况下,我们只需要考量三种数据类型:
用户输入数据
具体来参考以下示意图:
其中网络数据将由网络及线路组件所获取。其通常代表着大量数据,而且为了不影响应用的运行速度,大家需要在外部对其加以处理,而后再把结果交付至我们创建的应用。
组件通信机制
在这里,数据被设计为自上而下贯穿整个组件结构,但大家有时候也需要以自下而上的方式逆向交付数据以实现应用程序交互性。在这种情况下,我们就需要利用特定的技术手段实现这种&数据逆流&。下面来看几种实现此类目标的方式:
&大家可以选择使用回调属性的方式,这是最理想也最简单的解决方案,因为此时组件只需要同其直接上游对象进行数据共享。React.js能够自动对每个实例者组件方法绑定,因此维护工作不会占用我们大量精力。下面来看具体示例:
return&;&}&});&&var&Child&=&React.createClass({&render:&function()&{&return&Click&&}&});&
&如果大家希望实现的是其它抵达通知机制,那么可以利用单一系统实现发布/订阅。这种方式非常灵活而且同样易于维护。只需使用PubSubJS这类库,大家就能够随意对某一组件的生命周期方法进行绑定与解绑。
相关代码示例如下:
var&Parent&=&React.createClass({&handleMyEvent:&function(e)&{...},&componentWillMount:&function()&{&window.addEventListener(&my-event&,&this.handleMyEvent,&false);&},&componentWillUnmount:&function()&{&window.removeEventListener(&my-event&,&this.handleMyEvent,&false);&},&render:&function()&{...}&});&&var&Grandchild&=&React.createClass({&handleClick:&function(e)&{&var&customEvent&=&new&CustomEvent(&my-event&,&&{&detail:&{&...&},&bubbles:&true&});&React.findDOMNode(this.refs.link).dispatchEvent(customEvent);&},&render:&function()&{&return&Click&&}&});&
组件生命周期
组件永远拥有着与其API紧密关联的生命周期。在这种情况下,其生命周期包括启动、更新与卸载三种状态。而这些功能已经被内置在组件的定义当中。举例来说:
componentWillMount与componentWillUnmount 方法都被用于添加或者移除事件侦听机制。当然还有其它多种方法能够帮助我们实现对组件状态及属性的控制。
一旦我们建立起一套浏览器内运行环境,接下来就可以将UI方案拆分为多个简单组件。接下来的任务是弄清应用程序运行需要具备哪些数据,这些数据将处于何种位置且如何与应用进行共享。当这些问题得到解决,大家将能够获得可进行试用体验的已创建应用。
利用React.js,我们能够非常轻松地开发出强大且稳定的Web应用程序。这主要是因为大家需要使用的全部功能都能够由该框架自行提供,而且其在初始设计之时就充分考虑到创建高复杂性应用程序的种种需要。
原文标题:Develop Robust Web Apps with React.js
【独家译稿 ,合作站点转载请注明来源。】
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点热点热点热点热点
24H热文一周话题本月最赞
讲师:5人学习过
讲师:36人学习过
讲师:5人学习过
精选博文论坛热帖下载排行
本书重点介绍了主流ARM应用系统的开发与实践。全书基于目前较为通用、流行的ARM处理器,介绍了其原理、硬件结构、硬件电路设计与开发和软件...
订阅51CTO邮刊原文地址:http://purplebamboo.github.io//reactjs_source_analyze_part_two/
reactjs是目前比较火的前端框架,但是目前并没有很好的解释原理的项目。reactjs源码比较复杂不适合初学者去学习。所以本文通过实现一套简易版的reactjs,使得理解原理更加容易。包括:
本文假定你已经对reactjs有了一定的了解,如果没有至少看下ruanyifeng老师的。jsx不在本文的讨论范围,所有的例子原理都是使用原生的javascript。篇幅限制,服务器端的reactjs也不在本文讨论范围内。为了演示方便,本文以jQuery作为基本工具库。为了更清晰的演示原理,本文会忽略很多细节的东西,千万不要用于生产环境。
所有实例源码都托管在github。里面有分步骤的例子,可以一边看一边运行例子。
紧接上文,虚拟dom差异化算法(diff algorithm)是reactjs最核心的东西,按照官方的说法。他非常快,非常高效。目前已经有一些分析此算法的文章,但是仅仅停留在表面。大部分小白看完并不能了解(博主就是 = =)。所以我们下面自己动手实现一遍,等你完全实现了,再去看那些文字图片流的介绍文章,就会发现容易理解多了。
实现更新机制
下面我们探讨下更新的机制。
一般在reactjs中我们需要更新时都是调用的setState。看下面的例子:
var HelloMessage = React.createClass({
getInitialState: function() {
return {type: 'say:'};
changeType:function(){
this.setState({type:'shout:'})
render: function() {
return React.createElement(&div&, {onclick:this.changeType},this.state.type, &Hello &, this.props.name);
React.render(React.createElement(HelloMessage, {name: &John&}), document.getElementById(&container&));
点击文字,调用setState就会更新,所以我们扩展下ReactClass,看下setState的实现:
var ReactClass = function(){
ReactClass.prototype.render = function(){}
ReactClass.prototype.setState = function(newState) {
this._reactInternalInstance.receiveComponent(null, newState);
可以看到setState主要调用了对应的component的receiveComponent来实现更新。所有的挂载,更新都应该交给对应的component来管理。
就像所有的component都实现了mountComponent来处理第一次渲染,所有的componet类都应该实现receiveComponent用来处理自己的更新。
自定义元素的receiveComponent
所以我们照葫芦画瓢来给自定义元素的对应component类(ReactCompositeComponent)实现一个receiveComponent方法:
ReactCompositeComponent.prototype.receiveComponent = function(nextElement, newState) {
this._currentElement = nextElement || this._currentElement
var inst = this._
var nextState = $.extend(inst.state, newState);
var nextProps = this._currentElement.
inst.state = nextS
if (inst.shouldComponentUpdate && (inst.shouldComponentUpdate(nextProps, nextState) === false)) return;
if (ponentWillUpdate) ponentWillUpdate(nextProps, nextState);
var prevComponentInstance = this._renderedC
var prevRenderedElement = prevComponentInstance._currentE
var nextRenderedElement = this._instance.render();
if (_shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
prevComponentInstance.receiveComponent(nextRenderedElement);
ponentDidUpdate && ponentDidUpdate();
var thisID = this._rootNodeID;
this._renderedComponent = this._instantiateReactComponent(nextRenderedElement);
var nextMarkup = _renderedComponent.mountComponent(thisID);
$('[data-reactid=&' + this._rootNodeID + '&]').replaceWith(nextMarkup);
var _shouldUpdateReactComponent = function(prevElement, nextElement){
if (prevElement != null && nextElement != null) {
var prevType = typeof prevE
var nextType = typeof nextE
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.
return false;
不要被这么多代码吓到,其实流程很简单。
它主要做了什么事呢?首先会合并改动,生成最新的state,props然后拿以前的render返回的element跟现在最新调用render生成的element进行对比(_shouldUpdateReactComponent),看看需不需要更新,如果要更新就继续调用对应的component类对应的receiveComponent就好啦,其实就是直接当甩手掌柜,事情直接丢给手下去办了。当然还有种情况是,两次生成的element差别太大,就不是一个类型的,那好办直接重新生成一份新的代码重新渲染一次就o了。
本质上还是递归调用receiveComponent的过程。
这里注意两个函数:
inst.shouldComponentUpdate是实例方法,当我们不希望某次setState后更新,我们就可以重写这个方法,返回false就好了。_shouldUpdateReactComponent是一个全局方法,这个是一种reactjs的优化机制。用来决定是直接全部替换,还是使用很细微的改动。当两次render出来的子节点key不同,直接全部重新渲染一遍,替换就好了。否则,我们就得来个递归的更新,保证最小化的更新机制,这样可以不会有太大的闪烁。
另外可以看到这里还处理了一套更新的生命周期调用机制。
文本节点的receiveComponent
我们再看看文本节点的,比较简单:
ReactDOMTextComponent.prototype.receiveComponent = function(nextText) {
var nextStringText = '' + nextT
if (nextStringText !== this._currentElement) {
this._currentElement = nextStringT
$('[data-reactid=&' + this._rootNodeID + '&]').html(this._currentElement);
没什么好说的,如果不同的话,直接找到对应的节点,更新就好了。
基本元素element的receiveComponent
最后我们开始看比较复杂的浏览器基本元素的更新机制。
比如我们看看下面的html:
id=&test& name=&hello&&
想一下我们怎么以最小代价去更新这段html呢。不难发现其实主要包括两个部分:
属性的更新,包括对特殊属性比如事件的处理子节点的更新,这个比较复杂,为了得到最好的效率,我们需要处理下面这些问题:
拿新的子节点树跟以前老的子节点树对比,找出他们之间的差别。我们称之为diff所有差别找出后,再一次性的去更新。我们称之为patch
所以更新代码结构如下:
ReactDOMComponent.prototype.receiveComponent = function(nextElement) {
var lastProps = this._currentElement.
var nextProps = nextElement.
this._currentElement = nextE
this._updateDOMProperties(lastProps, nextProps);
this._updateDOMChildren(nextElement.props.children);
整体上也不复杂,先是处理当前节点属性的变动,后面再去处理子节点的变动
我们一步步来,先看看,更新属性怎么变更:
ReactDOMComponent.prototype._updateDOMProperties = function(lastProps, nextProps) {
for (propKey in lastProps) {
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey)) {
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
$(document).undelegate('[data-reactid=&' + this._rootNodeID + '&]', eventType, lastProps[propKey]);
$('[data-reactid=&' + this._rootNodeID + '&]').removeAttr(propKey)
for (propKey in nextProps) {
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
lastProps[propKey] && $(document).undelegate('[data-reactid=&' + this._rootNodeID + '&]', eventType, lastProps[propKey]);
$(document).delegate('[data-reactid=&' + this._rootNodeID + '&]', eventType + '.' + this._rootNodeID, nextProps[propKey]);
if (propKey == 'children') continue;
$('[data-reactid=&' + this._rootNodeID + '&]').prop(propKey, nextProps[propKey])
属性的变更并不是特别复杂,主要就是找到以前老的不用的属性直接去掉,新的属性赋值,并且注意其中特殊的事件属性做出特殊处理就行了。
下面我们看子节点的更新,也是最复杂的部分。
ReactDOMComponent.prototype.receiveComponent = function(nextElement){
var lastProps = this._currentElement.
var nextProps = nextElement.
this._currentElement = nextE
this._updateDOMProperties(lastProps,nextProps);
this._updateDOMChildren(nextProps.children);
var updateDepth = 0;
var diffQueue = [];
ReactDOMComponent.prototype._updateDOMChildren = function(nextChildrenElements){
updateDepth++
this._diff(diffQueue,nextChildrenElements);
updateDepth--
if(updateDepth == 0){
this._patch(diffQueue);
diffQueue = [];
就像我们之前说的一样,更新子节点包含两个部分,一个是递归的分析差异,把差异添加到队列中。然后在合适的时机调用_patch把差异应用到dom上。
那么什么是合适的时机,updateDepth又是干嘛的?
这里需要注意的是,_diff内部也会递归调用子节点的receiveComponent于是当某个子节点也是浏览器普通节点,就也会走_updateDOMChildren这一步。所以这里使用了updateDepth来记录递归的过程,只有等递归回来updateDepth为0时,代表整个差异已经分析完毕,可以开始使用patch来处理差异队列了。
所以我们关键是实现_diff与_patch两个方法。
我们先看_diff的实现:
var UPATE_TYPES = {
MOVE_EXISTING: 1,
REMOVE_NODE: 2,
INSERT_MARKUP: 3
function flattenChildren(componentChildren) {
var childrenMap = {};
for (var i = 0; i & componentChildren. i++) {
child = componentChildren[i];
name = child && child._currentelement && child._currentelement.key ? child._currentelement.key : i.toString(36);
childrenMap[name] =
return childrenM
function generateComponentChildren(prevChildren, nextChildrenElements) {
var nextChildren = {};
nextChildrenElements = nextChildrenElements || [];
$.each(nextChildrenElements, function(index, element) {
var name = element.key ? element.key :
var prevChild = prevChildren && prevChildren[name];
var prevElement = prevChild && prevChild._currentE
var nextElement =
if (_shouldUpdateReactComponent(prevElement, nextElement)) {
prevChild.receiveComponent(nextElement);
nextChildren[name] = prevC
var nextChildInstance = instantiateReactComponent(nextElement, null);
nextChildren[name] = nextChildI
return nextC
ReactDOMComponent.prototype._diff = function(diffQueue, nextChildrenElements) {
var self = this;
var prevChildren = flattenChildren(self._renderedChildren);
var nextChildren = generateComponentChildren(prevChildren, nextChildrenElements);
self._renderedChildren = []
$.each(nextChildren, function(key, instance) {
self._renderedChildren.push(instance);
var nextIndex = 0;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.MOVE_EXISTING,
fromIndex: prevChild._mountIndex,
toIndex: nextIndex
if (prevChild) {
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex: null
if (prevChild._rootNodeID) {
$(document).undelegate('.' + prevChild._rootNodeID);
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.INSERT_MARKUP,
fromIndex: null,
toIndex: nextIndex,
markup: nextChild.mountComponent()
nextChild._mountIndex = nextI
nextIndex++;
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) {
diffQueue.push({
parentId: self._rootNodeID,
parentNode: $('[data-reactid=' + self._rootNodeID + ']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex: null
if (prevChildren[name]._rootNodeID) {
$(document).undelegate('.' + prevChildren[name]._rootNodeID);
我们分析下上面的代码,咋一看好多,好复杂,不急我们从入口开始看。
首先我们拿到之前的component的集合,如果是第一次更新的话,这个值是我们在渲染时赋值的。然后我们调用generateComponentChildren生成最新的component集合。我们知道component是用来放element的,一个萝卜一个坑。
注意flattenChildren我们这里把数组集合转成了对象map,以element的key作为标识,当然对于text文本或者没有传入key的element,直接用index作为标识。通过这些标识,我们可以从类型的角度来判断两个component是否是一样的。
generateComponentChildren会尽量的复用以前的component,也就是那些坑,当发现可以复用component(也就是key一致)时,就还用以前的,只需要调用他对应的更新方法receiveComponent就行了,这样就会递归的去获取子节点的差异对象然后放到队列了。如果发现不能复用那就是新的节点,我们就需要instantiateReactComponent重新生成一个新的component。
这里的flattenChildren需要给予很大的关注,比如对于一个表格列表,我们在最前面插入了一条数据,想一下如果我们创建element时没有传入key,所有的key都是null,这样reactjs在generateComponentChildren时就会默认通过顺序(index)来一一对应改变前跟改变后的子节点,这样变更前与变更后的对应节点判断(_shouldUpdateReactComponent)其实是不合适的。也就是说对于这种列表的情况,我们最好给予唯一的标识key,这样reactjs找对应关系时会更方便一点。
当我们生成好新的component集合以后,我们需要做出对比。组装差异对象。
对比老的集合和新的集合。我们需要找出涵盖四种情况,包括三种类型(UPATE_TYPES)的变动:
MOVE_EXISTING
新的component类型在老的集合里也有,并且element是可以更新的类型,在generateComponentChildren我们已经调用了receiveComponent,这种情况下prevChild=nextChild,那我们就需要做出移动的操作,可以复用以前的dom节点。
INSERT_MARKUP
新的component类型不在老的集合里,那么就是全新的节点,我们需要插入新的节点
REMOVE_NODE
老的component类型,在新的集合里也有,但是对应的element不同了不能直接复用直接更新,那我们也得删除。
REMOVE_NODE
老的component不在新的集合里的,我们需要删除
所以我们找出了这三种类型的差异,组装成具体的差异对象,然后加到了差异队列里面。
比如我们看下面这个例子,假设下面这些是某个父元素的子元素集合,上面到下面代表了变动流程:
数字我们可以理解为给element的key。
正方形代表element。圆形代表了component。当然也是实际上的dom节点的位置。
从上到下,我们的4 2 1里 2 ,1可以复用之前的component,让他们通知自己的子节点更新后,再告诉2和1,他们在新的集合里需要移动的位置(在我们这里就是组装差异对象加到队列)。3需要删除,4需要新增。
好了,整个的diff就完成了,这个时候当递归完成,我们就需要开始做patch的动作了,把这些差异对象实打实的反映到具体的dom节点上。
我们看下_patch的实现:
function insertChildAt(parentNode, childNode, index) {
var beforeChild = parentNode.children().get(index);
beforeChild ? childNode.insertBefore(beforeChild) : childNode.appendTo(parentNode);
ReactDOMComponent.prototype._patch = function(updates) {
var initialChildren = {};
var deleteChildren = [];
for (var i = 0; i & updates. i++) {
update = updates[i];
if (update.type === UPATE_TYPES.MOVE_EXISTING || update.type === UPATE_TYPES.REMOVE_NODE) {
var updatedIndex = update.fromI
var updatedChild = $(update.parentNode.children().get(updatedIndex));
var parentID = update.parentID;
initialChildren[parentID] = initialChildren[parentID] || [];
initialChildren[parentID][updatedIndex] = updatedC
deleteChildren.push(updatedChild)
$.each(deleteChildren, function(index, child) {
$(child).remove();
for (var k = 0; k & updates. k++) {
update = updates[k];
switch (update.type) {
case UPATE_TYPES.INSERT_MARKUP:
insertChildAt(update.parentNode, $(update.markup), update.toIndex);
case UPATE_TYPES.MOVE_EXISTING:
insertChildAt(update.parentNode, initialChildren[update.parentID][update.fromIndex], update.toIndex);
case UPATE_TYPES.REMOVE_NODE:
_patch主要就是挨个遍历差异队列,遍历两次,第一次删除掉所有需要变动的节点,然后第二次插入新的节点还有修改的节点。这里为什么可以直接挨个的插入呢?原因就是我们在diff阶段添加差异节点到差异队列时,本身就是有序的,也就是说对于新增节点(包括move和insert的)在队列里的顺序就是最终dom的顺序,所以我们才可以挨个的直接根据index去塞入节点。
但是其实你会发现这里有个问题,就是所有的节点都会被删除,包括复用以前的component类型为UPATE_TYPES.MOVE_EXISTING的,所以闪烁会很严重。其实我们再看看上面的例子,其实2是不需要记录到差异队列的。这样后面patch也是ok的。想想是为什么呢?
我们来改造下代码:
ReactDOMComponent.prototype._diff = function(diffQueue, nextChildrenElements){
var lastIndex = 0;
var nextIndex = 0;
for (name in nextChildren) {
if (!nextChildren.hasOwnProperty(name)) {
var prevChild = prevChildren && prevChildren[name];
var nextChild = nextChildren[name];
if (prevChild === nextChild) {
prevChild._mountIndex & lastIndex && diffQueue.push({
parentId:this._rootNodeID,
parentNode:$('[data-reactid='+this._rootNodeID+']'),
type: UPATE_TYPES.REMOVE_NODE,
fromIndex: prevChild._mountIndex,
toIndex:null
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
if (prevChild) {
。。。。。
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
nextChild._mountIndex = nextI
nextIndex++;
可以看到我们多加了个lastIndex,这个代表最后一次访问的老集合节点的最大的位置。
而我们加了个判断,只有_mountIndex小于这个lastIndex的才会需要加入差异队列。有了这个判断上面的例子2就不需要move。而程序也可以好好的运行,实际上大部分都是2这种情况。
这是一种顺序优化,lastIndex一直在更新,代表了当前访问的最右的老的集合的元素。
我们假设上一个元素是A,添加后更新了lastIndex。
如果我们这时候来个新元素B,比lastIndex还大说明当前元素在老的集合里面就比上一个A靠后。所以这个元素就算不加入差异队列,也不会影响到其他人,不会影响到后面的path插入节点。因为我们从patch里面知道,新的集合都是按顺序从头开始插入元素的,只有当新元素比lastIndex小时才需要变更。其实只要仔细推敲下上面那个例子,就可以理解这种优化手段了。
这样整个的更新机制就完成了。我们再来简单回顾下reactjs的差异算法:
首先是所有的component都实现了receiveComponent来负责自己的更新,而浏览器默认元素的更新最为复杂,也就是经常说的 diff algorithm。
react有一个全局_shouldUpdateReactComponent用来根据element的key来判断是更新还是重新渲染,这是第一个差异判断。比如自定义元素里,就使用这个判断,通过这种标识判断,会变得特别高效。
每个类型的元素都要处理好自己的更新:
自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。
text节点的更新很简单,直接更新文案。
浏览器基本元素的更新,分为两块:
先是更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。然后是子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的_shouldUpdateReactComponent来判断,如果是可以直接更新的就会递归调用子节点的更新,这样也会递归查找差异对象,这里还会使用lastIndex这种做一种优化,使一些节点保留位置,之后根据差异对象操作dom元素(位置变动,删除,添加等)。
整个reactjs的差异算法就是这个样子。最核心的两个_shouldUpdateReactComponent以及diff,patch算法。
有了上面简易版的reaactjs,我们来实现一个简单的todolist吧。
var TodoList = React.createClass({
getInitialState: function() {
return {items: []};
add:function(){
var nextItems = this.state.items.concat([this.state.text]);
this.setState({items: nextItems, text: ''});
onChange: function(e) {
this.setState({text: e.target.value});
render: function() {
var createItem = function(itemText) {
return React.createElement(&div&, null, itemText);
var lists = this.state.items.map(createItem);
var input = React.createElement(&input&, {onkeyup: this.onChange.bind(this),value: this.state.text});
var button = React.createElement(&p&, {onclick: this.add.bind(this)}, 'Add#' + (this.state.items.length + 1))
var children = lists.concat([input,button])
return React.createElement(&div&, null,children);
React.render(React.createElement(TodoList), document.getElementById(&container&));
效果如下:
整个的流程是这样:
初次渲染时先使用ReactCompositeComponent渲染自定义元素TodoList,调用getInitialState拿到初始值,然后使用ReactDOMComponent渲染render返回的div基本元素节点。div基本元素再一层层的使用ReactDOMComponent去渲染各个子节点,包括input,还有p。在input框输入文字触发onchange事件,开始调用setState做出变更,直接变更render出来的节点,经过差异算法,一层层的往下。最后改变value值。点击按钮,触发add然后开始更新,经过差异算法,添加一个节点。同时更新按钮上面的文案。
基本上,整个流程都梳理清楚了
这只是个玩具,但实现了reactjs最核心的功能,虚拟节点,差异算法,单向数据更新都在这里了。还有很多reactjs优秀的东西没有实现,比如对象生成时内存的线程池管理,批量更新机制,事件的优化,服务端的渲染,immutable data等等。这些东西受限于篇幅就不具体展开了。
reactjs作为一种解决方案,虚拟节点的想法比较新奇,不过个人还是不能接受这种别扭的写法。使用reactjs,就要使用他那一整套的开发方式,而他核心的功能其实只是一个差异算法,而这种其实已经有相关的库实现了。
最后再吐槽下前端真是苦命,各种新技术,各种新知识脑细胞不够用了。也难怪前端永远都缺人。
相关资料:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:181725次
积分:2556
积分:2556
排名:第9406名
原创:58篇
转载:128篇
评论:26条
(1)(3)(3)(3)(8)(2)(6)(2)(6)(3)(1)(2)(4)(2)(5)(1)(1)(3)(1)(2)(1)(1)(1)(3)(3)(1)(4)(2)(5)(2)(4)(1)(3)(1)(3)(4)(3)(5)(1)(4)(1)(3)(2)(5)(9)(14)(8)(1)(10)(3)(9)(3)(5)(1)(1)}

我要回帖

更多关于 react 获取当前url 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信