目前来看,团队内部前端项目已全面实施组件化开发。组件化的好处太多,如:按需加载、可复用、易维护、可扩展、少挖坑、不改组件代码直接切成服务器端渲染(如Nuclear组件化可以做到,大家叫同构)...

怎么做到这么强大的优势,来回忆下以前见过的坑,或者现有项目里的坑。

CSS层叠样式?保佑不要污染别的HTML!

在web前端,一般一个组件必须要有骨架HTML和装饰的CSS以及JS逻辑。而CSS要是可以是局部作用域那就再好不过了!就不用写长长的前缀了,浪费带宽不说,而且费劲。

  1. .ui-popup-arrow-xx-xxxxx-xxxx-container {
  2. }

这回够长了吧,不会污染别的HTML了吧。真的太长了,没有办法,因为CSS不是局部的,怕污染其他的HTML,规划好长长的namespace、module是以前的最佳实践。

怎么优雅绑定事件?只能定义在window下?

如果HTML绑定的事件是局部作用域那就再好不过了!我真的见过模版代码里出现下面的代码:

  1. <div onclick="xxx()"></div>

然后在js里找到了下面的代码:

  1. <script>
  2. window.xxx = function(){
  3. }
  4. </script>

要绑定的事件一多,得污染多少全局变量啊。所以还有的工程师这么干:

  1. <div onclick="ns.xxx()"></div>
  2. <div onclick="ns.xxxx()"></div>

然后在js里找到了下面的代码:

  1. <script>
  2. window.ns = {};
  3. ns.xx = function(){
  4. }
  5. ns.xxx = function(){
  6. }
  7. </script>

这里貌似比不设定namespace好很多,但是还是妥协的结果。一般希望能封装成组件,组件的HTML里绑定的事件就是组件内定义的事件,内聚内聚!!

通过js动态绑定事件的坏处我以前专门写了一篇文章来阐述,主要是lazy bind会导致用户看到了页面,但是页面确无法响应用户的交互,这里不再阐述。

需求变更?找不到在哪改代码?

大型项目如游戏什么的为啥都是面向对象式的写法?如果一个组件刚好又能是一个Class那就再好不过,Class base可以更方便地抽象现实世界的物体及其属性或者逻辑算法,所以甚至有些编程语言都是面向对象的(这里逆向逻辑),如JAVA、C#...整体过程式的代码对于大型项目几乎没法维护(如基于jQuery就能容易写出整体都是过程式的组织结构),整体OO,局部过程式是可以接受的。

组件需要嵌套?只能复制粘贴原组件?

扁平无嵌套组件还是比较简单,对模板的字符串处理下,把绑定的事件全指向组件自身定义的方法,生命周期也好处理。在真正的业务里经常需要组件嵌套,这样也更利于复用。虽然大量模板引擎支持引用子模板、共享数据等,但是组件是有生命周期的,模板嵌套不能真正解决组件嵌套的问题。能支持组件嵌套并且声明式嵌套就那就再好不过了!

数据变了?重新生成HTML替换一下?

怎么替换?先查找dom?什么?你还在查找dom?你还在背诵CSS选择器?替换一下?不能增量更新吗?或者diff一下吧?不要每次全部替换啊!

首屏太慢?以前抽象的组件没法复用?

什么?首屏太慢?改成直出(服务器渲染)?以前代码没法复用?要推翻重写?什么?怎么搞?排期?产品不给排期?需求没变为什么要给排期?

下面来看下Nuclear怎么解决上面问题。

install Nuclear

  1. npm install alloynuclear

Hello,Nuclear!

  1. var HelloNuclear = Nuclear.create({
  2. render: function () {
  3. return '<div>Hello , {{name}} !</div>';
  4. }
  5. })
  6. new HelloNuclear({ name: "Nuclear" }, "body");

内置了mustache.js无逻辑模板。

事件绑定

  1. var EventDemo = Nuclear.create({
  2. clickHandler: function (evt, target, other1,other2) {
  3. //MouseEvent {isTrusted: true, screenX: 51, screenY: 87, clientX: 51, clientY: 21…}
  4. console.log(evt);
  5. //<div onclick="Nuclear.instances[0].clickHandler(event,this,'otherParameter1','otherParameter2')">Click Me!</div>
  6. console.log(target);
  7. //otherParameter1
  8. console.log(other1);
  9. //otherParameter2
  10. console.log(other2);
  11. alert("Hello Nuclear!");
  12. },
  13. render: function () {
  14. return '<div onclick="clickHandler(event,this,\'otherParameter1\',\'otherParameter2\')">Click Me!</div>'
  15. }
  16. })
  17. new EventDemo({ seen: true }, "body");

条件判断

  1. var ConditionDemo = Nuclear.create({
  2. render: function () {
  3. return '{{#seen}}\
  4. <div>\
  5. you can see me\
  6. </div>\
  7. {{/seen}}\
  8. {{^seen}}\
  9. <div>\
  10. yan can not see me\
  11. </div>\
  12. {{/seen}}'
  13. }
  14. })
  15. var cd = new ConditionDemo({ seen: true }, "body");
  16. setTimeout(function () {
  17. cd.option.seen = false;
  18. }, 2000);

2秒后改变seen,dom会自动变更。

循环

  1. var LoopDemo = Nuclear.create({
  2. render: function () {
  3. return '<ul>{{#list}}<li>姓名:{{name}} 年龄:{{age}}</li>{{/list}}</ul>'
  4. }
  5. })
  6. var ld = new LoopDemo({
  7. list: [
  8. { name: "dntzhang", age: 18 },
  9. { name: "vorshen", age: 17 }
  10. ]
  11. }, "body");
  12. setTimeout(function () {
  13. //增加
  14. ld.option.list.push({ name: "lisi", age: 38 });
  15. }, 1000);
  16. setTimeout(function () {
  17. //修改
  18. ld.option.list[0].age = 19;
  19. }, 2000);
  20. setTimeout(function () {
  21. //移除
  22. ld.option.list.splice(0, 1);
  23. }, 3000);

Array的变更也能监听到,能够自动触发Dom的变更。

局部CSS

  1. <body>
  2. <div>I'm other div!! my color is not red!!</div>
  3. <script src="../dist/nuclear.js"></script>
  4. <script type="text/javascript">
  5. var ScopedCSSDemo = Nuclear.create({
  6. clickHandler: function () {
  7. alert("my color is red!");
  8. },
  9. render: function () {
  10. return '<div onclick="clickHandler()">my color is red!</div>'
  11. },
  12. style: function () {
  13. return 'div { cursor:pointer; color:red }';
  14. }
  15. })
  16. //第三个参数true代表 增量(increment)到body里,而非替换(replace)body里的
  17. new ScopedCSSDemo ({ seen: true }, "body" ,true);
  18. </script>
  19. </body>

组件外的div不会被组件内的CSS污染。

讨厌反斜杠?

讨厌反斜杠可以使用 ES20XX template literals、或者split to js、css和html文件然后通过构建组装使用。也可以用template标签或者textare存放模板。

  1. <template id="myTemplate">
  2. <style>
  3. h3 {
  4. color: red;
  5. }
  6. button {
  7. color: green;
  8. }
  9. </style>
  10. <div>
  11. <div>
  12. <h3>TODO</h3>
  13. <ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>
  14. <form onsubmit="add(event)">
  15. <input nc-id="textBox" value="{{inputValue}}" type="text">
  16. <button>Add #{{items.length}}</button>
  17. </form>
  18. </div>
  19. </div>
  20. </template>
  21. <script>
  22. var TodoApp = Nuclear.create({
  23. install: function () {
  24. this.todoTpl = document.querySelector("#myTemplate").innerHTML;
  25. },
  26. add: function (evt) {
  27. evt.preventDefault();
  28. this.inputValue = "";
  29. this.option.items.push(this.textBox.value);
  30. },
  31. render: function () {
  32. return this.todoTpl;
  33. }
  34. });
  35. new TodoApp({ inputValue: "", items: [] }, "body");
  36. </script>

组件嵌套

  1. <script>
  2. var TodoList = Nuclear.create({
  3. render: function () {
  4. return '<ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>';
  5. }
  6. });
  7. </script>
  8. <script>
  9. var TodoTitle = Nuclear.create({
  10. render: function () {
  11. return '<h3>{{title}}</h3>';
  12. }
  13. });
  14. </script>
  15. <script>
  16. var TodoApp = Nuclear.create({
  17. install: function () {
  18. //pass options to children
  19. this.childrenOptions = [{ title: "Todo" }, { items: [] }];
  20. this.length = 0;
  21. },
  22. add: function (evt) {
  23. evt.preventDefault();
  24. //this.nulcearChildren[1].option.items.push(this.textBox.value);
  25. //or
  26. this.list.option.items.push(this.textBox.value);
  27. this.length = this.list.option.items.length;
  28. this.textBox.value = "";
  29. },
  30. render: function () {
  31. //or any_namespace.xx.xxx.TodoList 对应的 nc-constructor="any_namespace.xx.xxx.TodoList"
  32. return '<div>\
  33. <child nc-constructor="TodoTitle"></child>\
  34. <child nc-constructor="TodoList" nc-name="list"></child>\
  35. <form onsubmit="add(event)" >\
  36. <input nc-id="textBox" value="{{inputValue}}" type="text" />\
  37. <button>Add #'+ this.length + '</button>\
  38. </form>\
  39. </div>';
  40. }
  41. });
  42. new TodoApp({ inputValue: "" }, "body");
  43. </script>

通过在父对象的install里设置this.childrenOptions来把option传给子节点。

服务器端渲染

  1. function todo(Nuclear,server) {
  2. var Todo = Nuclear.create({
  3. add: function (evt) {
  4. evt.preventDefault();
  5. this.option.items.push(this.textBox.value);
  6. },
  7. render: function () {
  8. return `<div>
  9. <h3>TODO</h3>
  10. <ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>
  11. <form onsubmit="add(event)" >
  12. <input nc-id="textBox" type="text" value="" />
  13. <button>Add #{{items.length}}</button>
  14. </form>
  15. </div>`;
  16. },
  17. style: function () {
  18. return `h3 { color:red; }
  19. button{ color:green;}`;
  20. }
  21. },{
  22. server:server
  23. });
  24. return Todo;
  25. }
  26. if ( typeof module === "object" && typeof module.exports === "object" ) {
  27. module.exports = todo ;
  28. } else {
  29. this.todo = todo;
  30. }

通过第二个参数server来决定是服务器端渲染还是客户端渲染。server使用的代码也很简单:

  1. var koa = require('koa');
  2. var serve = require('koa-static');
  3. var router = require('koa-route');
  4. var app = koa();
  5. var jsdom = require('jsdom');
  6. var Nuclear = require("alloynuclear")(jsdom.jsdom().defaultView);
  7. var Todo = require('./component/todo')(Nuclear,true);
  8. app.use(serve(__dirname + '/component'));
  9. app.use(router.get('/todos', function *(){
  10. var str = require('fs').readFileSync(__dirname + '/view/index.html', 'utf8');
  11. var todo = new Todo({ items: ["Nuclear2","koa",'ejs'] });
  12. this.body = Nuclear.Tpl.render(str, {
  13. todo: todo.HTML
  14. });
  15. Nuclear.destroy(todo);
  16. }));
  17. app.listen(3000);

浏览器端使用的代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. </head>
  5. <body>
  6. {{{todo}}}
  7. <script src="./nuclear.js"></script>
  8. <script src="./todo.js"></script>
  9. <script>
  10. var Todo= todo(Nuclear);
  11. new Todo('body');
  12. </script>
  13. </body>
  14. </html>

这样,组件的代码不需要任何变更就可以在server和client同时使用。

Nuclear如何做到同构的?

内置三条管线如下所示:

比如一般前后端分离的开发方式,仅仅会走中间那条管线。而同构的管线如下所示:

这里前后后端会共用option,所以不仅仅需要直出HTML,option也会一并支持给前端用来二次渲染初始一些东西。

Nuclear优势

1.节约流量

2.提升用户体验

3.加载更加灵活

4.Dom查找几乎绝迹

5.搭积木一样写页面

6.提升代码复用性

7.可插拔的模板引擎

8.Lazy CSS首屏更轻松

9.Nuclear文件大小6KB (gzip)

10.零行代码修改无缝切到同构直出

...

...

Nuclear Github

https://github.com/AlloyTeam/Nuclear

漫谈Nuclear Web组件化入门篇的更多相关文章

  1. Mr.聂 带你成为web开发大牛——入门篇(上)

    作为一名IT届的后生,当初也经历过懵懂无知的实习期,对那种无力感深有体会.在这,希望能用我这几年的开发经验,让各位即将踏入或者刚刚踏入web开发领域的新人们少走些弯路.鉴于这是入门篇,下面我就从零为大 ...

  2. atitit.  web组件化原理与设计

    atitit.  web组件化原理与设计 1. Web Components提供了一种组件化的推荐方式,具体来说,就是:1 2. 组件化的本质目的并不一定是要为了可复用,而是提升可维护性. 不具有复用 ...

  3. Lightning Web Components 来自salesforce 的web 组件化解决方案

    Lightning Web Components 是一个轻量,快速,企业级别的web 组件化解决方案,官方网站也提供了很全的文档 对于我们学习使用还是很方便的,同时我们也可以方便的学习了解salesf ...

  4. 探讨Web组件化的实现

    CMS组件化,简单架构示意图: Web组件使用WebPage+WebAPI的好处: Ø  组件复用(组件条件管理页面复用+获取组件数据API复用). Ø  组件是分布式的第三方应用,本身高内聚.组件之 ...

  5. Sass与Web组件化相关的功能

    Sass https://en.wikipedia.org/wiki/Sass_(stylesheet_language) Sass (Syntactically Awesome Stylesheet ...

  6. web组件开发入门

    本文是学习慕课网阿当大话西游之WEB组件后的一个总结. 组件的分类 1 框架组件:依赖于某种框架的组件 2 定制组件:根据公司业务定制的组件 3 独立组件:不依赖框架的组件 定义和加载组件 解决css ...

  7. ASP.NET Web API 之一 入门篇

    一.基于RESTful标准的Web Api 原文讲解:https://www.cnblogs.com/lori/p/3555737.html 微软的web api是在vs2012上的mvc4项目绑定发 ...

  8. 2017年试试Web组件化框架Omi

    Open and modern framework for building user interfaces. Omi的Github地址https://github.com/AlloyTeam/omi ...

  9. web自动化-selenium 入门篇

    selenium安装介绍 selenium是web浏览器的自动化工具 官网:https://www.selenium.dev 构成: WebDriver: 浏览器提供的浏览器api来控制浏览器(模拟用 ...

随机推荐

  1. CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法

    CentOS7之按时间段截取指定的Tomcat日志到指定文件的方法 sed -n '/2016-11-02 15:00:/,/2016-11-02 15:05:/p' catalina.out > ...

  2. angularJS(6)

    angularJS(6) 一:angularJs的事件. 1.ng-click指令定义了AngularJS点击事件. <div ng-app="myapp" ng-contr ...

  3. Xamarin.Android之ContentProvider

    一.前言 掌握了如何使用SQLiteOpenHelper之后,我们就可以进行下一步的学习.本章我们将会学习如何使用ContentProvider来将数据库方面的操作封装起来,同时它还可以供其他应用访问 ...

  4. Linux上运行NET

    今天尝试了下Ubuntu上运行NET程序,按照 https://github.com/aspnet/Home 的指引,一步一步来: 1.安装DNVM(原名KVM) Linux控制台下输入 curl - ...

  5. smartcrop.js智能图片裁剪库

    今天将为大家介绍一款近期github上很不错的开源库 – smartcrop.js.它是一款图片处理的智能裁剪库.在很多项目开发中,经常会遇见上传图片的场景,它可能是用户照片信息,也可能是商品图片等. ...

  6. protocol buffers vs json vs XML

    原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com   在分布式系统中,数据序列化传递的情形非常常见,主流的三种,JSON.XML.Protobuf.   XML现在 ...

  7. ABP源码分析七:Setting 以及 Mail

    本文主要说明Setting的实现以及Mail这个功能模块如何使用Setting. 首先区分一下ABP中的Setting和Configuration. Setting一般用于需要通过外部配置文件(或数据 ...

  8. Entity Framework 6 Recipes 2nd Edition(10-3)译 -> 返回结果是一个标量值

    10-3. 返回结果是一个标量值 问题 想取得存储过程返回的一个标量值. 解决方案 假设我们有如Figure 10-2所示的ATM机和ATM机取款记录的模型 Figure 10-2. 一个ATM机和A ...

  9. 深入MySQL索引

    MySQL索引作为数据库优化的常用手段之一在项目优化中经常会被用到, 但是如何建立高效索引,有效的使用索引以及索引优化的背后到底是什么原理?这次我们深入数据库索引,从索引的数据结构开始说起. 索引原理 ...

  10. 动态给textView加图片

    Drawable img = layout.getResources().getDrawable(R.drawable.icon); // 调用setCompoundDrawables时,必须调用Dr ...