市面上的主流框架,相信作爲一個前端搬磚人員,或多或少都會有所接觸到。如ReactJs、VueJs、AngularJs。那麼對於每個框架的使用來說其實是比較簡單的,還記得上大學時候,老師曾經說過:"技術就是窗戶紙,捅一捅就破了",也就是說,任何一門技術,只要深入去研究,那麼它也不再是很神祕的東西了。我個人在工作中用VueJs是比較多的,當然React也會,那今天就爲大家來實現一個Vuejs框架中的render函數
首先來看一段代碼:
<div id="div1">
<span>test</span>
<Tab></Tab>
<UserLogin></UserLogin>
</div>
最終在頁面上的呈現是怎樣的呢?
毫無疑問,只看到了test這一段文本內容。因爲html不認識Tab、UserLogin這兩個"異類"元素。那麼假如現在要實現的是,通過一個render方法:
render({
root:'#div1',
components:{
Tab,UserLogin
}
})
將Tab、UserLogin這兩個組件的內容渲染出來,該去怎樣實現呢?這裏涉及到的知識點如下:
- 類型判斷
- DOM操作
- Js的繼承、多態
- 組件化思想
首先通過Js的繼承及組件化思想來定義兩個類Tab、UserLogin,它們都有一個自身的render方法(從父類Component)繼承而來並進行了重寫。直接上代碼:
Component類:
class Component{
render(){
throw new Error('render is required');
}
}
Tab類:
class Tab extends Component{
render(){
let div1 = document.createElement('div');
div1.innerHTML = '我是Tab組件';
return div1;
}
}
UserLogin類:
class UserLogin extends Component{
render(){
let div2 = document.createElement('div');
div2.innerHTML = "我是登錄組件"
return div2
}
}
到這裏,相信大家學過ES6的,對這樣的代碼都是感覺很熟悉的,重點是render函數究竟該怎樣去實現。再來看一下render函數的基本骨架:
render({
root:'#div1',
components:{
Tab,UserLogin
}
})
render函數接收了一個參數(對象類型),裏面包括兩個屬性root(掛載的元素),以及components(帶渲染的組件類)。對於render的實現,先從root這個屬性入手。靈魂拷問,root屬性一定是某個元素的id嗎?對於一個函數的參數來說,使用者傳遞什麼類型都是可以的,但只要符合規定的參數纔能有效,說那麼多其實就是需要對render函數對象參數的root屬性進行校驗。代碼如下:
function render(opts){
let root = null;
if(typeof opts.root === "string"){
root = document.querySelector(opts.root);
if(!root){
throw new Error(`can't found ${opts.root}`)
}
}else if(opts.root instanceof HTMLElement){
root = opts.root
}else{
throw new Error(`root invalid`)
}
}
這裏的操作的目的就是爲了找到root這個(父)元素。
接下來就是針對參數對象的components屬性來進行處理了,也就是說需要找到所有自定義元素(Tab、UserLogin),又一次靈魂拷問,可以通過父元素找到其包含的所有子元素,但是該怎樣去區分哪些元素是自定義的呢?先來看一下通過父元素找到所有子元素的代碼:
let elements = root.getElementsByTagName("*");
打印看看elements是怎樣的數據結構:
可以看到,有一個是我們很熟悉的自有元素span,還有兩個未知的元素tab、userlogin,這時你可能回想,將elements轉換爲數組、然後遍歷進行判斷是否有自定義元素,哎,其實這思路還不錯。看下代碼:
Array.from(elements).forEach((ele) =>{
if(ele.tagName ==='tab'){
//找到了自定義元素tab
}
if(ele.tagName ==='userlogin'){
//找到了自定義元素userlogin
}
})
這樣行嗎?顯然是不行的。萬一將元素標籤結構改成這樣呢
<div id="div1">
<span>test</span>
<Tab></Tab>
<UserLogin></UserLogin>
<List></List>
</div>
那是不是要寫很多個if判斷,顯示不對。我們知道,一個html文檔的繼承結構大致如下:
對於上述的自有元素span,它繼承是HTMLElement,也就是說span元素的構造函數是HTMLSpanElement,那麼對於上述兩個未知元素,它們的構造函數是什麼呢?其實是HTMLUnknownElement。這下就可以通過構造函數類再結合參數對象中components屬性來找到未知元素了,代碼如下:
Array.from(elements).forEach((ele) =>{
if(ele.constructor === HTMLUnknownElement){
for(let compName in opts.components){
if(compName.toLowerCase() === ele.tagName.toLowerCase()){
let CmpCls = opts.components[compName];
}
}
}
})
代碼中CmpCls其實就是我們最初定義的兩個類Tab、UserLogin,然後通過實例化它們,並調用各自實例對象的render方法,再通過找到的未知元素ele來進行父元素(root)裏內容的替換渲染了。代碼如下:
Array.from(elements).forEach((ele) =>{
if(ele.constructor === HTMLUnknownElement){
for(let compName in opts.components){
if(compName.toLowerCase() === ele.tagName.toLowerCase()){
let CmpCls = opts.components[compName];
let cmp = new CmpCls();
let res = cmp.render();
ele.parentNode.replaceChild(res,ele);
}
}
}
})
再看下頁面最終呈現的內容:
正確地將我們自定義Tab、UserLogin兩個組件的內容渲染了出來。
最終完整代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>實現render函數</title>
</head>
<body>
<div id="div1">
<span>test</span>
<Tab></Tab>
<UserLogin></UserLogin>
</div>
<script>
function render(opts){
//1.找到root
let root = null;
if(typeof opts.root === "string"){
root = document.querySelector(opts.root);
if(!root){
throw new Error(`can't found ${opts.root}`)
}
}else if(opts.root instanceof HTMLElement){
root = opts.root
}else{
throw new Error(`root invalid`)
}
//2.找出所有自定義的元素
let elements = root.getElementsByTagName("*");
Array.from(elements).forEach((ele) =>{
if(ele.constructor === HTMLUnknownElement){
for(let compName in opts.components){
if(compName.toLowerCase() === ele.tagName.toLowerCase()){
let CmpCls = opts.components[compName];
let cmp = new CmpCls();
let res = cmp.render();
ele.parentNode.replaceChild(res,ele);
}
}
}
})
}
class Component{
render(){
throw new Error('render is required');
}
}
class Tab extends Component{
render(){
let div1 = document.createElement('div');
div1.innerHTML = '我是Tab組件';
return div1;
}
}
class UserLogin extends Component{
render(){
let div2 = document.createElement('div');
div2.innerHTML = "我是登錄組件"
return div2
}
}
render({
root:'#div1',
components:{
Tab,UserLogin
}
})
</script>
</body>
</html>
Over!