React 可視化開發工具 Shadow Widget 非正經入門(之三:雙源屬性與數據驅動)

本系列博文從 Shadow Widget 做者的視角,解釋該框架的設計要點。本篇講解雙源屬性、不可變數據、事件驅動等。node

Love job

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"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 驅動的刷新是另外一個源頭,合起來是 "雙源驅動"。框架

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 的屬性,大體有兩種方法,其一,採起上面介紹的方法,讓兩個源頭歸一,再驅動本節點輸出。其二,按嚴格的單向數據流要求,把代碼寫成下面樣子: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 合一遠優於現有設計。迴歸本來的設計好處是潛在的,由於傾斜的地基會致使上層建築更加傾斜。

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 用到了,就會被系統自動註冊爲雙源屬性。

另外,命名爲 data-*, aria-*, dual-* 的屬性,也自動註冊爲雙源屬性。

自動註冊雙源屬性的設計目的是爲了簡化編程,若是遇到不想變成雙源屬性卻自動註冊了的狀況,不使用 duals.xxx 便可。

(本文完)

本專欄歷史文章:

  1. 介紹一項讓 React 能夠與 Vue 抗衡的技術
  2. React 可視化開發工具 Shadow Widget 非正經入門(之一:React 三宗罪)
  3. React 可視化開發工具 Shadow Widget 非正經入門(之二:分離界面設計)
相關文章
相關標籤/搜索