React 可视化开发工具 Shadow Widget 非正经入门(之三:双源属性与数据驱动)

前端之家收集整理的这篇文章主要介绍了React 可视化开发工具 Shadow Widget 非正经入门(之三:双源属性与数据驱动)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点。本篇讲解双源属性、不可变数据、事件驱动等。

1. React 中的隐式双源

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.nametxt 节点初始显示 "Hello,Wayne",过 5 秒后切换为 "Hello,George"

<root node>
  +-- main      // div
  |   +-- txt   // h1

我们研究一下 5 秒后切换都发生了什么,mainComp.setState({name:'George'}) 一句更改 main 节点的 state.name,然后系统触发下级 txt 节点的 props.name 变化,再驱动 txt 节点内容刷新。

本处 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 编程模型在表面弄了一点限制。

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;
    return <h1>Hello,{name}</h1>;
  }
}

这样,在 txt 节点,既可用 txtComp.setState({name:'George'}) 驱动刷新,也可由父节点传入的 props.name 变化来驱动刷新。我们额外要做的是,在 txt 节点用 this.oldName 记录 props.name 旧值,由 this.oldName !== this.props.name 来识别传入 props.name 是否变化了。

这么改造的意义在于:

  1. 自身状态变迁与父级驱动变迁,是两种普遍存在的现象,我们引用正规的 "双源驱动" 概念,便于将两种源头归一,如后面叙述,用 this.duals.xxx 表达,归一后才能构造事件发布与订阅的机制。

  2. React 让 props 属性只读的设计有点尴尬,有违普遍认知。
    如前面介绍,它不是不可变,而是限定本级与下级不可修改,这个规则对保障单向数据传递有利。但大众对 DOM 节点的认知是这样的,以 <input> 为例,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() {
    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 合一远优于现有设计。回归原本的设计好处是潜在的,因为倾斜的地基会导致上层建筑更加倾斜。

3. 数据侦听机制

Shadow Widget 将双源驱动归一后,用 duals.attr 存取属性,而且系统内部对读写 duals.attr 做了封装,"读属性" 自动转从 state.attr 读值,"写属性" 则封装成事件驱动机制,等效于调用 comp.setState({attr:value}),但它所做的事远不止这个,还包括

  1. 用户可以调用 comp.defineDual(attr,setterFunc) 注册自定义的 setter 函数,甚至对同一 duals.attr 多次注册不同 settrer 函数,比如基类定义一个 setter 函数,继承类中再定义另一个 setter,两个 setter 会依顺被调用。即 duals.attr 的 setter 也具有一种可继承的机制。

  2. duals.attr 可被侦听,被侦听后源头 duals.attr 若发生变化,相应的侦听函数自动被调起。

  3. 多个 component 节点的双源属性可以串接,源头更改其它地方会自动更新。

  4. 对某节点的 duals.attr 赋值,会导致多种联动响应,如果导致本节点其它双源属性更新,更新将在同一周期立即进行,如果导致其它节点的双源属性更新,将在下一周期在其它节点 render() 时进行,如果触发侦听事件,也在下一周期调用侦听函数。Shadow Widget 对 duals.attr 赋值的设计,已兼顾考虑了本节点内双源属性递归回调的效率,也保证了数据流传递的单向性。

  5. 在各节点注册 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 = valuecomp.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 用到了,就会被系统自动注册为双源属性。《Shadow Widget 用户手册》的 "2.2.2 定义双源属性" 有这些属性的完整列表。

另外,命名为 data-*,aria-*,dual-*属性,也自动注册为双源属性

自动注册双源属性的设计目的是为了简化编程,如果遇到不想变成双源属性自动注册了的情况,不使用 duals.xxx 即可。

(本文完)

本专栏历史文章

  1. 介绍一项让 React 可以与 Vue 抗衡的技术

  2. React 可视化开发工具 Shadow Widget 非正经入门(之一:React 三宗罪)

  3. React 可视化开发工具 Shadow Widget 非正经入门(之二:分离界面设计)

猜你在找的React相关文章