Home > Article > Web Front-end > The road to front-end component framework in 2015_html/css_WEB-ITnose
https://github.com/xufei/blog/issues/19
The componentization of web applications is A very complex topic.
In large-scale software, componentization is a consensus, which on the one hand improves development efficiency and on the other hand reduces maintenance costs. However, in the field of Web front-end, there is no very common component model. Because there is a lack of an implementation method that everyone can agree on, many frameworks/libraries have implemented their own componentization methods.
The front-end circle is most enthusiastic about making wheels. No other field can have such a chaotic and prosperous scene. On the one hand, this shows that creativity in the front-end field is very strong, but on the other hand, it shows that the infrastructure is imperfect.
I once had such an analogy to illustrate the several stages of a certain programming technology and its ecological development:
Then, compare these three Stage, look at the number of people paying attention to these three things, where do you think the Web has developed?
In terms of details, modularization and componentization standards are about to be implemented on a large scale (regardless of whether they are good or bad), and various APIs are generally available. We finally see hope of taking off. Various frameworks will be launched within a few years. There will be a very powerful shuffling. If the drag of old browsers is not taken into account, this shuffling process will be greatly accelerated before the production capacity of the Web front-end can be released.
But we must note that many of these standards that are about to be popularized will bring changes to previous work. Comparing it with the development history of the industrial system, the front-end field is currently at a stage before the invention of the steam engine. Early machinery (such as the machine in "Mulan Ci", which mainly has relatively primitive power and materials) has become popular.
So, from this perspective, many frameworks/libraries will die (specialized in modular AMD and CMD related libraries, some libraries focusing on standardized DOM selectors), and some will have to be Innovation, and some will be less affected (data visualization and other related directions), and can have the opportunity to continue to evolve in their own direction.
For this kind of thing, the key to gaining a broad mass base is how well it caters to future standards. Standards that may have a significant impact on front-end programming include:
The problem of module is easy to understand. JavaScript has a module mechanism in the language for the first time, while Web Components agrees on the way to build component libraries based on the pan-HTML system. Class enhances the programming experience, and observe provides An excellent way to separate data and presentation, promise is currently the most popular asynchronous programming method on the front end.
There are only two things that cannot be bypassed, one is module and the other is Web Components. The former is the basis of modularization, and the latter is the basis of componentization.
The standardization of modules mainly affects some AMD/CMD loading and related management systems. From this perspective, as @afc163 of the seajs team said, both AMD and CMD are outdated.
Modularization is relatively easy to migrate. It is basically just pure logic packaging. Compared with AMD or CMD, the packaging form has changed, but componentization is a more difficult problem.
Web Components provides a recommended way of componentization, specifically:
These types of things will affect various existing front-end frameworks /library had a huge impact:
At this point in time in early 2015, there were three frameworks/libraries leading the fashion in the front-end field, namely Angular, Polymer, and React ( Ranking according to the first letter), in this Zhihu article, what are the popular web development technologies at the end of 2014? Here, I have roughly answered some points, and the answers of several other friends are also worth reading. Regarding the detailed analysis of these three, Hou Zhenyu’s article is very good: Where will the front-end framework go in 2015?
We can see that Polymer has inherent advantages in this regard. Because its core concept is based on Web Components, that is to say, it basically does not consider how to solve current problems and directly focuses on the future as its development direction.
React’s programming model actually does not need to consider Web standards in particular. Its migration cost is not high. It even shields the UI layer implementation due to its implementation mechanism, so everyone can see its use on native. The use of canvas is very different from the DOM-based programming method, so for it, the compatibility issue of Web Components must be solved when encapsulating tags, which must be encapsulated before anyway.
The Angular 1.x version, like most frameworks/libraries of the same era, basically did not consider the compatibility of future standards. However, the 2.0 version after redesign has many trade-offs and changes. It becomes a radical change and suddenly becomes something of the future.
These three things each have their own merits. In the foreseeable few years, they will be three points apart. There may be new frameworks emerging. It is hard to say whether they will be more popular than these.
In addition, Rob Eisenberg, a member of the original Angular 2.0, created his own new generation framework, aurelia, which will become a strong competitor to Angular 2.0.
After looking at some of the existing things, we can roughly discuss some concepts of front-end componentization. Suppose we have some underlying component mechanism, regardless of whether it is native to the browser or a convention implemented by some framework/library, and now we plan to use it to build a large-scale Web application. What should we do?
The so-called componentization, the core meaning is to extract things that are truly valuable for reuse. So what kind of things have reuse value?
For the reusability of the control, basically There is no controversy, because this is a real universal function and relatively independent.
Basic logic functions mainly refer to things that have nothing to do with the interface, such as auxiliary libraries such as underscore, or some pure logic functions such as verification.
The reusability of public styles is also relatively easy to recognize, so things like bootstrap, foundation, and semantic will also be popular. However, they are not pure style libraries, but also have some small logic encapsulation. .
The last piece is business logic. There is a lot of controversy over the reuse of this piece. On the one hand, many people do not agree that business logic also needs to be componentized. On the other hand, how to de-componentize this thing also requires thinking.
In addition to the ones listed above, there are also a large number of business interfaces. Obviously, the reusability of these things is very low, and there is basically no reusability. However, there are still many solutions that use them as "components". ized”, making them “non-reusable components”. Why does this happen?
The essential purpose of componentization is not necessarily for reusability, but to improve maintainability. This is just like an object-oriented language. Java is purer than C because it does not allow exceptions. Even the main function must be written in a certain class. Therefore, Java is a pure object-oriented language, but C is not.
In our case, componentization can also be divided into: full componentization and partial componentization. How to understand the difference between these two things? Someone has asked what is the difference between a js framework and a library. Generally speaking, something with a strong agreement is called a framework, while something with a loose agreement is called a library. Many frameworks have a fully componentized concept. For example, ExtJS, which appeared many years ago, is a fully componentized framework, while jQuery and its plug-in system are partially componentized. So when writing something with ExtJS, no matter what you write, the writing method is almost the same. When using jQuery, most of the parts are original HTML. Wherever you need something different, just call the plug-in in that place to make specializations.
For a web application of a certain scale, "componentizing" everything will bring greater convenience in management. Let me give you an example. When writing code, short codes are obviously more readable than long codes. Therefore, many languages will recommend that "a method should generally not exceed a certain number of lines, and a class should not exceed a certain number of lines." In the Web front-end system, JavaScript is relatively well done. Nowadays, entry-level people rarely write a bunch of js together. CSS, under the guidance of frameworks such as SASS and LESS, has recently been gradually developing towards modularity. Otherwise, it would be very painful to write bootstrap-like CSS directly.
At this time, let’s look at the HTML part. If we don’t consider the use of technologies such as templates, some interface layout codes will be very much to write. For example, some forms need to be layered one after another, which is a lot Simple form elements require about three layers, not to mention things with complex layouts. Especially after the entire system is single-page, the header, footer, various nav or aside of the interface are likely to have a certain degree of complexity. If the code of these things is not segmented, then the HTML of the main interface will definitely be ugly.
We don’t need to use any method to segment it. For example, use some kind of template, use something like include in Angular, or tags in Polymer, React, or directly use native Web Components. In short, we can separate them piece by piece. Taken apart and then contained. From this perspective, these removed things are like components, but from the perspective of reusability, it is likely that each piece of most things is only used in one place, and there is no reusability at all. This was removed purely to make the entire project easier to manage and maintain.
At this time, we will focus on how different frameworks/libraries handle componentization of the UI layer, and find that there are two types, templates and functions.
Template is a very common thing. It expresses the original structure of the interface in the form of HTML strings, and then generates the real interface by substituting data. Some generate target HTML, and some also generate various Automatic binding of events. The former is a static template and the latter is a dynamic template.
There are also some frameworks/libraries that prefer to use functional logic to generate interfaces, the early ExtJS, and now React (it may still use templates internally, and it provides further encapsulation of the component creation interface??jsx) etc. The advantage of this implementation technology is that the programming experience is consistent on different platforms, and the same components can even be encapsulated for each platform. The caller can easily write a code that is available on the Web and different Native platforms. However, this method also has a troublesome aspect, that is, the interface adjustment is relatively cumbersome.
In the article cited earlier in this article by Hou Zhenyu, he raised these questions:
How to make components more reusable? More specifically:
For this reason, a "template copying" solution was also proposed. I have different opinions on this point.
Let’s take a look at how to cut a business interface into components.
There is such a simple scenario: an employee list interface includes two parts, the employee form and the form for filling in employee information. What components exist in this scenario?
Regarding this problem, there are two main tendencies. One is to only encapsulate "controls" and more general things into components, and the other is to componentize the entire application.
For the former method, there is only one component such as data table.
For the latter method, there may be: data table, employee form, and even a larger component such as employee list interface.
These two methods are what we called before "partial componentization" and "full componentization".
We mentioned earlier that full componentization has advantages in management. It can make things at different levels into similar structures. For example, the business scenario just now will probably end up like this when written. :
<Employee-Panel> <Employee-List></Employee-List> <Employee-Form></Employee-Form></Employee-Panel>
For the UI layer, the best componentization method is tagging. For example, in the above code, three tags express the entire interface. But I personally firmly oppose the abuse of labels. It does not mean that encapsulating all kinds of things as much as possible is necessarily good.
The main problems with full tagging are as follows:
First, the cost of semantics is too high. As long as a tag is used, it must be given appropriate semantics, that is, a name. But in actual use, it is probably just to simplify a bunch of html. What should the simplified thing be called? Just naming it takes up countless brain cells. For example, if you are talking about a form managed by employees, does this form have a header, a footer, can it be folded, etc. It is difficult to come up with a name that others will know at a glance, or it will be too long. This is relatively simple, because we are fully componentized, so it is likely to have a more complex interface that combines multiple things. After much thought, you couldn’t give it a name, so you wrote:
<Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right></Panel-With-Department-Panel-On-The-Left-And-Employee-Panel-On-The-Right>
This is crazy... maybe I'm exaggerating, but many times the project scale is large enough that you can't afford such a complicated name. In the end, it's probably impossible to distinguish it from a component with similar functions, because of these The damn components all exist in the same namespace. If it is just included as an interface fragment, there will be no such psychological burden.
For example, this one in Angular:
<div ng-include="'aaa/bbb/ccc.html'"></div>
Don’t give it a name, include it directly, and use the file path to distinguish it. The role of this fragment can be described by its directory structure, which is identified by physical names rather than logical names. The directory hierarchy acts as a good namespace.
Some of the current mainstream MVVM frameworks, such as knockout, angular, avalon, vue, etc., have an "interface template", but this template is not just a template, but can be regarded as a configuration document. A certain interface template describes its relationship with the data model. After it is parsed, it is associated with the data according to the various settings in it, and in turn updates its corresponding view.
A UI without business logic (or a UI with separated business logic) is basically not suitable to be treated as a component, because even if the logic remains unchanged, there are too many possibilities for interface revision. For example, even if a new CSS implementation is changed, from float layout to flex layout, it is possible to reduce the DOM structure by a few layers of divs. Therefore, in the scheme of using templates, the interface layer can only be regarded as a configuration file. It cannot be regarded as a component. If you do this, it will be much easier.
When the troops are marching, they pay attention to "opening roads when encountering mountains and building bridges when encountering water." The key point of this sentence is that roads and bridges are only opened when reaching certain terrains. Most of the time, the business scenarios solved by using MVVM models are flat. , you can walk sideways, there is no need to forcefully build a road. Therefore, looking at the entire solution, the UI layer implementation should be a coexistence of templates and controls. Most of the parts are templates, and a few places are roads and bridges that require separate time.
Second, the configuration is too complicated. There are many things that are not suitable for encapsulation. Not only is the cost of encapsulation high, but the cost of use is also high. Sometimes you will find that most of the calling code is writing various configurations.
Just like the employee form just now, since you don’t distinguish it from the naming of labels, you will definitely add configuration to the component. For example, if you originally wanted to do this:
<EmployeeForm heading="雇员表单"></EmployeeForm>
Then inside the component, determine whether the header is set. If not, it will not be displayed. If it is, it will be displayed. After two days, the product asked if it could bold or change the color of certain words in the heading, and then the coder began to allow this heading attribute to be passed into the HTML. Not long after, you will be surprised to find that someone is using your component, and without telling you, they pass the html of the fold button in the header, and use the selector to add an event to the fold button, and they can collapse it after clicking it. Form...
Then you think, this won’t work. I have to add another configuration to it so that it can easily control the display of the fold button. But it’s too unintuitive to write it like this now, so I use objects. Configuration of the structure:
<EmployeeForm> <Option collapsible="true"> <Heading> <h4><strong>雇员</strong>表单</h4> </Heading> </Option></EmployeeForm>
Then one day, I found that many panels can be folded, so I specially created a foldable panel component, and created an inheritance mechanism from which other ordinary business panels The inheritance has been out of control since then.
What do I mean by giving this example? I want to say that in larger-scale projects, trying to describe all ordinary business interfaces using full labeling and configuration will definitely achieve half the result with twice the effort. , and the larger the scale, the more pitfalls it will have. This is also the biggest problem with systems like ExtJS that over-encapsulate the UI layer.
Now that we have discussed this issue, let’s take a look at another issue: if UI components have business logic, how should it be handled.
For example, the drop-down box for gender selection is a very general function and is ideally suited to be provided as a component. But how to encapsulate it, we have some difficulties. In addition to the interface, this component also has data. Should this data be built into the component? Theoretically, from the perspective of the encapsulation of components, everything should be inside, so I created a component like this:
<GenderSelect></GenderSelect>
这个组件非常美好,只需直接放在任意的界面中,就能显示带有性别数据的下拉框了。性别的数据很自然地是放在组件的实现内部,一个写死的数组中。这个太简单了,我们改一下,改成商品销售的国家下拉框。
表面上看,这个没什么区别,但我们有个要求,本公司商品销售的国家的信息是统一配置的,也就是说,这个数据来源于服务端。这时候,你是不是想把一个http请求封装到这组件里?
这样做也不是不可以,但存在至少两个问题:
第一个问题只是资源的浪费,第二个就是数据的不一致了。曾经在很多系统中,大家都是手动刷新当前页面来解决这问题的,但到了这个时代,人们都是追求体验的,在一个全组件化的解决方案中,不应再出现此类问题。
如何解决这样的问题呢?那就是引入一层Store的概念,每个组件不直接去到服务端请求数据,而是到对应的前端数据缓存中去获取数据,让这个缓存自己去跟服务端保持同步。
所以,在实际做方案的过程中,不管是基于Angular,React,Polymer,最后肯定都做出一层Store了,不然会有很多问题。
我们回顾一下刚才那个下拉框的组件,发现存在几个问题:
所以,从这些角度,会尽量期望在HTML界面层与JavaScript业务逻辑之间,存在一种分离。
这时候,再看看绝大多数界面组件存在什么问题:
有时候我们考虑一下DOM操作的类型,会发现其实是很容易枚举的:
多数界面组件封装的绝大部分内容不过是这些东西的重复。这些东西,其实是可以通过某些配置描述出来的,比如说,某个数组以什么形式渲染成一个select或者无序列表之类,当数组变动,这些东西也跟着变动,这些都应当被自动处理,如果某个方案在现在这个时代还手动操作这些,那真的是一种落伍。
所以我们可以看到,以Angular,Knockout,Vue,Avalon为代表的框架们在这方面做了很多事,尽管理念有所差异,但大方向都非常一致,也就是把大多数命令式的DOM操作过程简化为一些配置。
有了这种方式之后,我们可以追求不同层级的复用:
所以这么一来,我们的复用粒度就非常灵活了。正因为这样,我一直认为Angular这样的框架战略方向是很正确的,虽然有很多战术失误。我们在很多场景下,都是需要这样的高效生产手段的。
我们做组件化这件事,一定是一种长期打算,为了使得当前的很多东西可以作为一种积累,在将来还能继续使用,或者仅仅作较小的修改就能使用,所以必须考虑对未来标准的兼容。主要需要考虑的方面有这几点:
之前有很多人对Angular 2.0的激进变更很不认同,但它的变更很大程度上是对标准的全面迎合。这不仅仅是它的问题,其实是所有前端框架的问题。不面对这些问题,不管现在多么好,将来都是死路一条。这个问题的根源是,这几个已有的规范约束了模块化和元素化的推荐方式,并且,如果要对当前和未来两边做适配的话,基本就没法干了,导致以前的都不得不做一定的迁移。
模块化的迁移成本还比较小,无论是之前AMD还是CMD的,都可以根据一些规则转换过来,但组件化的迁移成本太大了,几乎每种框架都会提出自己的理念,然后有不同的组件化理念。
还是从三个典型的东西来说:Polymer,React,Angular。
Polymer中的组件化,其实就是标签化。这里的标签,并不只是界面元素,甚至逻辑组件也可以这样,比如这个代码:
<my-panel> <core-ajax id="ajax" url="http://url" params="{{formdata}}" method="post"></core-ajax></my-panel>
注意到这里的core-ajax标签,很明显这已经是纯逻辑的了,在大多数前端框架或者库中,调用ajax肯定不是这样的,但在浏览器端这么干也不是它独创,比如flash里面的WebService,比如早期IE中基于htc实现的webservice.htc等等,都是这么干的。在Polymer中,这类东西称为非可见元素(non-visual-element)。
React的组件化,跟Polymer略有不同,它的界面部分是标签化,但如果有单纯的逻辑,还是纯JavaScript模块。
既然大家的实现方式都那么不一致,那我们怎么搞出尽量可复用的组件呢?问题到最后还是要绕到Web Components上。
在Web Components与前端组件化框架的关系上,我觉得是这么个样子:
各种前端组件化框架应当尽可能以Web Components为基石,它致力于组织这些Components与数据模型之间的关系,而不去关注某个具体Component的内部实现,比如说,一个列表组件,它究竟内部使用什么实现,组件化框架其实是不必关心的,它只应当关注这个组件的数据存取接口。
然后,这些组件化框架再去根据自己的理念,进一步对这些标准Web Components进行封装。换句话说,业务开发人员使用某个组件的时候,他是应当感知不到这个组件内部究竟使用了Web Components,还是直接使用传统方式。(这一点有些理想化,可能并不是那么容易做到,因为我们还要管理像import之类的事情)。
目前来看,前端框架/库仍然处于混战期,可比中国历史上的春秋战国,百家齐放,作为跟随者来说,这是很痛苦的,因为无所适从,很可能你作为一个企业的前端架构师或者技术经理,需要做一些选型工作,但选哪个能保证几年后不被淘汰呢?基本没有。
虽然我们不知道将来什么框架会流行,但我们可以从一些细节方面去关注,某个具体的方面,将来会有什么,也可以了解一下在某个具体领域存在什么样的方案。一个完整的框架方案,无非是以下多个方面的综合。
这块还是不讲了,支付宝seajs还有百度ecomfe这两个团队的人应该都能比我讲得好得多。
本文前面讨论过一些,也不深入了。
我们知道,现代框架的一个特点是自动化,也就是把原有的一些手动操作提取。在前端编程中,最常见的代码是在干什么呢?读写数据和操作DOM。不少现代的框架/库都对这方面作了处理,比如说通过某种配置的方式,由框架自动添加一些关联,当数据变更的时候,把DOM进行相应修改,又比如,当DOM发生变动的时候,也更新对应的数据。
这个关联过程可能会用到几种技术。首先我们看怎么知道数据在变化,这里面有三种途径:
一、存取器的封装。这个的意思也就是对数据进行一层包装,比如:
var data = { name: "aaa", getName: function() { return this.name; }, setName: function(value) { this.name = value; }}
这样,不允许用户直接调用data.name,而是调用对应的两个函数。Backbone就是通过这样的机制实现数据变动观测的,这种方式适用于几乎所有浏览器,缺点就是比较麻烦,要对每个数据进行包装。
这个机制在稍微新一点的浏览器中,也有另外一种实现方式,那就是defineProperty相关的一些方法,使用更优雅的存取器,这样外界可以不用调用函数,而是直接用data.name这样进行属性的读写。
The domestic framework avalon uses this mechanism. There is no defineProperty in the lower version of IE, but in the lower version of IE there is not only JavaScript, but also VBScript, which has accessors, so he cleverly used VBS to make such a Compatible packaging.
Another trouble with the accessor-based mechanism is that every time an attribute is dynamically added, a corresponding accessor must be added, otherwise the changes to this attribute cannot be obtained.
2. Dirty detection.
Frameworks represented by Angular 1.x use dirty detection to learn about data changes. The general principle of this mechanism is:
Save the old and new values of the data, whenever there is some DOM or network , timer and other events are generated. Use the data after this event to compare with the previously saved data. If they are the same, the interface refresh will not be triggered, otherwise it will be refreshed.
The idea of this method is to control all sources that may cause data changes (that is, various events). After they may operate on the data, determine whether there are changes in the old and new data, ignoring all intermediate changes, and That is to say, if you modify a certain data arbitrarily many times in the same event, but finally change it back, the framework will think that you have done nothing, and will not notify the interface to refresh.
It is undeniable that the efficiency of dirty detection is relatively low, mainly because the impact of data changes cannot be accurately known. Therefore, when the amount of data is larger, the waste is more serious, and some manual optimization is required. For example, a large array generates a list on the interface. When an item is selected, the color changes. Under this mechanism, every time the data status of this item is changed, all items need to be compared with the original ones. Then, all items need to be compared again to find that there are no related changes, and then the interface can be refreshed accordingly.
3. Observation mechanism.
In ES7, the observe method of Object is introduced, which can be used to monitor changes in objects or arrays.
This is the most reasonable observation plan so far. This mechanism is very precise and efficient. For example, the company commander tells the soldiers to observe what is going on in the bunker opposite. This meaning is very complicated, what does it include?
The so-called observation mechanism refers to changes in the properties of the observed object, addition, removal, position changes of array elements, etc. Let's first think about the binding of interface and data. This should be an external observation. You are the data and I am the interface. You nod and I smile. You stretch out your hand and I hit. This kind of binding should be a loose relationship. It should not destroy some original things because of the binding, so it is obviously more reasonable.
In addition to data changes that can be observed, DOM can also be observed. However, most current two-way synchronization frameworks synchronize DOM changes to data through events. For example, if a text box is bound to the properties of an object, it is very likely that the framework internally monitors the keyboard input, paste and other related events of this text box, and then takes the value and writes it into the object.
Doing this can solve most problems, but if you directly myInput.value="111", this change will not be available. This is not a big problem, because in a two-way binding framework, something that is both monitored and assigned manually is quite strange in itself. However, some frameworks will try to override the value assignment from the prototype of HTMLInputELement. Try to make this Things are also included in the jurisdiction of the framework.
Another question is that we only consider specific attributes of specific elements and can obtain changes through events. How to obtain DOM changes in a broader sense? For example, changes to general attributes, or even the addition and deletion of child nodes?
DOM4 introduces MutationObserver to implement the observation of such changes. Whether such a complex observation and synchronization mechanism is needed between the DOM and data has not yet been determined, but under the general trend of gradual automation of the entire front-end development, this is also something worth trying.
Complex correlation monitoring can easily lead to unexpected results:
In short, I don’t know where the impact will be in the end. Who let Qiu Chuji pass by Niujia Village?
Therefore, the related monitoring of changes is a very complex system, especially when a closed loop occurs. Building such a whole set of things requires extremely precise design, otherwise people who are familiar with the whole set of mechanisms will fall over with just a slight push using a specific scenario. Although Master Lingzhi was excellent in martial arts, he encountered Ouyang Feng, Zhou Botong, and Huang Yaoshi one after another, and all of them were immediately grabbed at the back of their necks, which is roughly what it meant.
Polymer implements an observe-js, which is used to observe changes in arrays, objects and paths. If you are interested, you can pay attention.
在有些框架,比如aurelia中,是混合使用了存取器和观察模式,把存取器作为观察模式的降级方案,在浏览器不支持observe的情况下使用。值得一提的是,在脏检测方式中,变更是合并后批量提交的,这一点常常被另外两种方案的使用者忽视。其实,即使用另外两种方式,也还是需要一个合并与批量提交过程。
怎么理解这个事情呢?数据的绑定,最终都是要体现到界面上的,对于界面来说,其实只关注你每一次操作所带来的数据变更的始终,并不需要关心中间过程。比如说,你写了这么一个循环,放在某个按钮的点击中:
for (var i=0; i<10000; i++) { obj.a += 1;}
界面有一个东西绑定到这个a,对框架来说,绝对不应当把中间过程直接应用到界面上,以刚才这个例子来说,合理的情况只应当存在一次对界面DOM的赋值,这个值就是对obj.a进行了10000次赋值之后的值。尽管用存取器或者观察模式,发现了对obj上a属性的这10000次赋值过程,这些赋值还是都必须被舍弃,否则就是很可怕的浪费。
React使用虚拟DOM来减少中间的DOM操作浪费,本质跟这个是一样的,界面只应当响应逻辑变更的结束状态,不应当响应中间状态。这样,如果有一个ul,其中的li绑定到一个1000元素的数组,当首次把这个数组绑定到这个ul上的时候,框架内部也是可以优化成一次DOM写入的,类似之前常用的那种DocumentFragment,或者是innerHTML一次写入整个字符串。在这个方面,所有优化良好的框架,内部实现机制都应当类似,在这种方案下,是否使用虚拟DOM,对性能的影响都是很小的。
Immutable Data是函数式编程中的一个概念,在前端组件化框架中能起到一些很独特的作用。
它的大致理念是,任何一种赋值,都应当被转化成复制,不存在指向同一个地方的引用。比如说:
var a = 1;var b = a;b = 2;console.log(a==b);
这个我们都知道,b跟a的内存地址是不一致的,简单类型的赋值会进行复制,所以a跟b不相等。但是:
var a = { counter : 1};var b = a;b.counter++;console.log(a.counter==b.counter);
这时候因为a和b指向相同的内存地址,所以只要修改了b的counter,a里面的counter也会跟着变。
Immutable Data的理念是,我能不能在这种赋值情况下,直接把原来的a完全复制一份给b,然后以后大家各自变各自的,互相不影响。光凭这么一句话,看不出它的用处,看例子:
对于全组件化的体系,不可避免会出现很多嵌套的组件。嵌套组件是一个很棘手的问题,在很多时候,是不太好处理的。嵌套组件所存在的问题主要在于生命周期的管理和数据的共享,很多已有方案的上下级组件之间都是存在数据共享的,但如果内外层存在共享数据,那么就会破坏组件的独立性,比如下面的一个列表控件:
<my-list list-data="{arr}"> <my-listitem></my-listitem> <my-listitem></my-listitem> <my-listitem></my-listitem></my-list>
我们在赋值的时候,一般是在外层整体赋值一个类似数组的数据,而不是自己挨个在每个列表项上赋值,不然就很麻烦。但是如果内外层持有相同的引用,对组件的封装性很不利。
比如在刚才这个例子里,假设数据源如下:
var arr = [ {name: "Item1"}, {name: "Item2"}, {name: "Item3"}];
通过类似这样的方式赋值给界面组件,并且由它在内部给每个子组件分别进行数据项的赋值:
list.data = arr;
赋值之后会有怎样的结果呢?
console.log(list.data == arr);console.log(listitem0.data == arr[0]);console.log(listitem1.data == arr[1]);console.log(listitem2.data == arr[2]);
这种方案里面,后面那几个log输出的结果都会是true,意思就是内层组件与外层共享数据,一旦内层组件对数据进行改变,外层中的也就改变了,这明显是违背组件的封装性的。
所以,有一些方案会引入Immutable Data的概念。在这些方案里,内外层组件的数据是不共享的,它们的引用不同,每个组件实际上是持有了自己的数据,然后引入了自动的赋值机制。
这时候再看看刚才那个例子,就会发现两层的职责很清晰:
所以我们再看这个过程,真是非常清晰明了,而且内外层各司其职,互不干涉。这是非常有利于我们打造一个全组件化的大型Web应用的。各级组件之间存在比较松散的联系,而每个组件的内部则是封闭的,这正是我们所需要的结果。
说到这里,需要再提一个容易混淆的东西,比如下面这个例子:
<outer-component> <inner-component></inner-component></outer-component>
如果我们为了给inner-component做一些样式定位之类的事情,很可能在内外层组件之间再加一些额外的布局元素,比如变成这样:
<outer-component> <div> <inner-component></inner-component> </div></outer-component>
这里中间多了一级div,也可能是若干级元素。如果有用过Angular 1.x的,可能会知道,假如这里面硬造一级作用域,搞个ng-if之类,就可能存在多级作用域的赋值问题。在上面这个例子里,如果在最外层赋值,数据就会是outer -> div -> inner这样,那么,从框架设计的角度,这两次赋值都应当是immutable的吗?
不是,第一次赋值是非immutable,第二次才需要是,immutable赋值应当仅存在于组件边界上,在组件内部不是特别有必要使用。刚才的例子里,依附于div的那层变量应当还是跟outer组件在同一层面,都属于outer组件的人民内部矛盾。
这里是facebook实现的immutable-js库
前端一般都习惯于用事件的方式处理异步,但很多时候纯逻辑的“串行化”场景下,这种方式会让逻辑很难阅读。在新的ES规范里,也有yield为代表的各种原生异步处理方案,但是这些方案仍然有很大的理解障碍,流行度有限,很大程度上会一直停留在基础较好的开发人员手中。尤其是在浏览器端,它的受众应该会比node里面还要狭窄。
前端里面,处理连续异步消息的最能被广泛接受的方案是promise,我这里并不讨论它的原理,也不讨论它在业务中的使用,而是要提一下它在组件化框架内部所能起到的作用。
现在已经没有哪个前端组件化框架可以不考虑异步加载问题了,因为,在前端这个领域,加载就是一个绕不过去的坎,必须有了加载,才能有执行过程。每个组件化框架都不能阻止自己的使用者规模膨胀,因此也应当在框架层面提出解决方案。
我们可能会动态配置路由,也可能在动态加载的路由中又引入新的组件,如何控制这些东西的生命周期,值得仔细斟酌,如果在框架层面全异步化,对于编程体验的一致性是有好处的。将各类接口都promise化,能够在可维护性和可扩展性上提供较多便利。
我们之前可能熟知XMLHTTP这样的通信接口,这个东西虽然被广为使用,但是在优雅性等方面,存在一些问题,所以最近出来了替代方案,那就是fetch。
细节可以参见月影翻译的这篇【翻译】这个API很“迷人”??(新的Fetch API)
在不支持的浏览器上,也有github实现的一个polyfill,虽然不全,但可以凑合用window.fetch polyfill
大家可以看到,fetch的接口就是基于promise的,这应当是前端开发人员最容易接受的方案了。
这个东西的意思是前后端同构的JavaScript,也就是说,比如一块界面,可以选择在前端渲染,也可以选择在后端渲染,值得关注,可以解决像seo之类的问题,但现在还不能处理很复杂的状况,持续关注吧。
Thank you very much for seeing this. The above is a summary of my thoughts in the past year. From the perspective of technology selection, it will be very painful for people who make large-scale web applications, because this is an era of sluggish development, and all existing frameworks/libraries have defects to varying degrees. When you look to the future, you find that they all need to be abandoned or transformed. The most painful thing for people is knowing that many things are bad, but they have to choose one of them to use. @yanqing discussed this issue with @inchzhi @Tiye, and they believe that it is difficult to select technology at this stage, so it is better to wait for a while. I completely agree with their point of view.
It is difficult to choose, but from a learning perspective, it is really a good era. There are so many things to learn. I try hard to see things that may be worth seeing on the road every day, but still I can’t finish it, so I can only try to keep up with the times.
The following paragraph is for your encouragement:
It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us , we were all going direct to Heaven, we were all going direct the other way--in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.