本系列博文从 Shadow Widget 做者的视角,解释该框架的设计要点。本篇讲解双源属性、不可变数据、事件驱动等。node
var mainComp = null; function Welcome(props) { return <h1>Hello, {props.name}</h1>; } class DivText extends React.Component { constructor(props) { super(props); this.state = {name:'Wayne'}; mainComp = this; } render() { return ( <div key='main'> <Welcome key='txt' name={this.state.name} /> </div> ); } } ReactDOM.render( <DivText />, document.getElementById('root') ); setTimeout( function() { mainComp.setState({name:'George'}); },5000);
这个例子建立的 component 树以下图,main
节点的 state.name
传递给 txt
节点用做 props.name
。txt
节点初始显示 "Hello, Wayne"
,过 5 秒后切换为 "Hello, George"
。git
<root node> +-- main // div | +-- txt // h1
咱们研究一下 5 秒后切换都发生了什么,mainComp.setState({name:'George'})
一句更改 main
节点的 state.name
,而后系统触发下级 txt
节点的 props.name
变化,再驱动 txt
节点内容刷新。github
本处 React 技术实现让初学者很费解,main
节点的 render
函数用 JSX 返回 Element,并不是每次渲染都用 <Welcome>
建立子节点。编程
render() { return ( <div key='main'> <Welcome key='txt' name={this.state.name} /> </div> ); }
而是首次渲染时建立一次,其后 render()
调用只对已存在的节点作更新,由 props.name
变化驱动子节点内容刷新。因此,上面 txt
节点的 props.name
对节点自身来讲,是不变量,但对父节点来讲,是可变量。由 state.xxx
驱动刷新与 props.xxx
驱动刷新本质是一回事,只不过 React 编程模型在表面弄了一点限制。segmentfault
props.xxx
驱动的刷新是一个源头,state.xxx
驱动的刷新是另外一个源头,合起来是 "双源驱动"。框架
因为 React 限定本节点 props.xxx
是只读的,咱们经过改造,让一个节点既接受 props.xxx
驱动,也接受 state.xxx
驱动。让 React 隐式的双源驱动,变成显式的双源驱动,以下:函数
var txtComp = null; class Welcome extends React.Component { constructor(props) { super(props); this.state = {name:props.name}; this.oldName = props.name; txtComp = this; } render() { var name = this.state.name; if (this.oldName !== this.props.name) name = this.state.name = this.oldName = this.props.name; return <h1>Hello, {name}</h1>; } }
这样,在 txt
节点,既可用 txtComp.setState({name:'George'})
驱动刷新,也可由父节点传入的 props.name
变化来驱动刷新。咱们额外要作的是,在 txt
节点用 this.oldName
记录 props.name
旧值,由 this.oldName !== this.props.name
来识别传入 props.name
是否变化了。工具
这么改造的意义在于:开发工具
this.duals.xxx
表达,归一后才能构造事件发布与订阅的机制。<input>
为例,type='button'
这个属性能够用 props.type
表达,由于生存周期里它不应有变化,而 title='for test'
属性应让本节点参与管理,生存期内可变。让自身节点管理相似 props.name, props.title
的属性,大体有两种方法,其一,采起上面介绍的方法,让两个源头归一,再驱动本节点输出。其二,按严格的单向数据流要求,把代码写成下面样子:this
class Welcome extends React.Component { constructor(props) { super(props); } setName(newName) { mainComp.setState({name:newName}); } render() { return <h1>Hello, {this.props.name}</h1>; } }
也就是借助父节点的 setState()
实现刷新,理论上,这也是单向数据流,理解有点别扭,自身节点的属性不能直接管理,非要到父节点跑一圈。
Shadow Widget 双源驱动的优势在于 "让 DOM 节点功能回归本原",让 props.xxx
服务于生存周期中不变量,让 duals.xxx
服务于可变量,state.xxx
也服务于可变量,但倾向于用来表达自身节点的私有状态。
reflux 为实现 React flux 机制,仿 component 接口设计了 store,若是没有上述 props.xxx
限制,我相信把 component 与 store 合一远优于现有设计。回归本来的设计好处是潜在的,由于倾斜的地基会致使上层建筑更加倾斜。
Shadow Widget 将双源驱动归一后,用 duals.attr
存取属性,并且系统内部对读写 duals.attr
作了封装,"读属性" 自动转从 state.attr
读值,"写属性" 则封装成事件驱动机制,等效于调用 comp.setState({attr:value})
,但它所作的事远不止这个,还包括:
comp.defineDual(attr,setterFunc)
注册自定义的 setter 函数,甚至对同一 duals.attr
屡次注册不一样 settrer 函数,好比基类定义一个 setter 函数,继承类中再定义另外一个 setter,两个 setter 会依顺被调用。即 duals.attr
的 setter 也具备一种可继承的机制。duals.attr
可被侦听,被侦听后源头 duals.attr
若发生变化,相应的侦听函数将自动被调起。duals.attr
赋值,会致使多种联动响应,若是致使本节点其它双源属性更新,更新将在同一周期当即进行,若是致使其它节点的双源属性更新,将在下一周期在其它节点 render()
时进行,若是触发侦听事件,也在下一周期调用侦听函数。Shadow Widget 对 duals.attr
赋值的设计,已兼顾考虑了本节点内双源属性递归回调的效率,也保证了数据流传递的单向性。duals.attr
的 setter 函数、侦听函数,能自动适应它的生存周期。好比 B 节点侦听 A 节点的 duals.attr
,不管 A 节点,仍是 B 节点先被卸载,侦听链都会自动断开。侦听源节点的双源属性,常见代码有这么两种写法:
sourceComp.listen('attr',targetComp,'attrMethod'); sourceComp.listen('attr',function(value,oldValue){});
第 1 行写法的效果是:sourceComp.duals.attr 发生变化后,自动触发 targetComp['attrMethod'] 的函数调用。第 2 行则触发由参数指定的回调函数。
Shadow Widget 采用 "恒等比较" 的方式判断两个数值是否更改成,在 comp.duals.attr = value
与 comp.setState({attr:value})
语句中,当所赋新值(value
)与旧值恒等(即 ===
),则视做数据未更新,也就不会触发相应的 setter 调用或 listen 调用。
Shadow Widget 已为各构件配置 shouldComponentUpdate()
与 componentWillReceiveProps()
缺省处理,除非有特别理由,您不该改变缺省 "以各属性新旧值是否恒等" 的判断方式。
至于如何对 Array 或 Object 快速构造新数据,以便被系统判断为 "非恒等",咱们建议用 React addon 提供的 update
接口,Shadow Widget 已缺省内置该函数,即 ex.update()
,请参考 Shadow Widget 的 API 手册。
双源属性通常要调用 comp.defineDual()
注册后才使用,但对于 DOM 节点内置属性是例外,如 title, id, name
等,这些属性只要节点在建立时,传入的 props
用到了,就会被系统自动注册为双源属性。
另外,命名为 data-*, aria-*, dual-*
的属性,也自动注册为双源属性。
自动注册双源属性的设计目的是为了简化编程,若是遇到不想变成双源属性却自动注册了的状况,不使用 duals.xxx
便可。
(本文完)
本专栏历史文章: