在一个风轻云淡周末晚上,宝宝已经睡着。我轻手轻脚地爬起来,打开电脑,把心里构思的Nuclear实现了一遍。我是那种无法带着idea入睡的人,这种人活着比较累。因为有现成的observejs、mustachejs和classjs可以用,所以比较顺利,很快就完成了。为什么取名叫Nuclear,因为Nuclear是核的意思,它爆炸起来比较凶猛。
为什么要写这样一个东西?因为学了一个星期的react,做游戏的人,对class base的编程有特别的好感,但是又无法适应jsx和vd无法快速搭建复杂的PC后台的困惑,对vd甚至要颠覆前端工作流程感到困惑。当时,看的相关文章挺多。对声明式,可嵌套,快速的vd的diff相关的东西,读了官网的代码和大量的相关文章以及各大分享的PPT,也体会到了一点。但是那些都不是我去写Nuclear的原因,当时写的原因很简单class base和ui=fn(state)。然后:
class base嘛,先来个classjs
ui=fn(state)嘛,在Nuclear是 通过this.option访问用户传入的配置
数据变化通知视图:observejs (赋值胜于method call)
模板?找个star最多的 = =! mustachejs就它了
Canvas组件? render里直接写逻辑,而不是返回模板,用户只需要传canvas容器、canvas宽高和组件配置。
不动手不知道,一动手吓一跳。
问题零:事件绑定,react的事件绑定都是写在标签上的。可通过this访问状态和组件方法。Nuclear只能通过这样子去解决了。this.node可以得到当前组件的根节点。当时安慰自己说:这种方式还可以在js解除,触发,貌似更加灵活= =!
onRefresh: function () { this.form = this.node.querySelector("form"); this.textBox = this.node.querySelector("input"); this.form.addEventListener("submit", function (evt) { evt.PReventDefault(); this.option.items.push(this.textBox.value); }.bind(this), false); }
问题一是嵌套的TodoList,非常地整齐优雅:
render: function() { return ( <div> <h3>TODO</h3> <TodoList items={this.state.items} /> <form onSubmit={this.handleSubmit}> <input onChange={this.onChange} value={this.state.text} /> <button>{'Add #' + (this.state.items.length + 1)}</button> </form> </div> ); }
上面是官网Todo的代码,这怎么搞?mustachejs虽然支持{{>partials}},但是也无法实现上面这种嵌套的自定义标签。所以最后Nuclear实现的时候混在一起了:
return '<div>/ <h3>TODO</h3>/ <ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>/ <form >/ <input type="text" />/ <button>Add #{{items.length}}</button>/ </form>/ </div>';
为此我纠结了许久,怎么实现自定义标签的嵌套?纠结没有结果之后,我去创建了一个asp.net的项目,看看里面的控件有多少可以嵌套。发现能嵌套进其他控件的基本就是容器类的,然后我就一直怀疑是否有必要嵌套,直到现在,嵌套到底有没有必要,我现在心里还是个问号。
如果真的有必要,那么实现起来不简单,因为要管理组件间的状态、事件、更新等。据说组件之间的状态共享通过爬树!?Tree是非常有用的数据结构。
在写markdown编辑器的时候,遇到了第二个问题:失去焦点。
因为最开始的时候根本没有考虑dom diff和局部刷新,只是碰一个问题解决一个。只要数据变化,dom全部刷新。导致的结构就是:光标本来在textare里,然后每次输入们字就失去焦点了,因为重新刷新了dom。这里才想到了dom diff。但最后决定通过标记 nc-refresh 去定义需要局部刷新的地方,实现成本最低:
return '<div>/ <h3>Input</h3>/ <textarea rows="15" cols="35">{{value}}</textarea>/ <h3>Output</h3>/ <div class="content" nc-refresh >/ {{{html}}}/ </div>/ </div>';
提供onRefresh方法出发刷新之后的回调。所以局部刷新区域内的事件绑定必须全部卸载onRefresh里面。不刷新的区域里面的dom元素事件绑定在installed里面。installed代表dom树已经ready里。
就这样,通过 nc-refresh和onRefesh和insalled 把第二个问题给fix了。脑子里一团浆糊,也想不到什么更好的方案(晚上写代码都是机械性的,走一步是一步)。
nuclear理念和react确实不相同,只是形似。比如Todo的例子。
var TodoList = Nuclear.create({ render: function () { return '<ul> {{#items}} <li>{{.}}</li> {{/items}}</ul>'; }});var TodoApp = TodoList.create({ onRefresh: function () { this.form = this.node.querySelector("form"); this.textBox = this.node.querySelector("input"); this.form.addEventListener("submit", function (evt) { evt.preventDefault(); this.option.items.push(this.textBox.value); }.bind(this), false); }, render: function () { return '<div>/ <h3>TODO</h3>' + this._super() + '<form >/ <input type="text" />/ <button>Add #{{items.length}}</button>/ </form>/ </div>'; }});new TodoApp("#todoContainer", { items: [] });
nuclear是继承+组合,react是嵌套+组合。然而这样的话,就会有个问题,如果TodoApp里用了多个组件,不仅仅是TodoList。那么nuclear就需要使用多重继承或依赖注入,这个需要nuclear后续做一些处理。
通过自己的动手,终于弄明白一些React的how/what/why。但是虽然知道归知道,进一步的提升Nuclear、把优秀的思想转成代码合并进Nuclear,我觉得连angularjs都没用过的我暂时也想不到好的方案。
后来我就发给了朋友看。朋友看到之后的反馈很逗B:文案太没霸气了= =!。
当时的文案是:一款类似React的超轻量级框架, 但是移除了JSX、Virtual Dom和RequestAnimationFrame
后来我改成了:一款类似React的超轻量级框架, 废除了JSX、Virtual Dom和RequestAnimationFrame
英文其实挺中立的: a react-like library without jsx , virtual dom and requestAnimationFrame
现在已经改过来了: http://alloyteam.github.io/Nuclear/
说实话,这一字之差确实拉了许多仇恨,现在也是追悔莫及。确实是炒作和标题党和博眼球的行为。
其实自私一点说,我确实希望项目能够得到更多的关注,比如oberserjs被貘馍和2位教主等大大们转发之后,收到了工业聚等朋友的大量宝贵建议和意见,最后都merge到代码里去了。
而Nuclear确实没了灵感,也想不到什么取巧的实现方式。所以丢到了wb里,听听社区的声音。
有三个人令我印象深刻。
尤: 实话实说吧,完全没有 get 到 react 的 point,把 react 最有价值的部分去掉了,还搞得更 OO...
我:嗯,把react我认为最讨厌的部分去掉了,OO不是槽点,是优点吧!?react也是oo啊,只是帮你new了,无继承,但可组合嵌套
尤小右:这说明你完全没理解 React... 组件不能嵌套要来何用?另外你这个任何数据变了都会重置组建的整个 DOM tree,万一组件很大呢?
我:你完全没有理解nuclear。nuclear可局部刷新,你看markdown那个demo的代码
尤小右:你所谓的局部更新依然是在 DOM 外进行了完整的渲染,虽然对 DOM 内的操作少了,但是成本依然比 virtual dom 高太多了
我: strToDom不是渲染啊
尤小右:嗯,不是渲染,但是是创建了大量无用节点,然后丢掉了。即使只有一个数据变了,你也会重新创建整个模板对应的 DOM 节点,这一点都不便宜的,你自己 benchmark 过没有
我:你吐槽了半天终于吐准了。哎,以后我还是专心写游戏吧,mv+的圈子要不来。
尤小右:性能倒是其次了,我一开始吐的另一点 - 组件不能嵌套,其实更致命
我:组件嵌套是我自己提的。亲,我一开始就说了,nuclear继承,组合,react嵌套组合
尤小右:继承和嵌套的意义完全不一样啊,有了嵌套才能用组件声明式地搭页面
我:各有利弊。目前nuclear支持命令式嵌套。我会考虑支持声明式嵌套
不怪尤小右,文案惹得祸。容易让人不看代码,先吐槽。后来认真分析写了一篇文章:https://github.com/yyx990803/tucao/issues/1 点赞!
寸志:我就是要喷,因为之前他做模块加载器的时候我就想喷了!
寸志:因为我是小白呀,我不懂呀!现在懂你为啥要放个 onrefresh 函数了,就是让我在 DOM 被换了重新做事件绑定 [哈哈] 。我之前模板是这么写的,现在我稍微改下模板,我就要改 onrefresh 的逻辑。小白常常会忘记了吧,这是硬伤。你写出Nuclear,其实挺好的,但是一副好似比 React 牛逼的架势..
究其原因:nuclear确实不够智能地判断哪些地方也重新绑定事件。而react声明式确没有这个问题。
由于以前kmdjs拉过他仇恨,加上文案惹祸。所以一上来就吐槽kmdjs和nuclear。后来平静下来发了好多让我思考的wb和地址。 点赞!
不怪寸志,文案和kmdjs惹得祸。容易让人不看代码,先吐槽。
寸志发的相关地址:https://github.com/et-studio/et 点赞
朴灵:不评论与reactjs的比较,但是这种框架我能顺手写10个。
2014年发生了很多事情甚至颠覆了我的价值观和人生观。每次坐飞机或者火车,我会把一些打磨了很久的代码定时发送给同事,如果安全到家,我就会取消定时,有时也会忘了取消。因为我觉得我的代码没有烂到看不下去,有些思想我想要分享出去。可是,能力有限,有些东西确实做不好,有些东西我还蛮满意。我是个爱分享爱炒作的家伙,以后不会了。
文案已改: http://alloyteam.github.io/Nuclear/
新闻热点
疑难解答