該系列文章在實現 cpreact 的同時理順 React 框架的核心內容html
項目地址
接上一章 HOC 探索 拋出的問題 ———— react 中的 onChange 事件和原生 DOM 事件中的 onchange 表現不一致,舉例說明以下:react
// React 中的 onChange 事件 class App extends Component { constructor(props) { super(props) this.onChange = this.onChange.bind(this) } onChange(e) { console.log('鍵盤松開馬上執行') } render() { return ( <input onChange={this.onChange} /> ) } } /*--------------分割線---------------*/ // 原生 DOM 事件中的 onchange 事件:<input id='test'> document.getElementById('test').addEventListener('change', (e) => { console.log('鍵盤松開之後還需按下回車鍵或者點下鼠標纔會觸發') })
咱們來看下 React 的一個 issue React Fire: Modernizing React DOM。有兩點信息和這篇文章的話題相關。git
從這兩點內容咱們能夠得知下面的信息:github
React 實現了一套合成事件機制,也就是它的事件機制和原生事件間會有不一樣。好比它目前 onChange 事件其實對應着原生事件中的 input 事件。在這個 issue 中明確了將來會使用 onInput 事件替代 onChange 事件,而且會大幅度地簡化合成事件。框架
有了以上信息後,咱們對 onChange 事件(未來的 onInput 事件)的代碼做以下更改:dom
function setAttribute(dom, attr, value) { ... if (attr.match(/on\w+/)) { // 處理事件的屬性: let eventName = attr.toLowerCase().substr(2) if (eventName === 'change') { eventName = 'input' } // 和現階段的 react 統一 dom.addEventListener(eventName, value) } ... }
區分自由組件以及受控組件在於表單的值是否由 value
這個屬性控制,比較以下代碼:函數
const case1 = () => <input /> // 此時輸入框內能夠隨意增減任意值 const case2 = () => <input defaultValue={123} /> // 此時輸入框內顯示 123,能隨意增減值 const case3 = () => <input value={123} /> // 此時輸入框內顯示 123,而且不能隨意增減值
case3
的情形即爲簡化版的受控組件。this
題目能夠換個問法:當 input
的傳入屬性爲 value
時(且沒有 onChange 屬性),如何禁用用戶的輸入事件的同時又能獲取焦點?spa
首先想到了 html 自帶屬性 readonly、disable,它們都能禁止用戶的輸入,可是它們不能知足獲取焦點這個條件。結合前文 onChange
的實現是監聽 input
事件,代碼分爲如下兩種狀況:code
1.dom 節點包含 value
屬性、onChange
屬性
2.dom 節點包含 value
屬性,不包含 onChange
屬性
代碼以下:
function vdomToDom(vdom) { ... if (vdom.attributes && vdom.attributes.hasOwnProperty('onChange') && vdom.attributes.hasOwnProperty('value')) { // 受控組件邏輯 ... dom.addEventListener('input', (e) => { changeCb.call(this, e) dom.value = oldValue }) ... } if (vdom.attributes && !vdom.attributes.hasOwnProperty('onChange') && vdom.attributes.hasOwnProperty('value')) { // 受控組件邏輯 ... dom.addEventListener('input', (e) => { dom.value = oldValue }) ... } ... }
能夠發現它們的核心都在這段代碼上:
dom.addEventListener('input', (e) => { changeCb.call(this, e) dom.value = oldValue })
區別是當有 onChange 屬性
時,能提供相應的回調函數 changeCb
經過事件循環機制改變表單的值。看以下兩個例子的比較:
const App = () => <input value={123} />
效果以下:
class App extends Component { constructor() { super() this.state = { num: 123 } this.change = this.change.bind(this) } change(e) { this.setState({ num: e.target.value }) } render() { return ( <div> <input value={this.state.num} onChange={this.change} /> </div> ) } }
這段代碼中的 change
函數即上個段落所謂的 changeCb
函數,經過 setState
的事件循環機制改變表單的值。
效果以下:
至此,模擬了受控組件的實現。