ReactNative组件状态设计思考

#1

这篇文章写的较早,是刚接触RN的时候进行的思考总结,是一个分析的过程,您还可以参考思想更成熟一点的这篇:RN组件架构设计:http://segmentfault.com/a/1190000004161358

设计React组件与设计一个jquery组件或者一个原生js组件最大的区别就是状态的设计。

##术语定义

  1. 属性
    1. 泛指用户在初始化组件时候可以传给组件的
    2. 有些框架也叫options
    3. 是public的
    4. 在RN体系中,叫props,其中还包含了事件
  2. 事件
    1. 是public的
    2. 是function类型的
    3. 在RN体系中使用props来引用
  3. 接口
    1. 是public的
    2. 是function类型的
    3. 通过组件实例化之后的对象,可以进行调用
  4. 内部属性
    1. 是private的
    2. 传统设计方案中一般用于存储组件的状态,数据等
    3. RN体系中没有对其进行明确,可以自由设计
  5. 状态
    1. RN体系中明确提出的概念
    2. 传统设计方案中一般使用内部属性来表示

##传统设计思路

按照原来设计组件的方法论,一个UI组件对外应该具有属性、事件、接口,对内具有内部属性,内部接口。

  1. 属性就像一份配置文件,描述了组件应该具有的功能、外观或者初始状态。
  2. 事件是组件在工作工程中,当满足某种条件的时候触发的回调函数
  3. 接口是组件对象的方法,可以让组件去执行某个任务

在原来的设计理念中,并没有提出组件状态的概念,但是其也是一直存在的,通常是使用组件私有属性来存储。

比如,你设计一个按钮,那么他可能有正常状态,禁用状态,那么我们会设计一个属性disable={true|false}来通知组件初始化的具有的状态,设计2个接口disable、enable来赋予js有动态改变其状态的能力,设计2个事件onEnable、onDisable来通知回调函数组件状态发生了变化。伪代码如下:

//组件定义
class button{

	constructor(disable,pid){//构造函数
	
		this.disable=disable,//属性:组件初始化使用这个作为状态
		this.pid=pid;//属性:父容器的id
		
		this._disable=null,//状态:私有属性

		if(this.disable==true){//根据属性来决定初始化状态
			this.disable();
		}else{
			this.enable();
		}
		this.render();//渲染组件
	}
		
	
	enable(){
		this._disable=false;
		if(this.el)this.el.set('disable',false);
		this.fireEvent('onEnable');//触发事件
	}
	
	disable(){
		this._disable=true;
		if(this.el)this.el.set('disable',true);
		this.fireEvent('onDisable');//触发事件
	}
	
	
	render(){
		//渲染组件,状态直接影响了组件的表现和功能
		$(this.pid).innerHTML='<button disable='+this._disable+' />';
		
		//初始化对dom节点的引用
		this.el=$(this.pid).getChild();
	}
	
}

//父容器
<div id='a'></div>

//实例化组件并使用
new button(false,'a');

##RN设计思路

上面的示例中,表示了一个传统UI组件的设计思路,_disable就是这个button组件的关键状态,它直接影响了组件的表现和行为。

而react架构直接把组件状态提升到了一个新的高度,主要有以下几点:

  1. 使用固定的接口来声明组件状态和状态默认值
  2. 使用固定的接口来获得和改变组件状态的值
  3. 状态的改变一定会改变组件的表现,会导致组件的重新渲染

前两项都不是问题,因为我们原来也得有这些东西,只不过写法发生了变化,关键是最后一项。这里说的不是问题,指的是思路上比较容易接受。这种固定写法,问题也很明显,因为要想把一个状态从RN状态体系拿进拿出,会产生一定的工作成本,很是不爽。

我一直在思索,组件状态的变化一定要导致view的变化吗?

答案肯定是no,因为有些状态仅仅影响组件的行为,并不影响表现。比如说,我赋予按钮一个新功能,它可以提交某个表单的数据,也就是需要一个新的状态formName,他就不影响表现,只影响行为。

考虑到性能的极致,我们就只能把这种状态放到RN的状态体系之外,作为对象的私有属性存在。

RN号称diff算法性能很高,但也不是0损耗,如果对RN的渲染理解很透彻,你当然也可以不这样设计,但是我这里还是倾向于保守一些,否则后期的调整也是有工作量的,因为定义、初始化、取值、改值这些操作代码都不一样。写到这里我突然想到,如果可以,我们可以通过某种方法方便的修改某个状态是否影响表现,不用改其它代码的话,那么我们设计状态的时候就不用区分了,就能让我们更关注设计本身。

##结论

说道这里,基本可以理清思路了。

组件按照传统方式设计,该怎么设计就怎么设计,在设计原来组件内部属性的时候,多考虑一步,哪些内部属性的改变是影响表现的,哪些内部属性是不影响表现的。将影响表现的放入到RN状态体系中,将不影响表现的,放入内部属性中。即可。

##tips

  1. 耦合性很强的父子组件,建议将状态统一放到父组件中,子组件中不要放状态了,无论是状态的跨组件共享,还是对view渲染的触发都很方便。除非你很确定某个状态只子组件内部使用,通常这种情况后边可能也会发生变化。
  2. 以下问题可以帮助识别是否是state,摘自官网
    1. 是否是从父级通过 props 传入的?如果是,可能不是 state 。
    2. 是否会随着时间改变?如果不是,可能不是 state 。
    3. 能根据组件中其它 state 数据或者 props 计算出来吗?如果是,就不是 state 。
  3. 根据state的特点进行识别,加入到上一条
    1. 属性的改变会影响到view的变化就可能是state
  4. 参考地址
    1. https://facebook.github.io/react/docs/thinking-in-react.html
    2. http://wiki.jikexueyuan.com/project/react/thinking-in-react.html