使用過前端模板的同窗們,尤爲是使用過nodejs寫後臺服務的同窗們,應該對ejs模板和jade模板都不陌生。對與ejs模板和jade模板孰強孰弱,載各大論壇中一直爭論不休,有說ejs更直觀的,也有說jade更優雅、更強大的。我今天不討論誰好誰壞,而是記錄一下這幾天發現的一個特殊的使用場景——遞歸樹形結構渲染。html
什麼是遞歸樹形結構渲染?前端
遞歸樹形結構實際上是特指那些父子結構中子級展開後和父級結構相同或相似,並有可能繼續展開不斷延伸,有點像樹形結構中的枝幹,每一級的枝幹便是上一級枝幹的子級,又是下一級枝幹的父級,單獨從局部上看又是同樣的結構,總體又構成了一個井井有條的樹。這種結構其實很常見,好比樹形文件夾-文件管理、家裏的族譜、公司的部門劃分、網站分類的菜單等。可是爲何又以爲在開發中感到很陌生,是由於咱們平時不多寫遞歸的代碼,前端的產品形態也不多使用遞歸和樹形結構,若是有就會去掉用別人的三方js庫,若是是app或者桌面應用就難免要遍歷文件夾來展現,還有就是前端的交互特色更多傾向於多級聯動,即點開某一級的時候再去拉數據加載或計算當前的下一級,好比常見的省份組件和日期選擇器。node
中所周知ejs模板更像是html的擴展,在html的基礎上加入了變量和邏輯(條件和循環)以及片斷引用。所以常規的狀況下是很適合使用,可讀性強,先後端的同窗都能輕鬆上手。可是要實現遞歸的樹形結構就要知足一個條件——本身調用本身。ejs並不支持定義一個函數。可是jade就不一樣,jade有mixin的語法,這從某種程度上就知足了要求。webpack
可是我現有的項目都是ejs模板實現的,局部使用jade又會使項目的模板引擎管理顯得很亂。加上另外一個緣由就是個人項目是先後端複用同一套模板,並且這個須要遞歸的功能是在前端模板來實現的,webpack中使用ejs-loader會有另外一個問題就是不支持include語法。面前有一個方法就是在前端再加一個jade-loader來遞歸處理樹形的數據。web
通過一番鬥爭我找到了在ejs中實現遞歸樹形結構渲染的方法。要記住兩個限制:一、儘可能不使用include語法,來保證前端工程中ejs-loader的兼容性;二、ejs模板自己不支持函數定義。後端
可是這兩個不就是遞歸實現的基本途徑嗎?是也不是,ejs只是不容許函數的定義,可是卻容許函數的調用,即 <%= data %> 中data能夠是變量值或表達式,既然是表達式,就說明data能夠是某個函數的調用表達式即 <%= data.fn(option) %> 。甚至來講函數的返回值能夠是一個html或者另外一個模板。api
既然是data.fn 那麼fn就是data的一部分,即函數是數據中的一個字段,咱們知道webpack 使用ejs-loader時require進來就是個函數。數組
1 var $$tmpl = require('./tmpl.ejs'); 2 // $$tmpl 是個直接能夠渲染數據的函數 3 $(body).append($$tmpl(data));
咱們直接將這個函數傳進去,即:app
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>
還記得遞歸調用的特色嗎?一、調用自身;二、由特定條件的出口
能夠看出二級遞歸模板是哥不斷延展的ul > li > ul > li 的樹形結構,再想象遞歸調用的特色。不難看出
<%= tmplFn ({"list": item.subList , "tmplFn": tmplFn})%>
就是不斷調用自身的保障,tmplFn就是從最外層的js開始傳入的,每次做爲屬性字段傳入,供下一級再次使用。而 if (item.subList && item.subList.length > 0) 就是跳出結束使用的邊界條件。
就這樣,咱們就實現了ejs的遞歸調用,並且是在ejs功能不全的前端ejs-loader中兼容的遞歸調用。