回顧
接上文:【大前端之前后分離01】JS前端渲染VS服務器端渲染,我們探討了為什么要做前后分離,以及前端渲染需要解決的問題,最后提出了自己的解決方案:
前端代碼編譯形成兩套代碼:①前端發布版本 + ②服務器端腳本
這個想法借鑒了fis plus的smarty模塊化思維,以及reactJS編譯運行的概念,上次初步論證了其可行性,也遺留了一些問題,其中比較關鍵的問題是:
前端模塊嵌套問題
我們在一個模板中又有一個widget,在子模板中又有一個widget,父模塊與子模塊中有數據依賴,或者子模塊為一個循環,循環卻依賴父模塊某個值,這個便很麻煩。
舉個例子來說,我們首頁引入了一個商品模塊,商品類型模塊為一循環模塊,里面又有子模塊:
index首頁模塊:
1 <div id="type_widget_wrapper"> 2 <script type="text/javascript"> 3 render('text!./template/type.html', './model/type', './controller/type', 'type_widget_wrapper'); 4 </script> 5 </div>
type模塊:
1 <ul id="type_id"> 2 <% for (var i = 0, len = data.length; i < len; i++) { %> 3 <li class="type js_type"> 4 <h2><%=data[i].name%></h2> 5 <ul class="product_list"> 6 <% for (var j = 0, len1 = data[i].product.length; j < len1; j++) { %> 7 <li class="product"> 8 <%=data[i].product[j].name%> 9 </li> 10 <% } %> 11 </ul> 12 </li> 13 <% } %> 14 </ul>
可以看到,其中有第二次循環迭代的將該類型的商品信息讀出,如果我們想將商品信息模塊化的,這里便出現了模塊嵌套情況:
1 <ul id="type_id"> 2 <% for (var i = 0, len = data.length; i < len; i++) { %> 3 <li class="type js_type"> 4 <h2><%=data[i].name%></h2> 5 <ul class="product_list"> 6 <div id="product_list_widget_wrapper"> 7 <script type="text/javascript"> 8 render('text!./template/product_list.html', './model/product_list', './controller/product_list', 'product_list_widget_wrapper'); 9 </script> 10 </div> 11 </ul> 12 </li> 13 <% } %> 14 </ul>
這里暫時不考慮子模塊中還有異步數據請求問題,我們將列表對應的模板放到了單個文件中:
1 <% for (var j = 0, len1 = data[i].product.length; j < len1; j++) { %> 2 <li class="product"> 3 <%=data[i].product[j].name%> 4 </li> 5 <% } %>
這里的循環解析便是我們今天研究的重點,因為前端模塊至少需要兩個條件:
① 唯一的dom容器
② 能獲取父級模塊的相關數據
為了解決這個問題,我這里提出了迭代模塊的概念。
迭代模塊
所謂迭代模塊,便是用於數據內嵌形式,並且處於循環中的模塊,比如上述例子,我整個type模板就變成了這樣(這里為最簡形式):
1 <ul id="type_id"> 2 <% for (var i = 0, len = data.length; i < len; i++) { %> 3 <li class="type js_type"> 4 <h2> 5 <%=data[i].name%></h2> 6 <ul class="product_list"> 7 8 <div id="data_inner_widget_wrapper_<%=i %>"> 9 <script type="text/javascript"> 10 iteratorRender({ 11 index: typeof <%=i%> == 'string' ? '<%=i%>' : <%=i%>, 12 value: <%=JSON.stringify(data[i])%>, 13 name: 'data_inner' 14 }); 15 </script> 16 </div> 17 18 </ul> 19 </li> 20 <% } %> 21 </ul>
這個是編譯過后形成的前端代碼,最初是這樣的:
1 <ul id="type_id"> 2 <% for (var i = 0, len = data.length; i < len; i++) { %> 3 <li class="type js_type"> 4 <h2> 5 <%=data[i].name%></h2> 6 <ul class="product_list"> 7 <%iteratorWidget({ 8 index: <%=i%>, 9 value: <%=JSON.stringify(data[i])%>, 10 name: 'data_inner', 11 }); %> 12 </ul> 13 </li> 14 <% } %> 15 </ul>
1 <%iteratorWidget({ 2 index: <%=i%>, //索引,整數或者字符串 3 value: <%=JSON.stringify(data[i])%>, //對應數據對象,字符串或者json對象 4 name: 'data_inner', 5 }); %>
這個時候前端需要實現iteratorRender方法,首先前端模板將上述代碼解析結束后是這個樣子的:

1 "<ul id="type_id"> 2 3 <li class="type js_type"> 4 <h2> 5 電腦</h2> 6 <ul class="product_list"> 7 8 <div id="data_inner_widget_wrapper_0"> 9 <script type="text/javascript"> 10 iteratorRender({ 11 index: typeof 0 == 'string' ? '0' : 0, 12 value: {"id":1,"name":"電腦","product":[{"name":"戴爾"},{"name":"蘋果"},{"name":"聯想"},{"name":"華碩"}]}, 13 name: 'data_inner' 14 }); 15 </script> 16 </div> 17 18 </ul> 19 </li> 20 21 <li class="type js_type"> 22 <h2> 23 書籍</h2> 24 <ul class="product_list"> 25 26 <div id="data_inner_widget_wrapper_1"> 27 <script type="text/javascript"> 28 iteratorRender({ 29 index: typeof 1 == 'string' ? '1' : 1, 30 value: {"id":2,"name":"書籍","product":[{"name":"三國演義"},{"name":"西游記"},{"name":"紅樓夢"},{"name":"水滸傳"}]}, 31 name: 'data_inner' 32 }); 33 </script> 34 </div> 35 36 </ul> 37 </li> 38 39 <li class="type js_type"> 40 <h2> 41 游戲</h2> 42 <ul class="product_list"> 43 44 <div id="data_inner_widget_wrapper_2"> 45 <script type="text/javascript"> 46 iteratorRender({ 47 index: typeof 2 == 'string' ? '2' : 2, 48 value: {"id":3,"name":"游戲","product":[{"name":"仙劍1"},{"name":"仙劍2"},{"name":"仙劍3"},{"name":"仙劍4"}]}, 49 name: 'data_inner' 50 }); 51 </script> 52 </div> 53 54 </ul> 55 </li> 56 57 </ul>
1 <li class="type js_type"> 2 <h2> 3 電腦</h2> 4 <ul class="product_list"> 5 6 <div id="data_inner_widget_wrapper_0"> 7 <script type="text/javascript"> 8 iteratorRender({ 9 index: typeof 0 == 'string' ? '0' : 0, 10 value: { "id": 1, "name": "電腦", "product": [{ "name": "戴爾" }, { "name": "蘋果" }, { "name": "聯想" }, { "name": "華碩"}] }, 11 name: 'data_inner' 12 }); 13 </script> 14 </div> 15 16 </ul> 17 </li>
然后前端方法的實現為:
1 //最簡單實現,僅考慮渲染,不嚴謹 2 var iteratorRender = function (opts) { 3 var name = opts.name; 4 var index = opts.index; 5 var data = typeof opts.value == 'string' ? JSON.parse(opts.value) : opts.value; 6 var wrapperId = name + '_widget_wrapper_' + index; 7 var template = 'text!./template/' + name + '.html'; 8 var controller = './controller/' + name; 9 10 require([template, controller], function (tpl, view) { 11 var html = $(_.template(tpl)(data)); 12 var wrapper = $('#' + wrapperId); 13 html.insertBefore(wrapper); 14 wrapper.remove(); 15 //執行控制器 16 view.init(); 17 }); 18 }
然后代碼運行,邏輯跑通了:
結語
由於最近工作強度上來了,解決了前端渲染時候的模板嵌套問題,一直拖到了今天,服務器端的模板嵌套更好處理,該方案后續會繼續細化