本系列博文從 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
便可。
(本文完)
本專欄歷史文章: