本文共 4994 字,大约阅读时间需要 16 分钟。
本系列的上一篇文章《》列举了前端开发中的种种痛点。本篇文章中将详细探讨其中“复用性”痛点。我们将用原生 DHTML API 、 ReactJS 和 Binding.scala 实现同一个需要复用的标签编辑器,然后比较三个标签编辑器哪个实现难度更低,哪个更好用。
\\在InfoQ的许多文章都有标签。比如本文的标签是“binding.scala”、“data-binding”、“scala.js”。
\\假如你要开发一个博客系统,你也希望博客作者可以添加标签。所以你可能会提供标签编辑器供博客作者使用。
\\如图所示,标签编辑器在视觉上分为两行。
\\ \\第一行展示已经添加的所有标签,每个标签旁边有个“x”按钮可以删除标签。第二行是一个文本框和一个“Add”按钮可以把文本框的内容添加为新标签。每次点击“Add”按钮时,标签编辑器应该检查标签是否已经添加过,以免重复添加标签。而在成功添加标签后,还应清空文本框,以便用户输入新的标签。
\\除了用户界面以外,标签编辑器还应该提供 API 。标签编辑器所在的页面可以用 API 填入初始标签,也可以调用 API 随时增删查改标签。如果用户增删了标签,应该有某种机制通知页面的其他部分。
\\首先,我试着不用任何前端框架,直接调用原生的 DHTML API 来实现标签编辑器,代码如下:
\\\\u0026lt;!DOCTYPE html\u0026gt;\\u0026lt;html\u0026gt;\\u0026lt;head\u0026gt;\ \u0026lt;script\u0026gt;\ var tags = [];\\ function hasTag(tag) {\ for (var i = 0; i \u0026lt; tags.length; i++) {\ if (tags[i].tag == tag) {\ return true;\ }\ }\ return false;\ }\\ function removeTag(tag) {\ for (var i = 0; i \u0026lt; tags.length; i++) {\ if (tags[i].tag == tag) {\ document.getElementById(\"tags-parent\").removeChild(tags[i].element);\ tags.splice(i, 1);\ return;\ }\ }\ }\\ function addTag(tag) {\ var element = document.createElement(\"q\");\ element.textContent = tag;\ var removeButton = document.createElement(\"button\");\ removeButton.textContent = \"x\";\ removeButton.onclick = function (event) {\ removeTag(tag);\ }\ element.appendChild(removeButton);\ document.getElementById(\"tags-parent\").appendChild(element);\ tags.push({\ tag: tag,\ element: element\ });\ }\\ function addHandler() {\ var tagInput = document.getElementById(\"tag-input\");\ var tag = tagInput.value;\ if (tag \u0026amp;\u0026amp; !hasTag(tag)) {\ addTag(tag);\ tagInput.value = \"\";\ }\ }\ \u0026lt;/script\u0026gt;\\u0026lt;/head\u0026gt;\\u0026lt;body\u0026gt;\ \u0026lt;div id=\"tags-parent\"\u0026gt;\u0026lt;/div\u0026gt;\ \u0026lt;div\u0026gt;\ \u0026lt;input id=\"tag-input\" type=\"text\"/\u0026gt;\ \u0026lt;button onclick=\"addHandler()\"\u0026gt;Add\u0026lt;/button\u0026gt;\ \u0026lt;/div\u0026gt;\ \u0026lt;script\u0026gt;\ addTag(\"initial-tag-1\");\ addTag(\"initial-tag-2\");\ \u0026lt;/script\u0026gt;\\u0026lt;/body\u0026gt;\\u0026lt;/html\u0026gt;\\
为了实现标签编辑器的功能,我用了 45 行 JavaScript 代码来编写 UI 逻辑,外加若干的 HTML \u0026lt;div\u0026gt; 外加两行 JavaScript 代码填入初始化数据。
\\HTML 文件中硬编码了几个 \u0026lt;div\u0026gt;。这些\u0026lt;div\u0026gt; 本身并不是动态创建的,但可以作为容器,放置其他动态创建的元素。
\\代码中的函数来会把网页内容动态更新到这些 \u0026lt;div\u0026gt; 中。所以,如果要在同一个页面显示两个标签编辑器,id 就会冲突。因此,以上代码没有复用性。
\\就算用 代替 DHTML API,代码复用仍然很难。为了复用 UI ,jQuery 开发者通常必须额外增加代码,在onload 时扫描整个网页,找出具有特定 class 属性的元素,然后对这些元素进行修改。对于复杂的网页,这些onload 时运行的函数很容易就会冲突,比如一个函数修改了一个 HTML 元素,常常导致另一处代码受影响而内部状态错乱。
\\ReactJS 提供了可以复用的组件,即 React.Component 。如果用 ReactJS 实现标签编辑器,大概可以这样写:
\\\class TagPicker extends React.Component {\\ static defaultProps = {\ changeHandler: tags =\u0026gt; {}\ }\\ static propTypes = {\ tags: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,\ changeHandler: React.PropTypes.func\ }\\ state = {\ tags: this.props.tags\ }\\ addHandler = event =\u0026gt; {\ const tag = this.refs.input.value;\ if (tag \u0026amp;\u0026amp; this.state.tags.indexOf(tag) == -1) {\ this.refs.input.value = \"\";\ const newTags = this.state.tags.concat(tag);\ this.setState({\ tags: newTags\ });\ this.props.changeHandler(newTags);\ }\ }\\ render() {\ return (\ \u0026lt;section\u0026gt;\ \u0026lt;div\u0026gt;{\ this.state.tags.map(tag =\u0026gt;\ \u0026lt;q key={ tag }\u0026gt;\ { tag }\ \u0026lt;button onClick={ event =\u0026gt; {\ const newTags = this.state.tags.filter(t =\u0026gt; t != tag);\ this.setState({ tags: newTags });\ this.props.changeHandler(newTags);\ }}\u0026gt;x\u0026lt;/button\u0026gt;\ \u0026lt;/q\u0026gt;\ )\ }\u0026lt;/div\u0026gt;\ \u0026lt;div\u0026gt;\ \u0026lt;input type=\"text\" ref=\"input\"/\u0026gt;\ \u0026lt;button onClick={ this.addHandler }\u0026gt;Add\u0026lt;/button\u0026gt;\ \u0026lt;/div\u0026gt;\ \u0026lt;/section\u0026gt;\ );\ }\\}\\\
以上 51 行 ECMAScript 2015 代码实现了一个标签编辑器组件,即TagPicker。虽然代码量比 DHTML 版长了一点点,但复用性大大提升了。
\\如果你不用 ECMAScript 2015 的话,那么代码还会长一些,而且需要处理一些 JavaScript 的坑,比如在回调函数中用不了 this。
\\ReactJS 开发者可以随时用 ReactDOM.render 函数把 TagPicker 渲染到任何空白元素内。此外,ReactJS 框架可以在state 和 props 改变时触发 render ,从而避免了手动修改现存的 DOM。
\\如果不考虑冗余的 key 属性,单个组件内的交互 ReactJS 还算差强人意。但是,复杂的网页结构往往需要多个组件层层嵌套,这种父子组件之间的交互,ReactJS 就很费劲了。
\\比如,假如需要在 TagPicker 之外显示所有的标签,每当用户增删标签,这些标签也要自动更新。要实现这个功能,需要给 TagPicker 传入 changeHandler 回调函数,代码如下:
\\\class Page extends React.Component {\\ state = {\ tags: [ \"initial-tag-1\
转载地址:http://uvjyx.baihongyu.com/