接上一章 HOC 探索 抛出的问题 ———— react 中的 onChange 事件和原生 DOM 事件中的 onchange 表现不一致, 举例说明如下:
// 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。有两点信息和这篇文章的话题相关。
从这两点内容我们可以得知下面的信息:
React 实现了一套合成事件机制, 也就是它的事件机制和原生事件间会有不同。比如它目前 onChange 事件其实对应着原生事件中的 input 事件。在这个 issue 中明确了未来会使用 onInput 事件替代 onChange 事件, 并且会大幅度地简化合成事件。
有了以上信息后, 我们对 onChange 事件(将来的 onInput 事件)的代码作如下更改:
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
的情形即为简化版的受控组件。
题目可以换个问法: 当 input
的传入属性为 value
时(且没有 onChange 属性), 如何禁用用户的输入事件的同时又能获取焦点?
首先想到了 html 自带属性 readonly、disable, 它们都能禁止用户的输入, 但是它们不能满足获取焦点这个条件。结合前文 onChange
的实现是监听 input
事件, 代码分为以下两种情况:
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
的事件循环机制改变表单的值。
效果如下:
至此, 模拟了受控组件的实现。