译者前言
这是一篇来自 StackOverflow 的问答,提问的人认为 React 相比 WebComponents
有一些“先天不足”之处,列举如下:
- 原生浏览器支持
- 原生语法支持(意即不把样式和结构混杂在 JS 中)
- 使用 Shadow DOM 封装样式
- 数据的双向绑定
这些都是确然的。不过他还是希望听听大家的看法,于是就有了这篇精彩的回答。
需要说明的是,这篇回答并没有讨论太多的技术细节,但却广泛全面的谈及了许多
观念性的东西,并且补充了许多进阶的阅读资料。可以说“含金量”一般但是“信
息量”很大,特别能够帮助很多进阶者开拓视野,增长知识,转换观念。
另外,答案里所引用的几个演讲视频都非常精彩!遗憾地是访问受限——大家懂的。
然而我始终觉得这年头不会“科学上网”的话就不要搞开发了,所以在此还是极力
推荐你好好看一看。
我觉得会对一些人有所助益的,特别是提问题总是提不到点子上的朋友(笑),特
此翻译并记录于下:
原题:Pros and Cons of Facebook's React vs. Web Components
(Polymer)
Update: this answer seems to be pretty popular so I took some time to
clean it up a little bit, add some new info and clarify some things that
I thought was not clear enough.
更新:看来我的答案蛮受青睐的,于是我花了点时间略做整理,添加了新的内
容并把之前没有讲清楚的地方也做了进一步的解释。
Most of your concerns are really a matter of opinion and personal
preference but I'll try to answer as objectively as I can:
你所提及的大部分实际上都属于个人偏好,不过我还是尽我所能客观的回答如下:
Native vs. Compiled
Write JavaScript in vanilla JavaScript, write CSS in CSS, write HTML
in HTML.
原生语言对决预编译语言
用纯 JavaScript 来写 JavaScript,用 CSS 来写 CSS ,用 HTML 来写 HTML。
Back in the day there were hot debates whether one should write native
Assembly by hand or use a higher level language like C to make the
compiler generate Assembly code for you. Even before that people refused
to trust assemblers and preferred to write native machine code by hand
(and I'm not joking).
回顾往昔,人们也曾争论到底是直接手写汇编还是用像 C 这样的高阶语言利用编译
器来生成汇编代码。甚至在更早以前人们拒绝信任汇编程序而宁愿手工编写原生机
器代码(我没开玩笑)。
Meanwhile, today there are a lot of people who write HTML in Haml or
Jade, CSS in Sass or Less and JavaScript in CoffeeScript or TypeScript.
It's there. It works. Some people prefer it, some don't.
时至今日,许多人都使用 Haml 或 Jade 编写 HTML,用 Sass 或 Less 编写 CSS
,用 CoffeeScript 或 TypeScript 编写 JavaScript。存在即真理,能抓老鼠的
就是好猫。有人爱,当然也会有人恨。
The point is that there is nothing fundamentally wrong in not writing
JavaScript in vanilla JavaScript, CSS in CSS and HTML in HTML. It's
really a matter of preference.
重点是在本质上使用预编译语言没有什么错误。这纯粹是喜好的问题。
Internal vs. External DSLs
Style encapsulation using Shadow DOM React instead has this, which
requires writing CSS in JavaScript. Not pretty.
内部与外部 DSLs 的对决
使用 Shadow DOM 封装样式,而 React 则使用这个解决方案
,需要把 CSS 写进 JavaScript 里。不优雅。
Pretty or not, it is certainly expressive. JavaScript is a very powerful
language, much more powerful than CSS (even including any of CSS
preprocessors). It kind of depends on whether you prefer internal or
external DSLs for those sorts of things. Again, a matter of preference.
优雅与否,确有其意义。JavaScript 是一门非常强大的语言,远甚于 CSS(算上
任何一种 CSS 预处理语言也是如此)。这个问题视乎你喜欢内部还是外部 DSLs
。还是的,偏好问题。
(Note: I was talking about the inline styles in React that was
referenced in the original question.)
(注意:我指的是原题中引用的在 React 中内联样式的写法。)
Types of DSLs - explanation
Update: Reading my answer some time after writing it I think that I need
to explain what I mean here. DSL is a domain-specific language and it
can be either internal (using syntax of the host language like
JavaScript - like for example React without JSX, or like the inline
styles in React mentioned above) or it can be external (using
a different syntax than the host language
- like in this example would be inlining CSS (an external DLS) inside
JavaScript).
DSLs 的种类 - 解释
更新:回答完以后又重读一遍我的答案,我觉得有必要进一步解释一下。DSL
指的是领域特定语言,它可以是内部的(使用宿主语言的语法,好比
JavaScript - 之于不使用 JSX 的 React,或者之前提到的在 React 中内联样式
的写法),也可以是外部的(使用和宿主语言不同的语法 - 比如本例中在
JavaScript 内部编写的内联 CSS (属于外部 DSLs))。
译注:此处作者的回答似乎有些自相矛盾,因为在 React 中写内联 CSS 和在
JavaScript 中写内联 CSS 应该是一回事(都属于使用外部 DSLs),毕竟
React 本身就是使用宿主语言(也就是 JavaScript - 这里属于内部 DSLs)来
编写的呀。
It can be confusing because some literature uses different terms than
"internal" and "external" to describe those kinds of DLSs. Sometimes
"embedded" is used instead of "internal" but the word "embedded" can
mean different things - for example Lua is described as "Lua: an
extensible embedded language" where embedded has nothing to do with
embedded (internal) DSL (in which sense it is quite the opposite - an
external DSL) but it means that it is embedded in the same sense
that, say, SQLite is an embedded database. There is even eLua where "e"
stands for "embedded" in a third sense - that it is meant for embedded
systems! That's why I don't like using the term "embedded DSL" because
things like eLua can be "DSLs" that are "embedded" in two different
senses while not being an "embedded DSL" at all!
某些资料里用不同的术语来描述上述的 DSLs 种类,这常常会令人混淆。有时候
“嵌入式”会用来指代“内部的”但是“嵌入式”也可以是别的意思——比方说 Lua 可
被描述为“Lua:一种可扩展的嵌入式语言”,在这里“嵌入式”和“内部 DSLs” 一
毛钱关系都没有(更甚者完全相反,嵌入式是一种外部 DSL),它说的是和
“SQLite 是一种嵌入式数据库”一个意思。甚至还有一种 eLua,它的 “e” 代
表另外一种“嵌入式”——嵌入式系统!这就是为什么我不喜欢使用术语“嵌入式 DSL”
的原因,像 eLua 可以是拥有两种“嵌入式”含义的 DSLs,但却和“嵌入式 DSLs”
没有任何关联。
To make things worse some projects introduce even more confusion to the
mix. Eg. Flatiron templates are describes as "DSL-free" while in fact it
is just a perfect example of an internal DSL with syntax like:
map.where('href').is('/').insert('newurl');
更糟的是一些项目引入的概念让这潭浊水变得更混了。例如:Flatiron 模板自称
“无 DSL”而实际上却是一个完美的内部 DSL 的例子,其语法如下:
map.where('href').is('/').insert('newurl');
That having been said, when I wrote "JavaScript is a very powerful
language, much more powerful than CSS (even including any of CSS
preprocessors). It kind of depends on whether you prefer internal or
external DSLs for those sorts of things. Again, a matter of preference."
I was talking about those two scenarios:
如前所述,当我写道“JavaScript 是一门非常强大的语言,远甚于 CSS(算上
任何一种 CSS 预处理语言也是如此)。这个问题视乎你喜欢内部还是外部 DSLs
。还是的,偏好问题。”我所指的是如下两个场景:
一:
/** @jsx React.DOM */
var colored = { color: myColor };
React.renderComponent(Hello World!, mountNode);
二:
// SASS:
.colored {
color: $my-color;
}
// HTML:
Hello World!
The first example uses what was
described in the question as: "writing CSS in JavaScript. Not pretty."
The second example uses Sass. While I agree that using JavaScript to
write CSS may not be pretty (for some definitions of "pretty") but there
is one advantage of doing it.
第一个例子如同原题所述:“在 JavaScript 里写 CSS。不优雅。”第二个例子则
使用 Sass。尽管我也同意使用 JavaScript 写 CSS 可能不太优雅(某种意义上的
“优雅”)然而这么做还是有一个优点的。
I can have variables and functions in Sass but are they lexically scoped
or dynamically scoped? Are they statically or dynamically typed?
Strongly or weakly? What about the numeric types? Type coersion? Which
values are truthy and which are falsy? Can I have higher-order
functions? Recursion? Tail calls? Lexical closures? Are they evaluated
in normal order or applicative order? Is there lazy or eager exaluation?
Are arguments to functions passed by value or by reference? Are they
mutable? Immutable? Persistent? What about objects? Classes? Prototypes?
Inheritance?
我可以在 Sass 里使用变量和函数,可它们是词法作用域还是动态作用域的存在呢
?它们是静态类型还是动态类型?数字类型如何?类型强制转换又如何?哪些是直
真哪些又是假值?我能用高阶函数吗?递归呢?尾调用?词法闭包?求值是顺序还
是按应用序?是否可以使用惰性求值或主动求值?函数是按值传参还是按引用传参
?它们是可变的?不可变的?持久化的?还有对象呢?类呢?原型呢?继承呢?
Those are not trivial questions and yet I have to know answers to them
if I want to understand Sass or Less code. I already know those
answers for JavaScript so it means that I already understand every
internal DSL (like the inline styles in React) on those very levels so
if I use React then I have to know only one set of answers to those
(and many similar) questions, while when I use for eg. Sass and
Handlebars then I have to know three sets of those answers and
understand their implications.
这些并非无碍痛痒的问题,如果我想要理解 Sass 或 Less 的代码就必须知道上述
疑问的答案。我已经知道这些问题(以及许多类似问题)在 JavaScript 里的答案
了,也就意味着我已经理解每一种内部 DSLs 了,然而当我使用比方说 Sass
和 Handlebars 那么我得知道三套答案(包含 JS)同时还得理解它们的实现。
It's not to say that one way or the other is always better but every
time you introduce another language to the mix then you pay some price
that may not be as obvious at a first glance, and this price is
complexity.
这倒不是说某一种方式肯定会更好,但是每一次你引入另外一种语言就不得不花代
价(去学习和理解它),这种代价可能在一开始不是那么明显并且会很复杂。
I hope I clarified what I originally meant a little bit.
希望我把我的本意解释的比较清楚了。
Data binding
Two-way binding
数据绑定
双向绑定
This is a really interesting subject and in fact also a matter of
preference. Two-way is not always better than one-way. It's a question
of how do you want to model mutable state in your application. I always
viewed two-way bindings as an idea somewhat contrary to the principles
of functional programming but functional programming is not the only
paradigm that works, some people prefer this kind of behavior and both
approaches seem to work pretty well in practice. If you're interested in
the details of the design decisions related to the modeling of the state
in React then watch the talk by Pete Hunt (linked to in the question)
and the talk by Tom Occhino and Jordan Walke who explain it very well
in my opinion.
这是很有意思的话题而且实际上还是偏好问题。双向绑定不总是比单向绑定好。这
取决于你想要怎样来模型化应用的可变状态(也就是如何通过对象来描述可变状态
,或者说为可变状态建模)。我一直将双向绑定视作对立于函数式编程的一种思路
,不过函数式编程也不是唯一可用的范式,一些人喜欢这样的方式并且在实际中也
能很好地工作。如果你有兴趣了解在 React 中关于状态模型化背后的设计决策细
节的话,推荐你观看 Pete Hunt 的演讲 以及 Tom
Occhina 和 Jordan Walke 的演讲,他俩把我的观点解释的非常精
彩。
Update: see also another talk by Pete Hunt: Be predictable, not correct:
functional DOM programming.
更新:同时看看 Pete Hunt 的另外一个演讲:Be predictable, not
correct: functional DOM programming。
Native vs. VM
Native browser support (read "guaranteed to be faster")
原生对决 VM(虚拟机)
浏览器原生支持(暗示“确保更快”)
Now finally something that is not a matter of opinion.
终于到不是偏好问题的部分了。
Actually here it is exactly the other way around. Of course "native"
code can be written in C++ but what do you think the JavaScript engines
are written in?
好有一比亦具异曲同工之妙,“原生”代码可以用 C++ 来写,不过你觉得
JavaScript 引擎是用什么写的呢?
译注:此段不好直译。作者的本意是浏览器的原生支持是用可以用 C++ 搞定,但是虚
拟机(JavaScript 的解释器,比如 V8)也是用 C++ 写的),所以不能因为虚
拟机不属于浏览器原生支持的部分就认为它没有原生支持“快”。
As a matter of fact the JavaScript engines are truly amazing in the
optimizations that they use today - and not only V8 any more, also
SpiderMonkey and even Chakra shines these days. And keep in mind that
with JIT compilers the code is not only as native as it can possibly be
but there are also run time optimization opportunities that are simply
impossible to do in any statically compiled code.
实际上今天的 JavaScript 引擎的性能优化做得异常出色——而且不仅仅是 V8,
还包括 SpiderMonkey 甚或 Chakra。要明白有了 JIT 编译器,代码(执行)不但
是无限逼近于原生而且还会有静态编译代码所不具备的运行时优化。
When people think that JavaScript is slow, they usually mean JavaScript
that accesses the DOM. The DOM is slow. It is native, written in C++ and
yet it is slow as hell because of the complexity that it has to
implement.
人们所谈论的“JavaScript 很慢”,实际上指的是访问 DOM 的 时候,DOM 很慢
才是真的。即便它是原生的,使用 C++ 编写,却还是慢到令人发指,这是因为它
的实现非常复杂。
Open your console and write:
打开控制台(浏览器开发者工具中的)然后执行:
console.dir(document.createElement('div'));
and see how many properties an empty div element that is not even
attached to the DOM has to implement. These are only the first level
properties that are "own properties" ie. not inherited from the
prototype chain:
看看一个空的 div
元素(并且还没有插入到 DOM)有多少属性要实现吧。这还
只是第一层的也就是“自有属性”,没包括自原型链继承而来的。
align, onwaiting, onvolumechange, ontimeupdate, onsuspend, onsubmit,
onstalled, onshow, onselect, onseeking, onseeked, onscroll, onresize,
onreset, onratechange, onprogress, onplaying, onplay, onpause,
onmousewheel, onmouseup, onmouseover, onmouseout, onmousemove,
onmouseleave, onmouseenter, onmousedown, onloadstart,
onloadedmetadata, onloadeddata, onload, onkeyup, onkeypress,
onkeydown, oninvalid, oninput, onfocus, onerror, onended, onemptied,
ondurationchange, ondrop, ondragstart, ondragover, ondragleave,
ondragenter, ondragend, ondrag, ondblclick, oncuechange,
oncontextmenu, onclose, onclick, onchange, oncanplaythrough,
oncanplay, oncancel, onblur, onabort, spellcheck, isContentEditable,
contentEditable, outerText, innerText, accessKey, hidden,
webkitdropzone, draggable, tabIndex, dir, translate, lang, title,
childElementCount, lastElementChild, firstElementChild, children,
nextElementSibling, previousElementSibling, onwheel,
onwebkitfullscreenerror, onwebkitfullscreenchange, onselectstart,
onsearch, onpaste, oncut, oncopy, onbeforepaste, onbeforecut,
onbeforecopy, webkitShadowRoot, dataset, classList, className,
outerHTML, innerHTML, scrollHeight, scrollWidth, scrollTop,
scrollLeft, clientHeight, clientWidth, clientTop, clientLeft,
offsetParent, offsetHeight, offsetWidth, offsetTop, offsetLeft,
localName, prefix, namespaceURI, id, style, attributes, tagName,
parentElement, textContent, baseURI, ownerDocument, nextSibling,
previousSibling, lastChild, firstChild, childNodes, parentNode,
nodeType, nodeValue, nodeName
Many of them are actually nested objects - to see second level (own)
properties of an empty native div in your browser, see this fiddle.
它们中有许多实际上还是层叠的对象——要查看浏览器里一个空的原生 div
的
第二层(自有的)属性,请见这个 fiddle。
I mean seriously, onvolumechange property on every single div node? Is
it a mistake? Nope, it's just a legacy DOM Level 0 traditional event
model version of one of the event handlers "that must be supported by
all HTML elements, as both content attributes and IDL attributes"
[emphasis added] in Section 6.1.6.2 of the HTML spec by W3C - no way
around it.
说真的,有必要让每一个 div 节点都包括 onvolumechange
属性吗?这是个错
误?不是的,这只是老旧的 DOM Level 0 版本中传统事件模型里的一个事件回调
,它“必须为所有的 HTML 元素所支持,既作为内容属性也作为 IDL 属性”(重
点强调),定义于 W3C 的 HTML 规范中的 Section 6.1.6.2,无可
回避。
Meanwhile, these are the first level properties of a fake-DOM div in
React:
与之对应的,以下是 React 中伪 DOM div 元素的第一层属性列表:
props, _owner, _lifeCycleState, _pendingProps, _pendingCallbacks,
_pendingOwner
Quite a difference, isn't it? In fact this is the entire object
serialized to JSON (LIVE DEMO), because hey you actually can serialize
it to JSON as it doesn't contain any circular references - something
unthinkable in the world of native DOM (where it would just throw an
exception):
大不一样,不是吗?实际上这是序列化为 JSON 的一整个对象(演示
),因此你完全可以把它序列化成 JSON 因为它不会包含循环引用,
这在原生 DOM 的世界里是不可想象的(那只会抛出异常)。
{
"props": {},
"_owner": null,
"_lifeCycleState": "UNMOUNTED",
"_pendingProps": null,
"_pendingCallbacks": null,
"_pendingOwner": null
}
This is pretty much the main reason why React can be faster
than the native browser DOM - because it doesn't have to implement
this mess.
这就是 React 比原生浏览器 DOM 快的主要原因——它不用实现那些有的没的
。
See this presentation by Steven Luscher to see what is faster: native
DOM written in C++ or a fake DOM written entirely in JavaScript. It's
a very fair and entertaining presentation.
看看这个 Steven Luscher 的演讲 吧,看看哪个更快:用 C++
写的原生 DOM 还是用 JavaScript 写的伪 DOM。这是一个非常公平且有很欢乐的
演讲。
To sum it up: features from Web Components like templates, data binding
or custom elements will have a lot of advantages over React but until
the document object model itself gets significantly simplified then
performance will not be one of them.
总结陈词:WebComponenets 的特点如模板,数据绑定或自定义元素终会有很多胜
于 React 的优势,然而性能绝非其一除非文档对象模型能够得到显著地简化。