React-Native VS Cocoa Touch – 下一代UI编程思维
React 与状态
React.js
自从Facebook一推出,就受到Web前端工程师的强烈推崇。虽说曾经火过一时的Angular.js
颠覆了前端的工程,但是React
更多颠覆的,是下一代UI编程的思维。
传统UI编程,基本很多地方都需要将数据来源,绑定到对应的UI对象,比如用户点击了一个操作,更改了名称,那么你需要更新执行一个回调函数来处理点击操作,并且把新的数据更新原有的UI对象的属性,比如大概就是这样的东西
1 2 3 4 5
| func onClick(sender) { var data = getData(sender); self.button.title = data.name; self.button.color = data.color; }
|
这样虽然说直观,但是有很大的问题。试想,假如有很多种的回调函数,每个回调函数监听不同的操作,比如onMounseDown
,onMouseUp
,onKeyDown
,onScroll
……甚至根据不同的sender,我们会有不同的操作,我们就必须得手写很多机械的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| self.xx = data.xx self.xy = data.xy ```swift
这类的代码,这就给日后维护和扩展带来了灾难,假如我要换一个UI组件,又得一个个检查是否赋值成功;假如我要把这个UI组件在新的UIView里面重用,我又得改动所有的赋值代码,对于大型项目这种UI对象成百上千,UI属性上万,这样是非常可怕的。
而`React`,就将所有的赋值,数据绑定,抽象成为一个个状态,不同的事件监听,就是不同的状态而已,而这些状态之间相互独立,不会受到某些全局变量更改而造成UI混乱的情况,更好的是,开发者不需要考虑到底这个属性什么时候赋值,是在数据更新之前还是之后,需不需要定时刷新这种无意义的苦力活上。
## React-Native VS Cocoa Touch
> 完整代码:[Tutorial][1]
看看`React-Native`的sample,需求就是实现一个电影列表显示的View。类似这样:
![iOS][2] ![Android][3]
这段代码中,只有View和ViewModel(Model就是临时的JSON),React用状态把UI属性和数据绑定起来,从而避免了事件监听手动判断时机来赋值
```jsx var AwesomeProject = React.createClass({ getInitialState: function() { return { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), loaded: false, }; },
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.movies), loaded: true, }); }) .done(); },
render: function() { return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderMovie} style={styles.listView} /> ); } }
|
完整代码:iOS Demo
相比来说,原生Cocoa Touch的实现,就要丑陋的多了,尤其是渲染部分绑定UI对象的属性和数据来源,假如你有多处数据来源,多种UI属性,你就得写很多判断来保证你的UI对象的属性符合预期的赋值顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| override func viewDidLoad() { super.viewDidLoad() fetchData() }
func fetchData() { indicator.startAnimating() request(.GET, API_URL, parameters: ["apikey": API_KEY, "page_limit": PAGE_SIZE]) .responseJSON{ _, _, data, _ in self.render(JSON(data!)) } }
func render(result:JSON) { indicator.stopAnimating() movieJSON = result self.tableView.reloadData() }
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { var movieNum = movieJSON["movies"].arrayValue.count return movieNum }
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = self.tableView.dequeueReusableCellWithIdentifier("movieIdentifier", forIndexPath: indexPath) as! MovieTableViewCell
let row = indexPath.row
cell.movieTitle.text = movieJSON["movies"][row]["title"].stringValue cell.movieYear.text = movieJSON["movies"][row]["year"].stringValue var movieImageUrl = movieJSON["movies"][row]["posters"]["thumbnail"].stringValue
if cell.movieImage.image == nil { request(.GET, movieImageUrl).response{ _, _, data, _ in let movieImage = UIImage(data: data as! NSData) cell.movieImage.image = movieImage } }
return cell } ```swift
# React 与函数式
`React`更多的,就是一种类似函数式的想法,把UI对象属性,数据来源,当作一个Monad包裹起来,传统意义上的不同数据来源进行UI属性赋值,相当于这个Monad经过不同的函数作用,达到状态的切换,好处就是大大减少了开发者手动维护UI属性的工作,而且可以达到更高的开发效率。而且再也不怕扩展了,因为这时候可以把多个组件分配给不同的人,每个人完全不需要管别人内部的变量名是什么,UI属性是什么,只要把自己的状态管理好,Model层接口统一,剩下的合并即可。
# React 与效率
既然提到了状态,因为`React`采取`VirtualDOM`来diff需要进行状态更新的UI对象,每次确保了只更新属性发生改变的部分。实现也很高效,使用了一个普通的二叉树`vtree`。每个结点`vnode`就是对应的Tag,比如<Image>之类,结点还存储了一个struct用来保存这些Tag的属性(比如Image的size)
其中的diff算法可以一看……代码地址在:[GitHub-vtree][5]
```javascript //主要逻辑,就是对b树进行reorder,找出a树与b树的同结点不同属性的diff //剩下可以无痛patch的部分只需要下面的for循环就可以处理 var aChildren = a.children var orderedSet = reorder(aChildren, b.children) var bChildren = orderedSet.children
var len = aLen > bLen ? aLen : bLen
for (var i = 0; i < len; i++) { var leftNode = aChildren[i] var rightNode = bChildren[i] index += 1
if (!leftNode) { if (rightNode) { apply = appendPatch(apply, new VPatch(VPatch.INSERT, null, rightNode)) } } else { walk(leftNode, rightNode, patch, index) } }
|
这是核心reorder代码,目标找到是同一结点不同属性的diff,多出来的不需要管
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| for (var i = 0 ; i < aChildren.length; i++) { var aItem = aChildren[i] var itemIndex
if (aItem.key) { if (bKeys.hasOwnProperty(aItem.key)) { itemIndex = bKeys[aItem.key] newChildren.push(bChildren[itemIndex])
} else { itemIndex = i - deletedItems++ newChildren.push(null) } } }
for (var j = 0; j < bChildren.length; j++) { var newItem = bChildren[j] if (newItem.key) { if (!aKeys.hasOwnProperty(newItem.key)) { newChildren.push(newItem) } }
var simulate = newChildren.slice()
for (var k = 0; k < bChildren.length;) { var wantedItem = bChildren[k] simulateItem = simulate[simulateIndex]
while(simulateIndex < simulate.length) { simulateItem = simulate[simulateIndex] removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key)) }
|
整体的复杂度,达到了O(M+N),已经是理论下界了。比起手动管理状态来说,效率可以说是直接持平,甚至对部分滥用事件监听的写法效率会更高。
总结
虽然我并不喜欢UI编程,但是自从图形化出现之后,UI已经成为了继数据结构、算法外,面向终端用户的应用又一个大工程。
从最早的指令式跳转赋值UI,函数指针响应处理,到中期的面向对象,消息发送回调事件,手动管理属性,在到如今的React以状态和VirualDOM来绑定数据和组件。
UI编程其实也是在不断进化的,也许今后会有更好的开发方式让我们这群不会写UI的人也能够轻松写起来UI。