摘要:本篇讲解双源属性不可变数据事件驱动等。可被侦听,被侦听后源头若发生变化,相应的侦听函数将自动被调起。本文完本专栏历史文章介绍一项让可以与抗衡的技术可视化开发工具非正经入门之一三宗罪可视化开发工具非正经入门之二分离界面设计
本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点。本篇讲解双源属性、不可变数据、事件驱动等。
1. React 中的隐式双源var mainComp = null; function Welcome(props) { returnHello, {props.name}
; } class DivText extends React.Component { constructor(props) { super(props); this.state = {name:"Wayne"}; mainComp = this; } render() { return (); } } ReactDOM.render(, document.getElementById("root") ); setTimeout( function() { mainComp.setState({name:"George"}); },5000);
这个例子创建的 component 树如下图,main 节点的 state.name 传递给 txt 节点用作 props.name。txt 节点初始显示 "Hello, Wayne",过 5 秒后切换为 "Hello, George"。
+-- main // div | +-- txt // h1
我们研究一下 5 秒后切换都发生了什么,mainComp.setState({name:"George"}) 一句更改 main 节点的 state.name,然后系统触发下级 txt 节点的 props.name 变化,再驱动 txt 节点内容刷新。
本处 React 技术实现让初学者很费解,main 节点的 render 函数用 JSX 返回 Element,并非每次渲染都用
render() { return (); }
而是首次渲染时创建一次,其后 render() 调用只对已存在的节点做更新,由 props.name 变化驱动子节点内容刷新。所以,上面 txt 节点的 props.name 对节点自身来说,是不变量,但对父节点来说,是可变量。由 state.xxx 驱动刷新与 props.xxx 驱动刷新本质是一回事,只不过 React 编程模型在表面弄了一点限制。
props.xxx 驱动的刷新是一个源头,state.xxx 驱动的刷新是另一个源头,合起来是 "双源驱动"。
2. 改造双源驱动由于 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; returnHello, {name}
; } }
这样,在 txt 节点,既可用 txtComp.setState({name:"George"}) 驱动刷新,也可由父节点传入的 props.name 变化来驱动刷新。我们额外要做的是,在 txt 节点用 this.oldName 记录 props.name 旧值,由 this.oldName !== this.props.name 来识别传入 props.name 是否变化了。
这么改造的意义在于:
自身状态变迁与父级驱动变迁,是两种普遍存在的现象,我们引用正规的 "双源驱动" 概念,便于将两种源头归一,如后面叙述,用 this.duals.xxx 表达,归一后才能构造事件发布与订阅的机制。
React 让 props 属性只读的设计有点尴尬,有违普遍认知。
如前面介绍,它不是不可变,而是限定本级与下级不可修改,这个规则对保障单向数据传递有利。但大众对 DOM 节点的认知是这样的,以 为例,type="button" 这个属性可以用 props.type 表达,因为生存周期里它不该有变化,而 title="for test" 属性应让本节点参与管理,生存期内可变。
让自身节点管理类似 props.name, props.title 的属性,大致有两种方法,其一,采取上面介绍的方法,让两个源头归一,再驱动本节点输出。其二,按严格的单向数据流要求,把代码写成下面样子:
class Welcome extends React.Component { constructor(props) { super(props); } setName(newName) { mainComp.setState({name:newName}); } render() { returnHello, {this.props.name}
; } }
也就是借助父节点的 setState() 实现刷新,理论上,这也是单向数据流,理解有点别扭,自身节点的属性不能直接管理,非要到父节点跑一圈。
Shadow Widget 双源驱动的优点在于 "让 DOM 节点功能回归本原",让 props.xxx 服务于生存周期中不变量,让 duals.xxx 服务于可变量,state.xxx 也服务于可变量,但倾向于用来表达自身节点的私有状态。
reflux 为实现 React flux 机制,仿 component 接口设计了 store,如果没有上述 props.xxx 限制,我相信把 component 与 store 合一远优于现有设计。回归原本的设计好处是潜在的,因为倾斜的地基会导致上层建筑更加倾斜。
3. 数据侦听机制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 若发生变化,相应的侦听函数将自动被调起。
多个 component 节点的双源属性可以串接,源头更改其它地方会自动更新。
对某节点的 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 行则触发由参数指定的回调函数。
4. 数据更新的判断依据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 手册。
5. 自动定义的双源属性双源属性一般要调用 comp.defineDual() 注册后才使用,但对于 DOM 节点内置属性是例外,如 title, id, name 等,这些属性只要节点在创建时,传入的 props 用到了,就会被系统自动注册为双源属性。
另外,命名为 data-*, aria-*, dual-* 的属性,也自动注册为双源属性。
自动注册双源属性的设计目的是为了简化编程,如果遇到不想变成双源属性却自动注册了的情况,不使用 duals.xxx 即可。
(本文完)
本专栏历史文章:
介绍一项让 React 可以与 Vue 抗衡的技术
React 可视化开发工具 Shadow Widget 非正经入门(之一:React 三宗罪)
React 可视化开发工具 Shadow Widget 非正经入门(之二:分离界面设计)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87077.html
摘要:本篇解释中类的控制指令,与指令式界面设计相关。本专栏历史文章介绍一项让可以与抗衡的技术可视化开发工具非正经入门之一三宗罪可视化开发工具非正经入门之二分离界面设计可视化开发工具非正经入门之三双源属性与数据驱动可视化开发工具非正经入门之四 本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点。本篇解释 Shadow Widget 中类 Vue 的控制指令,与指令式界面...
摘要:前言非正经入门是相对正经入门而言的。不过不要紧,正式学习仍需回到正经入门的方式。快速入门建议先学会用拼文写文档注册一个账号,把库到自己名下,然后用这个库写自己的博客,参见这份介绍。会用拼文写文章,相当于开发已入门三分之一了。 本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点,既作为用户手册的补充,也从更本质角度帮助大家理解 Shadow Widget 为什么这...
阅读 2211·2019-08-30 15:54
阅读 1946·2019-08-30 13:49
阅读 665·2019-08-29 18:44
阅读 823·2019-08-29 18:39
阅读 1104·2019-08-29 15:40
阅读 1523·2019-08-29 12:56
阅读 3133·2019-08-26 11:39
阅读 3093·2019-08-26 11:37