此前我在Twitter 上这样表达过对React 的理解,但是Twitter 篇幅有限,所以在这篇文章里,我要做更详尽的阐述。
我从前不喜欢React, 是因为写React 的render function 不像写template 一样方便,尤其是存在复杂的判断渲染的时候,Vue 的template 一个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 。换成代码来说,也就是:
这是React building UI 的核心思想,所有的组件,就是接受state, 返回一个View. 这样看上去比较抽象,比如我们有一个Clock 组件:
const Clock = ( time ) => `
Clock 是一个function, 接受一个time 参数,返回的是一串HTML String. 在程序里,我们可以给一个Interval, 每秒传一个当前的time, 得到一个新的HTML String, 然后apply 到某个DOM 上。
const $app = document. getElementById ( 'app' )
$app.innerHTMl = Clock (Date. now ())
这样的实现是能达到目的的,但是问题在于,每次 innerHTML
时,整个 #app
的DOM 树会被重新渲染。
我们都知道,DOM 更新的花费是昂贵的。整个DOM 树,实际上只是一个 span
在不断变化,所以我们需要DOM diff 算法来得知到底哪一个DOM 节点才需要被更新,从而节省开销:
const Clock = ({ time }) => (
const $app = document. getElementById ( 'app' )
ReactDOM. render (< Clock time = {Date. now ()} />, $app)
在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 :
这个求和函数就是一个pure function. 因为函数内部没有对input 做任何改变,并且返回一个新的值。我传1 和1 ,得到的永远是2.
Pure Function 的好处是利于维护和测试。要测试一个Pure Function, 仅仅是传不同的值,预言对应的返回值。
现在回头看React 的Component, 也可以算是一个Pure Function ——接收不同的props, 然后render 对应的View. 上面Clock 的例子,props 和返回的View 是映射关系。
光是 state => View
还不够,在构建UI 的时候,我们希望state 改变的时候,立即rerender 整个View, 也就是我们经常用到的setState()
.
这样就很容易理解为什么我说React 仅仅是实现构建UI 思想的手段,因为构建UI 的思想总结起来就是:
State 是Reactive 的( 比如React 的setState
)
state => View ( 依靠DOM diff)
View 组成Component
管理state (依靠第三方的state manager )
无论是React 还是Vue, 大抵都是这样的思想。Vue 1 还不完全是,Vue 2 就更接近了,只是后者写法既可以写得像template, 又可以写直接写vdom.
而开发者常常感到困难的地方实际上是上面的第4 点——管理state. 写React 写得痛苦,大部分原因是用把library 当成framework 去用 ,把处理state 的逻辑瞎写到View 层中去,也就是所谓的Dump Component.
改变state 是side effect, 我们应该把它从View 层中分离出去。我多次提到,View 层真正要做的,仅仅是根据state 返回对应的View . state 的变化逻辑,应该在给state manager 库去做,例如Redux, Mobx. 下面我用Mobx 作为例子:
https://jsbin.com/fumerup/edit?js,output
如果没有接触过Mobx 不用慌张,只需要知道,Mobx 的Observable 变化时,被observer 包装的React 组件会重新渲染。使用state manager, 明显地分离了View 和side-effect:
测试这样的程序,首先为side effect 的逻辑做测试,再为View 做测试。View 的测试在这里就十分简单了,给他传一个store 实例,借助 enzyme 之类的testing utilities 预言不同的action 得到的返回View.
React 是Reactive Programming 在Web User Interface 上实现的手段,它只不过是一个库,提供了reactive render, component system 和降低开销的DOM diff 算法. 把React 换掉,只要不是手动操作DOM, 其它的框架也不过大同小异。重要的是理解它背后的思想。说到底,前端开发在解决什么问题,用什么样的方式解决问题,在使用任何框架和库之前先把这两个问题思考明白,就不会认为前端难学了。