[react] 细数 React 的原罪
-
Props & onChange 的原罪 。「props & onChange 接口规范」它不是一个典型的「程序接口规范」。
- 当你拿到一个可视组件的 ref,却没有类似
setProps()
这样的方法来改变其props
,你只能在 render() 方法中,通过 jsx 语言来设置其props
。这意味着父元素必须保存并维护这个 props 对应的值,而更多时候,父容器只是想一次性的设置一个值,然后就走,让以后的事情交给子元素自己去维护。当然,你也可以通过 ref 来获取子元素的应用,然后调用其成员方法来达到一次性改变属性的目的,但又会带来其他问题,请看下一条。 - 在大多数客户端框架里,有一个视图类
ComponentA
,compA = new ComponentA()
,compoA
即是对这个可视组件的表示。而,在 React 世界里,compA.render()
返回的结果(结果的结构参见下图),才是这个可视组件的表示。这种不同带来的后果是,我们很难对已经渲染过的节点进行后续更改。当然 React 提供了React.cloneElement(element,props)
接口来改变,但是这个接口怎么看都不像一种正派的接口,而像是。。。hack
。因为这种改变,其他人时候很难看懂的。换句话说,我们不是通过一个明确定义的接口,而是“随性的”,“肆无忌惮”的修改了内存。 -
React 用
props & PropType
的方式重新定义了一种接口规范,定义了一个组件的输入和输出。输入props
一般是string
,boolean
,number
,object
;输出参数一般是function
。PropType
定义了每个props
的取值范围、 是否必填等信息。这整个一套机制是跟面向对象语言里的interface
的功能是高度重合的,也就是说,React 重新发明了轮子。并且这个轮子有自己很大的局限性:他不是能自表达的。什么意思呢?在典型的面向对象语言里(以 java 为例), 假设有一下代码:1
2
3
4
5
6inteface IRunable{
run();
getSpeed();
}
function clone(IRunable){}这段代码里的
clone
函数的第一个参数必须满足IRunable
接口,否则编译器会抛出编译错误。但是,切换到props
接口规范,假设我们定义了一个对象的接口:1
2
3
4
5
6
7
8
9
10
11const SomePropType = PropType.shape({
name: PropType.string.isRequired,
uuid: PropType.stirng.isRequired,
onChange: PropType.func.isRequired,
});
function clone(element){}. // element 是一个必须满足 SomePropType 接口的元素。
const AnotherPropType = PropType.shape({
elment: ???how??? // 一个必须满足 SomePropType 接口的元素。
})你没办法定义一个
clone
方法,限制它的第一个参数必须是满足某个PropType
定义的组件对象;甚至,你也没办法定义一个接口AnotherPropType
,使得它的某个属性满足SomePropType
。
- 当你拿到一个可视组件的 ref,却没有类似
- Controller 和 View 融为一谈。React 本身是一个 View 层的工具,他最擅长的是把数据
render()
成 dom 元素。在 Android SDK 里,有一个东西跟它的功能很相似: layout xml。但是在 React 的设计理念里,没有对 view 和 controller 的明确的概念的区分。我们可以在 Component 里做 view 层该做的事情(根据给定数据显示界面),也可以做 controller 应该做的事情(处理交互动作、处理网络请求)。以至于有很多博客来“教” React 的开发者来明确分这两个概念,参考博客Presentational and Container Components。当然你可以说,这是开发者自身素质和抽象能力的体现,但是作为一个使用量如此巨大的框架,官方居然没有为用户构造和区分这个概念,还要用户自己去发现和总结,不得不说,是一大黑点。