从 React 谈 Web UI 开发

ReactWeb UI开发

Jan 01, 2017 ·
7分钟阅读

此前我在Twitter上这样表达过对React的理解,但是Twitter篇幅有限,所以在这篇文章里,我要做更详尽的阐述。

我从前不喜欢React,是因为写Reactrender function不像写template一样方便,尤其是存在复杂的判断渲染的时候,Vuetemplate一个v-if就搞定了。而在React里写,需要把这个判断写成function,然后条件判断return哪一个view.这是我最初对React的偏见所在之一。

然而经过自己的实践和思考,加上阅读一些文章,我发现以前的想法是错的。我在使用React的时候,没有做到Thinking in React.从而对React产生了不解和困惑。

有很多人把React当成框架来用,这是用不好React的根本原因。很少人认真思考A JavaScript library for building User Interface背后的含义,把React用得一团糟。

何谓For Building User Interface?意思就是,这个库仅仅是用于构建UI的,这是React本质要解决的问题。我甚至和很多人说,事实上React本身是不是React已经不重要了,重要的是我们写UI的思维。React这个library本身仅仅是用来实现这个思维的手段React提供的,是优秀的DOM diff算法,和一套Component system。换成代码来说,也就是:

(state) => View

这是React building UI的核心思想,所有的组件,就是接受state,返回一个View.这样看上去比较抽象,比如我们有一个Clock组件:

const Clock = (time) => `
<div id='clock'>
<span>It's now: </span>
<span>${time}</span>
</div>
`

Clock是一个function,接受一个time参数,返回的是一串HTML String.在程序里,我们可以给一个Interval,每秒传一个当前的time,得到一个新的HTML String,然后apply到某个DOM上。

const $app = document.getElementById('app')
setInterval(() => {
$app.innerHTMl = Clock(Date.now())
}, 1000)

这样的实现是能达到目的的,但是问题在于,每次 innerHTML 时,整个 #appDOM树会被重新渲染。

我们都知道,DOM更新的花费是昂贵的。整个DOM树,实际上只是一个 span 在不断变化,所以我们需要DOM diff算法来得知到底哪一个DOM节点才需要被更新,从而节省开销:

const Clock = ({time}) => (
<div id='clock'>
<span>It's now: </span>
<span>{time}</span>
</div>
)
const $app = document.getElementById('app')
setInterval(() => {
ReactDOM.render(<Clock time={Date.now()} />, $app)
}, 1000)

React里,把props传入,返回一个类似HTML的结构,然后render到指定的DOM节点上。这里React会算出哪个节点应该被更新:

我们这样手动去setInterval然后render未免有点傻,我们可以更改state (也就是通常用到的setState)自动地让React随着state的改变而重新render.这里的time就是一个state.这叫做Reactive.

Functional Programming里有Pure Function的概念。Pure Function之所以Pure,是因为不存在side effect.举个例子,我们写一个求和function

function add (a, b) {
return a + b
}

这个求和函数就是一个pure function.因为函数内部没有对input做任何改变,并且返回一个新的值。我传11,得到的永远是2.

Pure Function的好处是利于维护和测试。要测试一个Pure Function,仅仅是传不同的值,预言对应的返回值。

现在回头看ReactComponent,也可以算是一个Pure Function——接收不同的props,然后render对应的View.上面Clock的例子,props和返回的View是映射关系。

光是 state => View 还不够,在构建UI的时候,我们希望state改变的时候,立即rerender整个View,也就是我们经常用到的setState().

这样就很容易理解为什么我说React仅仅是实现构建UI思想的手段,因为构建UI的思想总结起来就是:

  1. StateReactive(比如ReactsetState)
  2. state => View (依靠DOM diff)
  3. View组成Component
  4. 管理state (依靠第三方的state manager

无论是React还是Vue,大抵都是这样的思想。Vue 1还不完全是,Vue 2就更接近了,只是后者写法既可以写得像template,又可以写直接写vdom.

而开发者常常感到困难的地方实际上是上面的第4点——管理state.React写得痛苦,大部分原因是用把library当成framework去用,把处理state的逻辑瞎写到View层中去,也就是所谓的Dump Component.

改变stateside effect,我们应该把它从View层中分离出去。我多次提到,View层真正要做的,仅仅是根据state返回对应的View. state的变化逻辑,应该在给state manager库去做,例如Redux, Mobx.下面我用Mobx作为例子:

https://jsbin.com/fumerup/edit?js,output

如果没有接触过Mobx不用慌张,只需要知道,MobxObservable变化时,被observer包装的React组件会重新渲染。使用state manager,明显地分离了Viewside-effect:

测试这样的程序,首先为side effect的逻辑做测试,再为View做测试。View的测试在这里就十分简单了,给他传一个store实例,借助 enzyme 之类的testing utilities预言不同的action得到的返回View.

ReactReactive ProgrammingWeb User Interface上实现的手段,它只不过是一个库,提供了reactive render, component system和降低开销的DOM diff算法.React换掉,只要不是手动操作DOM,其它的框架也不过大同小异。重要的是理解它背后的思想。说到底,前端开发在解决什么问题,用什么样的方式解决问题,在使用任何框架和库之前先把这两个问题思考明白,就不会认为前端难学了。


sponsor
sponsor
通过支付宝[email protected]或赞赏码赞助此文