ejs模版實現遞歸樹形結構渲染


  使用過前端模板的同學們,尤其是使用過nodejs寫后台服務的同學們,應該對ejs模板和jade模板都不陌生。對與ejs模板和jade模板孰強孰弱,載各大論壇中一直爭論不休,有說ejs更直觀的,也有說jade更優雅、更強大的。我今天不討論誰好誰壞,而是記錄一下這幾天發現的一個特殊的使用場景——遞歸樹形結構渲染。

什么是遞歸樹形結構渲染?

  遞歸樹形結構其實是特指那些父子結構中子級展開后和父級結構相同或類似,並有可能繼續展開不斷延伸,有點像樹形結構中的枝干,每一級的枝干即是上一級枝干的子級,又是下一級枝干的父級,單獨從局部上看又是一樣的結構,整體又構成了一個層次分明的樹。這種結構其實很常見,比如樹形文件夾-文件管理、家里的族譜、公司的部門划分、網站分類的菜單等。但是為什么又覺得在開發中感到很陌生,是因為我們平時很少寫遞歸的代碼,前端的產品形態也很少使用遞歸和樹形結構,如果有就會去掉用別人的三方js庫,如果是app或者桌面應用就不免要遍歷文件夾來展示,還有就是前端的交互特點更多傾向於多級聯動,即點開某一級的時候再去拉數據加載或計算當前的下一級,比如常見的省份組件和日期選擇器。

我遇到的問題——ejs沒有遞歸語法?

  中所周知ejs模板更像是html的擴展,在html的基礎上加入了變量和邏輯(條件和循環)以及片段引用。因此常規的情況下是很適合使用,可讀性強,前后端的同學都能輕松上手。但是要實現遞歸的樹形結構就要滿足一個條件——自己調用自己。ejs並不支持定義一個函數。但是jade就不同,jade有mixin的語法,這從某種程度上就滿足了要求。

  但是我現有的項目都是ejs模板實現的,局部使用jade又會使項目的模板引擎管理顯得很亂。加上另一個原因就是我的項目是前后端復用同一套模板,而且這個需要遞歸的功能是在前端模板來實現的,webpack中使用ejs-loader會有另一個問題就是不支持include語法。面前有一個方法就是在前端再加一個jade-loader來遞歸處理樹形的數據。

ejs實現遞歸樹形結構渲染的猜想

  經過一番斗爭我找到了在ejs中實現遞歸樹形結構渲染的方法。要記住兩個限制:1、盡量不使用include語法,來保證前端工程中ejs-loader的兼容性;2、ejs模板本身不支持函數定義。

  但是這兩個不就是遞歸實現的基本途徑嗎?是也不是,ejs只是不允許函數的定義,但是卻允許函數的調用,即 <%= data %>  中data可以是變量值或表達式,既然是表達式,就說明data可以是某個函數的調用表達式即 <%= data.fn(option) %> 。甚至來說函數的返回值可以是一個html或者另一個模板。

那么函數的定義在哪里完成?

  既然是data.fn 那么fn就是data的一部分,即函數是數據中的一個字段,我們知道webpack 使用ejs-loader時require進來就是個函數。

1 var $$tmpl = require('./tmpl.ejs');
2 // $$tmpl 是個直接可以渲染數據的函數
3 $(body).append($$tmpl(data));

  我們直接將這個函數傳進去,即:

 1 var $$tmpl = require(' ./tmpl.ejs '); // 一級模板
 2 var $$recursiveTmpl = require('./recursive.ejs'); // 二級模板:局部遞歸部分的模板,此時$$tmpl 和 $$recursiveTmpl 都是函數
 3 // $$tmpl 是個直接可以渲染數據的函數
 4 $.get('/api/getlist', function (res) {
 5     // 一級模板渲染使用的數據由數據和二級模板函數組成
 6     var data = {
 7         'list': res.list,
 8         'tmplFn': $$recursiveTmpl
 9     }
10 
11     // 將數據傳入,一級模板
12     $(body).append($$tmpl(data));
13 })

  js調用的部分就算是完成了,那么模板改怎么寫呢?我們看看一級模板—— ./tmpl.ejs 

<p>遞歸list</p>
<div>
    <%= tmplFn({"list": list, "tmplFn": tmplFn}) %>
</div>

 

我們再看看二級遞歸模板—— ./recursive.ejs 

 1 <ul>
 2     <% list.forEach(function(item){ %>
 3         <li>
 4            <span><%= item.title %></span>
 5            <% if (item.subList && item.subList.length > 0) { %>
 6                <span>有下一級</span>
 7                <%= tmplFn ({"list": item.subList , "tmplFn": tmplFn})%>
 8            <% } else { %>
 9                <span>沒有下一級了</span>
10            <% } %>
11         </li>
12     <% } %>
13 </ul>

還記得遞歸調用的特點嗎?1、調用自身;2、由特定條件的出口

可以看出二級遞歸模板是哥不斷延展的ul > li > ul > li 的樹形結構,再想象遞歸調用的特點。不難看出

<%= tmplFn ({"list": item.subList , "tmplFn": tmplFn})%>

 

就是不斷調用自身的保障,tmplFn就是從最外層的js開始傳入的,每次作為屬性字段傳入,供下一級再次使用。而 if (item.subList && item.subList.length > 0) 就是跳出結束使用的邊界條件。

就這樣,我們就實現了ejs的遞歸調用,而且是在ejs功能不全的前端ejs-loader中兼容的遞歸調用。

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM