一步一步帶你實現virtual dom(一)
一步一步帶你實現virtual dom(二)--props和事件
要寫你自己的虛擬dom,有兩件事你必須知道。你甚至都不用翻看react的源**,或者其他的基於虛擬dom的**。他們**量都太大,太複雜。然而要實現乙個虛擬dom的主要部分只需要大約50行的**。50行**!!
下面就是那兩個你要知道的事情:
下面我們就來看看這兩條是如何實現的。
首先我們需要在記憶體裡儲存我們的dom樹。只要使用js就可以達到這個目的。假設我們有這樣的乙個樹:
但是如果用這個方法來對應到巨大的dom樹的話那將是非常困難的。所以我們來寫乙個helper方法,這樣結構上也就容易理解一些:
function h(type, props, ...children) ;
}
現在我們可以這樣生成乙個虛擬dom樹:
h('ul', ,
h('li', {}, 'item 1'),
h('li', {}, 'item 2'),
)
這樣看起來就清晰了很多。但是我們還可以做的更好。你應該聽說過jsx對吧。是的,我們也要用那種方式。但是,這個應該如何下手呢?
如果你讀過babel的jsx文件的話,你就會知道這些都是babel的功勞。babel會把下面的**轉碼:
轉碼為:
react.createelement('ul', ),
react.createelement('li', {}, 'item 1'),
react.createelement('li', {}, 'item 2')
);
你注意到多相似了嗎?如果把react.createelement(...)
體換成我們自己的h
方法的話,那我們也已使用類似於jsx的語法。我們只需要在我們的檔案最頂端加這麼一句話:
/** @jsx h */
這一行/** @jsx h */
就是在告訴babel「大兄弟,按照jsx的方式轉碼,但是不要用react.createelement
, 使用h
。你可以使用任意的東西來代替h。
那麼把上面我們說的總結一下,我們會這樣寫我們的虛擬dom:
/** @jsx h */
const a = ;
然後babel就會轉碼成這樣:
const a = ,
h('li', {}, 'item 1'),
h('li', {}, 'item 2'),
)};
當方法h
執行的時候,它就會返回js的物件--我們的虛擬dom樹。
const a = (
, children: [
, children: [『item 1』] },
, children: [『item 2』] }
] });
在jsfiddle裡執行一下試試。
現在我們的dom樹用純的js物件來代表了。很酷了。但是我們需要根據這些建立實際的dom。因為我們不能只是把虛擬節點轉換後直接載入dom裡。
首先我們來定義一些假設和一些術語:
我們來寫乙個方法:createelement()
,這個方法可以接收乙個虛擬節點之後返回乙個真實的dom節點。先不考慮props
和children
,這個之後會有介紹。
function createelement(node)
return document.createelement(node.type);
}
因為我們不僅需要處理文字節點(js的字串),還要處理各種元素(element)。這些元素都是想js的物件一樣的:
, children: [...]}
我們可以用這個結構來處理文字節點和各種element了。
function createelement(node)
const $el = document.createelement(node.type);
node.children
.map(createelement)
return $el;
}
看起來還不錯,我們先不考慮節點的props
。要理解虛擬節點的概念並不需要這些東西卻會增加很多的複雜度。
我們可以把虛擬節點轉化為真實的dom了。現在該考慮比較我們的虛擬樹了。基本上我們需要寫一點演算法了。虛擬樹的比較需要用到這個演算法,比較之後只做必要的修改。
如何比較樹的不同?
//new
//old
//new
//old
//new
hi there!
hello
//old
hi there!
click it //發生了修改,變成了new裡的節點
//new
//old
加醒的兩個節點可以看到都是
,是相等的。但是它的子節點裡面卻有不同的節點。
我們來寫乙個方法updateelement
,它接收三個引數:$parent
、newnode
和oldnode
。$parent
是真的dom元素。它是我們虛擬節點的父節點。現在我們來看看如何處理上面提到的全部問題。
這個問題很簡單:
function updateelement($parent, newnode, oldnode)
}
如果當前沒有新的虛擬節點,我們就應該把它從真的dom裡刪除掉。但是,如何做到呢?我們知道父節點(作為引數傳入了方法),那麼我們就可以呼叫$parent.removechild
方法,並傳入真dom的引用。但是我們無法得到它,如果我們知道的節點在父節點的位置,就可以用$parent.childnodes[index]
來獲取它的引用。index
就是節點的位置。
假設index
也作為引數傳入了我們的方法,我們的方法就可以這麼寫:
function updateelement($parent, newnode, oldnode, index = 0) else if(!newnode)
}
首先寫乙個方法來比較兩個節點(新的和舊的)來區分節點是否發生了改變。要記住,節點可以是文字節點,也可以是元素(element):
function changed(node1, node2)
現在有了當前節點的index
了,index就是當前節點在父節點的位置。這樣可以很容易用新建立的節點來代替當前節點了。
function updateelement($parent, newnode, oldnode, index = 0) else if(!newnode) else if(chianged(newnode, oldnode))
}
最後,需要遍歷新舊節點的子節點,並比較他們。可以在每個節點上都使用updateelement
方法。是的,遞迴。
但是在開始**之前需要考慮一些問題:
function updateelement($parent, newnode, oldnode, index = 0) else if(!newnode) else if(changed(newnode, oldnode)) else if(newnode.type)
}}
在jsfiddle裡看看**把!
祝賀你!我們搞定了。我們寫出了虛擬節點的實現。從上面的例子中你已經可以理解虛擬節點的概念了,也大體可以知道react是如何運作的了。
當時還有很多需要講述的內容,其中包括:
一步一步實現FormsAuthentic驗證登入
本文不講原理,只講用法,原理性的東西網上特別多,不過還是會對一些要用到的東西進行解釋,不深入講原理。本文中用的是vs2012 net mvc 4.0。原理看這篇文章 看完這個文章絕對受益匪淺。說下登入的整個流程 使用者輸入賬號密碼 點選提交 資料提交到後台控制器 去資料庫取得使用者資料 如果登入成功...
一步一步 Sql Azure
一步一步 sql azure 1.使用 windowsazure 平台賬號登陸 2.新建sqlazure server 3.新建資料庫 4.為sql azure server 新增防火牆規則,只有將本機新增到規則裡才能從本機連線到該sqlazure server 5.連線到sql azure ser...
一步一步學cscope
告之 1,我不喜歡寫部落格 因為感覺太浪費時間 2,部落格能記住自己某階段學過的東西,而這些東西可能會很快的忘卻 所以我以後要學著在部落格上浪費時間 前言 本文件記錄了我今天 2007 11 9 下午學習cscope的一點收穫,特收錄部落格以作分享。在學習cscope過程中查閱了大量的文件,但發現適...