-
Notifications
You must be signed in to change notification settings - Fork 761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Web应用组件化的权衡 #22
Comments
一不小心,又写多了……看来只能节选一些出来讲了 |
高产 太棒 |
mark |
Mark |
还是看文字版本比较好,slides的字体太大了,在笔记本上面查看的话。 |
最近的感觉,如果是按照 React理想化的单向数据流的方方式来设计图形化档案,实际上会更像 Canvas 绘图,所有的 View 的细节就应该在每一帧重新生成出来,那样的话也不用强调 Virtual DOM 了,直接把 DOM 生成出来,甚至 DOMA 上的状态也记录在 Model 或者 ViewModel 重新生成出来得了。所以 DOM 被重复那么多遍,完全是因为对了 React 来说 DOM 这种面向对象的抽象对他来说太碍眼太重要了。 |
感谢徐老师分享,今天讲的也很棒! |
现场听加文字,学到了好多,谢谢老师~ |
@jiyinyiyong 是的,DOM对于React体系来说,有些束缚的感觉…… |
本以为说了这么多组件化的痛点之后,会兜售一套自己的解决方案,结果竟然没有。 很精彩,受益匪浅! |
楼主提的组件化设计其实就是软件架构思想,不仅仅是前端UI了。对于前端来说,已经到了跨平台的组件时代了。 |
@bh-lay 在组件化领域,不存在通用的解决方案,只存在对于某些点的改进,还需要针对场景选择实现方式的一些策略 |
@luqin 是这样,当Web应用持续往重量级发展的时候,它跟传统的客户端开发技术有很多相似之处,可以吸收它们的大部分理念,融合Web技术的自身特点来考虑 |
学习。。 |
感觉收获很多 谢谢了! |
现在web前端的很多技术都是,在妥协遗留问题的前提下,一步步优化得出来的。如手机端,体验好的必然是native app。又要跨平台,又要牛逼的组织、渲染、通信,是很难融合的。 |
插一句题外话:你以前也是软创的? |
最近越来越感觉我原先对 Web Components 的态度有点过于乐观,架构上要考虑的事情太多了。 |
@myst729 我前一阵回顾了以前写的那篇有关Web Components的,发觉其中一些认知是不对的,比如js所处的位置之类,但这不影响我的一个整体看法:对“能够跨框架复用的Web Components”,也就是Web Components想要达到的一个目标持悲观态度。 Web Components能够成为各框架中,组件的底层实现,这一步没什么困难,但没法进一步走下去,如果它走不到跨框架复用,就它的存在感就很弱了。我这篇文章最后对Shadow DOM和Scoped CSS的质疑就是在这里,如果我们能够借助构建工具,轻松达成这些能力,那它们带给我们的提升是什么呢…… |
赞一个,昨天听的受益匪浅 |
好文,谢谢徐老师! |
分析得很好,学习了! 个人对组件化的理解其实也都是从后端开发借鉴的,在后端其实也有很多取舍,有的时候希望用configuration来配置一个通用组件,这样从上层看起来很清楚,有的时候逻辑太复杂,就单独写成独立模块,靠命名来描述特定的behavior,而且完全不是为了复用就是为了分治,可是时间一长规模一大就会很模糊和混淆(后端命名空间支持很好所以倒是不会太痛苦)。自己也做一点些前端,不过其实也很期待从前端的对稍大规模开发的新探索中看到新意来解决后端开发一直以来的疑问。个人现在比较偏向于更opinionated的组件化框架是因为现在前端开发有思维惯性,在MVC框架中虽然完全可以按照您一直以来倡导的组件化,插件化方法进行架构和开发,但是较容易回到重度耦合的套路上。所以感觉在一种强调组件优先的框架上开发会引导团队时刻从所期望的方向来考虑问题。 |
👍 @xufei ppt是怎么生成?看页面去获取解析md文件 |
@xiongsongsong 大致类似你说的第一种,但如果用web components的实现,组件内部的样式是隔离的作用域,主题的事情没有那么好办的。 |
@huang-x-h 这个是@hax 写的,你看他那个js是最好的语言 |
css-modules 怎么样? |
个人觉得,在组件化的过程中,其实最难考虑的可能就是 样式无法实现其强大的层叠样式功能,就不算层叠样式表了。。通信在复杂的组件通信过程中,没有一个管理员,怕是后期也难以维护。 |
没有最好的,只有最合适的 |
这里讲的模板是什么意思? |
@andge 这里的模板,指的是angular里面那种绑定了特定语法的html片段 |
datagrid思路真是漂亮,可以理解成,把angular指令的template进一步组件化,暴露出去还是一个公用指令,是这个套路吧 |
@sutaking 是这样,我在这里用了angular特有的黑魔法:作用域继承,这使得内部数据处于同一个作用域,无论是组件自身,还是作为渲染部件的模板,都能共享这个作用域。 如果使用Vue或者React,也是可以做到的,用React的话,一般来说,这里不会传入模板,而是传入渲染部件组件,而且要在组件内部给这些渲染部件传递数据。 |
polymer theme的实现是用了css变量,generator-polymer 这个项目有用到。react的模块化要结合css module,两个方案都挺有意思的 |
受教! HTML的不利因素2应该可以忽略吧,tr或者li元素应该都可以使用div+css来替换 |
说的很好,要是有演讲视频就好了~因为看的不是很懂 |
一下子给自己增加了很多的知识面,很赞,我还是要关注一下新技术的变革已经发展方向以及变革思想 |
昨天看一遍后去走走了解了一个圈, 今天在看一遍, 看出笔者的视角, 真是有共鸣啊~ |
这两天 在重复阅读这篇文章,受益颇深, 🙏 感谢分享 |
mark |
🤓飞叔,写的好,仔细又看了一遍 |
反复看了这文章,顿悟了很多,现在做技术不单单是要用好技术,还要结合场景来考虑技术的实现; |
感谢 |
真啰嗦。。 |
如果把模版单拎出来呢,诸如样式css所应用的位置具体还是落地到模版上, |
1. 基本概念
什么是Web应用?
所谓Web应用,指的是那些虽然用Web技术构建,但是展现形式却跟桌面程序或者移动端原生应用类似的产品。这类产品的特点是逻辑较重,交互复杂,通常也是单页式的。
主要包括:
大部分可以等同于所谓的“单页面应用”,可以参见之前写的这篇:构建单页Web应用
组件化开发的优势是什么?
组件化的最重要作用就是提升开发和维护的效率。
最原始的组件,其功能可以单独开发测试,然后逐级拼装成更复杂的组件,直到整个应用。每一级都是易装配,可追踪,可管控的。
在Web应用中,组件化一般指什么?
在开发Web应用的时候,无论技术选型,工程方案,还是对人员的技能需求都是有一些特点的,最重要的特点莫过于组件化。
组件化这个词,在UI这一层通常指“标签化”,也就是把大块的业务界面,拆分成若干小块,然后进行组装。
狭义的组件化一般是指标签化,也就是以自定义标签(自定义属性)为核心的机制。
广义的组件化包括对数据逻辑层业务梳理,形成不同层级的能力封装。
在Web应用中,组件化的主要目标是什么?
很多人会把复用作为组件化的第一需求,但实际上,在UI层,复用的价值远远比不上分治。
分治带来的是可管理性,相比一大团HTML和JavaScript的混杂,组件化之后,整个应用成为了一个很清晰的树,一眼就能看清包含关系,也能够很容易理清数据的传递方向。而且,整个应用可以从叶子节点,逐步向上测试,哪一级出了问题,可以很容易发现。
但是复用就很麻烦了,因为组件的内部实现与外部接口都很难取舍。很可能我们在设计之初,都是把组件设想成一个单一的东西,然后在实际项目中,发现最后都面目全非了。
所以,复用的工程成本很高,在使用的时候需要权衡,除了最常用了基础控件,其他的不要刻意追求。
2. 组件化应当做到什么程度?
一个软件产品中,如果把核心稳定的部分视为资产,灵活可变的部分视为耗材,我们如何对待资产?如何对待耗材?
对待资产,我们一般会比较重视,会有长远的规划,优雅的实现,持续的维护,细致的测试,详尽的文档等等,但是对于耗材,基本上会视为一次性的东西,不会有这么严谨的过程。
组件属于资产还是耗材?模板呢?
按照上面的分类,组件明显属于资产,而模板一般属于耗材。
在有些框架中,模板的使用度较低,但是常见的包含双向绑定的框架中,都有很大比重的模板。有些模板是嵌入到组件内部的,有些则是独立存在的,比如Angular中,可以使用ng-include动态包含一个模板,这个模板就是独立的了。
大部分Web应用中,资产多一些,还是耗材多一些?
大部分Web系统的前端部分,其实都是耗材比资产多,人们选用Web相关技术的一个典型心理就是容易写,而且相对随意一些。
大部分Web应用都适合“全”组件化吗?
这个问题要从几个方面回答:
组件与模板的对比
在展示内容偏多的网站中,模板是一个很常见的东西,它通过某种占位的HTML,包含简单的文本格式化,简单的条件判断,做一些很基础的动态内容生成操作。
但是在Web应用中,因为强调组件化,所以很多人对模板的重要性有些忽视了。这里的“模板”指的是双向绑定的动态模板,不是传统的静态模板,这个基本概念之前有过回答:
Handlebars 和angularjs有什么区别?分别在什么情况下使用?
在Web应用中,应当如何看待模板的地位呢?我们先来看另外一个问题:
HTML,CSS,JS,这三者里面,谁是整个Web工程的入口?
展示型的Web项目中,毫无疑问HTML是入口,也是根基,不管是JS还是CSS都是作为它的辅助。但到了Web应用中,还是这样吗?我们很多Web应用实际上是以JS为入口的,HTML不再被视为骨架,而是视为一种动态的东西,由JS创建并管理。
在这个前提下,人们对动态的HTML又有两种不同方式的认知:它是模板,还是组件?
从典型的MVVM三层中,我们可以看到,View Model是Model的外围,View是View Model的外围,一层一层出去,外层实际上可以视为内层的配置文件。而如果从组件化的角度出发,View跟View Model共同构成了组件层。
因此,动态的HTML究竟算是什么,取决于我们从什么角度去看待它,也取决于我们在使用什么框架。
3. 组件化框架
目前有哪些流行的组件化框架?
我们现在开发Web应用,一般也不会从0开始,通常是选取一个核心框架(库),然后在此基础上确定一些规则,逐步构建外围体系,现在比较火的有React,Angular,Vue,Polymer等。
“MV*”:Angular,Vue等
“反应式”:React,Reactive等
标准增强:Polymer
几个流派各自特点是什么?
MV*: 分层,绑定
React: 组件化,单向数据流
React中一般的组件相当于MVVM流派中的什么?
以上提到的几个东西,在组件化这块,可能争议最大的是Angular,因为Angular 1.x的官方指引中,并未在组件化这个方向上作一些指导,也没有提倡,甚至连建议都没有,而React和Polymer是天然组件化的,Vue提供的文档里以很大篇幅详细说明了组件化的机制和实践方式。
但是,这并不是说,Angular 1.x就是与组件化冲突的,它仍然可以通过directive等相关机制,实现自己特色的组件化方案。
Directive可以实现自定义标签和自定义属性,这两者可以理所当然地归类到组件中,但是,在Angular中,模板本身也可以视为一种组件,一种轻量级的组件,它不一定就是静态的,仍然可以有一些简单的操作和行为。
Directive和模板相当于MVVM中的View层,它们的运行,一般是离不开ViewModel的支撑的,在Angular中,这就是controller。所以,如果以Angular框架来说,directive和模板、controller,共同形成了视图层组件体系。推广到其他MVVM框架来说,也就是View和ViewModel,而React整体就处于视图层,所以这两者算是一个对等关系。
这些流派有共同的未来吗,会是什么?
无论是哪种框架,在开发Web应用的时候都要面临一个问题:业务数据层如何设计?
这一层东西,其实目前各路框架都未提出有力的解决方案,大家的重点都还是在做上层UI。
但是从长远来看,业务数据层会是一个基本没有框架差异的东西,同一个方案,大家都可以用,比如说之前有人把flux之类的东西放到React之外的框架用,也一样可以。
而上层UI,其实现过程现在也很明确地是要往Web Components靠拢,实现逻辑都是使用ES新标准,数据绑定机制都是getter setter或者observe,加载方式都在考虑HTTP2之类,一旦某个领域出现了理念突破,很快就会被其他框架吸收融合。
所以总的来说,各框架是趋同的。
4. 组件化的实践
一个全组件化体系,会形成组件树,上下级组件之间应当如何通讯?不同层级的组件之间应当如何通讯?
当我们把一个应用使用组件化的理念进行构建的时候,整个应用就形成了一个倒置的树,树根就是应用本身,其余节点是层层嵌套的组件们,叶子节点是最基础的组件。
如何规划组件树的层级与组件的粒度?
如果我们有两个不同团队,同样基于组件化的理念,使用同一个框架,做同样功能的产品,最终形成的组件树可能差别很大,这个差别主要在于:
把什么视为组件,组件的粒度是怎样的。
在组件化的应用中,组件树的层级不宜过深,从根节点算起,应当尽可能控制在3到5层内,如果层级太多的话,会造成组件通讯和数据传递的负担。
如何约定组件之间的通讯方式?
在一个组件化的应用中,会存在组件之间的数据传递。
以React为例,如果存在两级嵌套的组件:
这里面可能存在:
这里面,前三种都可以通过该组件的props传递进去,属于对组件的常规用法,第四种,则属于对数据层的利用。
那么,我们如何权衡两种数据通讯方式呢?
一个比较粗糙的办法是,从数据模型的角度去考虑。如果一个组件所要获取的数据模型是比较独立的,不依赖其他业务数据,可以直接去获取,如果跟其他这个数据模型跟其他数据之间存在耦合,比如主从联动关系,由父组件进行分发会比较好。
另外一个着眼点是权衡上下两级组件之间的关系密切程度,如果它们之间的关系很强,对外界来说是一个紧密结合的整体,可以直接在它们之间传递数据,如果关系不强,或者在组件树上距离较远,适合通过第三方转发通信。
从这里我们得出的结论是:
并不是选择了框架,就可以顺利把一个Web应用做出来了,还需要一件很重要的事,那就是:业务架构。组件之间的关系都是需要统筹规划的,这里面有很多技巧,可以参见一些大型桌面程序的架构,从中获取不少经验。
数据通讯层
全组件化还带来另外一个课题,那就是数据层的设计。比如说,我们可能有一个选择城市的列表组件,它的数据来源于服务端的一个查询,为了方便起见,很可能你会选择把查询的调用封装在组件内部,然后这个组件如果被同一个可见区域的多个部分使用,或者是这个查询及其数据结果被同一可见区域的其他组件也调用了,就出现了两个问题:
另外,对于关联数据的更新,也不太便于控制,RESTful之类的服务端接口规范在复杂场景下会显得力不从心。
在数据通信这层,Meteor这样的框架提出了自己的解决思路,跳出传统HTTP的局限,把眼光转向WebSocket这样的东西,并且在前端实现类似数据库的访问接口。
Facebook对此问题提出了更暴力的解决方式,Relay和GraphQL,这两个东西我认为意义是很大的,它解决的不光是自己的痛点,而且是可以用于其他任意的前端组件化体系,对前端组件化这个领域的完善度作出了极其重大的贡献。
5. 其他思考
如何看待“可视化继承”?
在不少组件化框架,包括桌面端的,Web端的,都有“可视化继承”这个概念,比如说,我们有一个List组件用于展现列表数据,然后,又有另外一个需求,在这个列表上显示checkbox,用于多选。在很多组件化框架里,都会存在这样的继承关系:
我觉得有必要探讨一下这里这个extends,是不是一定要用这样的方式来实现一个形态类似原组件的新组件?
在全组件式体系中,继承是不如组合优雅的,以上面这个情况来说,它会在render方法里,重新实现自己的东西,所以,它继承了什么呢,很少很少的东西。
我们可以换种思路,保持组件不变,通过不同的配置项使其相应不同的功能。
模板外置的组件实现方式
在实现一个很基础的UI组件的时候,我们一般都会想要把它搞得既简洁,又强大,但这件事情本身是很难权衡的,针对不同的组件,可能会有不同的策略。
我们在开始实现组件的时候,通常会尽可能考虑需求,然后将其作为默认实现,并且对外提供一些配置项,用于开关这些功能。
还是用列表举例,比如我们有一个列表,可以用于选中,内部结构可能会搞成这样:
然后对外的形式这样:
或者这样:
然后,加需求了,列表有多种形态,一种横着排的,一种竖着排的,一种片状的,每行N个,排满换行,然后这里面还再分,元素是否定宽,还是流式。
那我们就面临着几个选择:
加配置属性,或者增加不同的元素,如TileList,HorizontalList等等。
接着,我们来了对列表项的自定义需求:
……
所以,这个组件变得非常复杂,对外的接口很复杂,内部实现也很复杂,代码更是臃肿不堪。摆在我们面前的有这么一个矛盾:
怎样让我们的组件既强大,又便于使用?
面对此类场景,我想给出一个解决方案,那就是:
为了说明这个理念,我花了大约一个小时,写了这样一个demo,看其中datagrid那段。
其主体实现逻辑是这段:datagrid.js
看看这个代码,再对比所展示出来的这些功能,会不会觉得差异有点大?
奥秘在哪里呢,在于我们给每种场景传入了不同的模板,如下:
这个理念其实并不新鲜,在Adobe Flex的组件框架中,List系列的组件就通过开放自定义itemRenderer的方式,极大提升了可扩展性,并且保持原组件实现的优雅。同理,使用类似的方式,用React也可以这样实现。
但我们这个地方会更加简洁,其原因在于两点:
对于Angular的这个作用域机制,很多人都反感,但我认为,它并不一定就比全部在传递时候赋值的immutable机制差,在业务开发中,组件化固然是有用,但频繁的上下级数据传递可能会让整个系统更加零碎化,数据层的零碎化是非常不利的。
今年大家有了React,黑Angular就格外狠了,我举这个例子也是为了说明,Angular 1.x的设计,除了module是完全的败笔,变更检测机制值得商榷,其他的并无大问题,甚至还存在一些优势。使用某框架的时候,如果熟悉原理并加以合理利用,能够巧妙解决业务上遇到的很多问题。
模板的意义
除了上面提到的,模板还有另外的意义。
我们会发现,在React的体系里,HTML和DOM本身还重要吗?重要性其实是大幅降低了,所以我们会看到ReactNative,ReactCanvas之类的实现,而且,最新版本的React中,把React DOM单独抽取出来了,这意味着,React未来只把DOM作为它的可选视图渲染层之一。
但是我们必须认识到,在Web体系中,HTML和DOM有不可替代的优势,它们是当前Web技术的根基,尽管有缺点,并不代表应当被抛弃,至少是在现在这个时代。
所以,在Web应用这样的体系中,组件的实现技术还是应当尽可能基于DOM来考虑。也正是在这种场景下,模板和绑定技术仍然存在很重要的作用,比如可访问性等等特性,都是别的非DOM体系所缺乏积累的。
此外,模板某种程度上可以视为“组件的字面量形式”,也就是组件的一种序列化形式,如果我们要动态加载组件,使用模板会非常方便,这也就是我上面那个数据表格例子的意义所在。
HTML体系做组件化的不利因素
HTML本身的标签,其实做组件化是有些别扭的,这个原因在哪里呢,两点:
在其他一些体系里并不存在这样的问题,比如WPF,比如Adobe Flex,因为他们没有这样的“历史负担”。
另外一个方面,所谓的组件嵌套,从声明式代码的编写方式来看,就是标签的嵌套。标签嵌套的含义在UI层被赋予了更多潜规则,比如这个代码:
如果Service并非有UI展现的东西,而是像polymer里面的core-ajax那样,或者Adobe Flash体系里的WebService,你可以把它当做Panel实例里面的一个成员变量,然后设置它的属性或者调用方法。但是,对于更普通的情形:
同样的写法,这个含义一样吗?很明显不一样,因为Button也是一个可展示的组件,这时候你默认它是被放置在Panel的展现内部,作为它的可视化子元素的。也就是说,这时候,你不但在逻辑上把两者建立了关联,还要在布局上考虑它们的约束。
如果你的外层元素是一个布局为主的容器,那好说,比如这里的Panel,我们默认它有一块展示区,所有子节点都放在里面以某种方式排版,或者flow,或者float,或者flex,甚至border-layout,东西南北中。
如果外层元素不是一个布局为主的容器,允许它嵌套别的东西,逻辑上就很难理解。它必须约束自己所能允许放置的子元素的类型。比如:
List下面就只能放ListItem类型的东西。
再回头看Web Components
我觉得,在有了类似angular那种自定义元素、属性的方式(具体实现可以改进),或者React那种自定义标签之后,Web Components的使用场景变得很尴尬了。
我们现在看Web Components的作用,主要还是隔离,包括对逻辑和内部展现的隔离。JavaScript逻辑的隔离其实作用不是很大,因为我们用其他办法也能达到相同的效果,但是Shadow DOM和Scoped CSS这两个东西就很耐人寻味了。
比如说,我们现在用Shadow DOM实现了一个东西,然后,在浏览器里面打开查看开关,还是可以看到里面的东西,那如果不纠结它的实现机制的话,跟使用某种组件化框架创建的自定义元素相比,差异是不是就没有那么大了?因为写的时候都只是写一个自定义的元素,运行的时候在内部放了具体实现细节。
至于Scoped CSS,更有意思,因为它实际上带来了对已有的工程方案的挑战。我们思考Web Components普及之后的组件化思路,在样式这块几乎都必然走到一条路上,那就是:样式的inline化,把组件的样式全部内置,否则,组件的独立性无从保证。但我们不要忘了,/deep/和::shadow选择器是用来干什么的?这是允许外部的样式对组件内部的东西作调整,这是一个很无奈的选择,因为确实有这种场景,比如你需要对所有组件设置全局风格之类。另外上次听谁说到父选择器,允许元素控制其上级的样式……真是被震惊了,我理解这种需求,比如某种图片放到一个容器里,不管它放在哪,都希望其父容器背景如何如何,但是,这是对组件化技术的一种挑战……
在实际工程中,样式inline化是有很多缺陷的,比如刚才提到的:theme怎么办?从我近期的一些文章可以看到观点,就是不赞同全组件化,尤其是在上层更倾向于直接使用HTML模板而不是封装过的组件,因为我认为:Web,或者说泛HTML体系,它跟其他任何的客户端展现技术,比如Java Swing,WPF,QT,Adobe Flex之类相比,最本质的不同在于极其强大的CSS,正是因为有它,我们才有可能极尽所能地、简单而优雅地打造不同的用户体验,而不是用各种画布去绘制像素。如果你决定在底层去各种绘制,那确实可以把UI层全组件化,但这个事情也只能在有限范围干,比如移动端,比如游戏,否则代价不堪设想。
面对theme的需求,我们只能通过往动态构建的路上去走,这里面也会有很多要考虑的点。
6. 小结
看到这里,有什么感觉?想要在有一定复杂度的Web应用中全面推行组件化,需要考虑的东西非常多,相当于从农业社会到工业社会的飞跃,我们不能期望一蹴而就,需要通盘考虑。
各类客户端开发技术中有很多值得借鉴的地方,结合Web技术自身的一些特点,可以触类旁通。
The text was updated successfully, but these errors were encountered: