从 0 到 1 实现 react - 9.onChange 事件以及受控组件

该系列文章在实现 cpreact 的同时理顺 React 框架的核心内容

项目地址

从一个疑问点开始

接上一章 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。有两点信息和这篇文章的话题相关。

  • Drastically simplify the event system
  • Migrate from onChange to onInput and don’t polyfill it for uncontrolled components

从这两点内容我们可以得知下面的信息:

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 的事件循环机制改变表单的值。

效果如下:

至此,模拟了受控组件的实现。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据钻研

HTML基础

HTML基础 ---- HTML基本知识与结构 HTML常见标签 标签写法与嵌套的讨论 HTML、CSS、javascript三者的关系 HTML是网页内容...

8463
来自专栏大前端_Web

移动端Web页面常见问题解决

经过研究,是devicePixelRatio作怪,因为手机分辨率太小,如果按照分辨率来显示网页,这样字会非常小,所以苹果当初就把iPhone 4的960*640...

2162
来自专栏Windows Community

Windows 8.1 应用再出发 - 视图状态的更新

本篇我们来了解一下Windows 8.1 给应用的视图状态带来了哪些变化,以及我们怎么利用这些变化作出更好的界面视图。 首先我们来简单回顾一下Windows 8...

2796
来自专栏大数据钻研

css print

最近做表单打印,遂整理了一些打印相关的内容。 说到网页打印,首先想到的便是@media查询(即网页css),通过使用媒体类型print即可解决实际应用的大多数...

2983
来自专栏CRPER折腾记

Angular 2 + 折腾记 :(10) 初步了解动画,以及一步一步写个动画效果

ng2.x动画相关的api是并入@angular/core这个核心模块的,在angular4之后开始独立

782
来自专栏青玉伏案

iOS开发之使用Storyboard预览UI在不同屏幕上的运行效果

  在公司做项目一直使用Storyboard,虽然有时会遇到团队合作的Storyboard冲突问题,但是对于Storyboard开发效率之高还是比较划算的。在之...

2098
来自专栏大数据钻研

HTML 入门笔记 - 初识HTML

基础框架 <!DOCTYPE HTML><html><head><meta http-equiv="Content-Type" content="text/ht...

4165
来自专栏Google Dart

AngularDart Material Design 弹出框 顶

该组件将自己发布为DropdownHandle,因此其子级可以通过注入来控制其可见性:

1763
来自专栏行者常至

001.html常用的基础知识点

主要包括结构(Structure)、表现(Presentation)和行为(Behavior)三个方面。

2742
来自专栏飞扬的花生

一步一步学习Bootstrap系列--表单布局

前言:Bootstrap 属于前端 ui 库,通过现成的ui组件能够迅速搭建前端页面,简直是我们后端开发的福音,通过几个项目的锻炼有必要总结些常用的知识,本篇把...

28110

扫码关注云+社区

领取腾讯云代金券