【大前端之前后分离02】前端模板嵌套问题
回顾
接上文:【大前端之前后分离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 }
然后代码运行,逻辑跑通了:
结语
由于最近工作强度上来了,解决了前端渲染时候的模板嵌套问题,一直拖到了今天,服务器端的模板嵌套更好处理,该方案后续会继续细化