React进阶学习——React事件处理

122 阅读4分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情

事件处理

前程似锦,祝你也祝我—— 我是恰个橘子,今天学习React事件处理 可以看到 React 里面绑定事件的方式和在 HTML 中绑定事件类似,使用驼峰式命名指定要绑定的 onClick 属性为组件定义的一个方法 {this.handleClick.bind(this)}

注意要显式调用 bind(this) 将事件函数上下文绑定要组件实例上,这也是 React 推崇的原则:没有黑科技,尽量使用显式的容易理解的 JavaScript 代码。

“合成事件”和“原生事件”

React 实现了一个“合成事件”层(synthetic event system),这个事件模型保证了和 W3C 标准保持一致,所以不用担心有什么诡异的用法,并且这个事件层消除了 IE 与 W3C 标准实现之间的兼容问题。

“合成事件”还提供了额外的好处:

事件委托

“合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,并且在组件卸载(unmount)的时候自动销毁绑定的事件。

什么是“原生事件”?

比如你在 componentDidMount 方法里面通过 addEventListener 绑定的事件就是浏览器原生事件。

使用原生事件的时候注意在 componentWillUnmount 解除绑定 removeEventListener

所有通过 JSX 这种方式绑定的事件都是绑定到“合成事件”,除非你有特别的理由,建议总是用 React 的方式处理事件。

Tips

关于这两种事件绑定的使用,这里有必要分享一些额外的人生经验

如果混用“合成事件”和“原生事件”,比如一种常见的场景是用原生事件在 document 上绑定,然后在组件里面绑定的合成事件想要通过 e.stopPropagation() 来阻止事件冒泡到 document,这时候是行不通的,参见 Event delegation,因为 e.stopPropagation 是内部“合成事件” 层面的,解决方法是要用 e.nativeEvent.stopImmediatePropagation()

”合成事件“ 的 event 对象只在当前 event loop 有效,比如你想在事件里面调用一个 promise,在 resolve 之后去拿 event 对象会拿不到(并且没有错误抛出):

handleClick(e) {
  promise.then(() => doSomethingWith(e));
}

详情见 Event pooling 说明。

参数传递

给事件处理函数传递额外参数的方式:bind(this, arg1, arg2, ...)

render: function() {
    return <p onClick={this.handleClick.bind(this, 'extra param')}>;
},
handleClick: function(param, event) {
    // handle click
}

React 支持的事件列表

DOM 操作

大部分情况下你不需要通过查询 DOM 元素去更新组件的 UI,你只要关注设置组件的状态(setState)。但是可能在某些情况下你确实需要直接操作 DOM。

首先我们要了解 ReactDOM.render 组件返回的是什么?

它会返回对组件的引用也就是组件实例(对于无状态状态组件来说返回 null),注意 JSX 返回的不是组件实例,它只是一个 ReactElement 对象(还记得我们用纯 JS 来构建 JSX 的方式吗),比如这种:

// A ReactElement
const myComponent = <MyComponent />// render
const myComponentInstance = ReactDOM.render(myComponent, mountNode);
myComponentInstance.doSomething();

findDOMNode()

当组件加载到页面上之后(mounted),你都可以通过 react-dom 提供的 findDOMNode() 方法拿到组件对应的 DOM 元素。

import { findDOMNode } from 'react-dom';
​
// Inside Component class
componentDidMound() {
  const el = findDOMNode(this);
}

findDOMNode() 不能用在无状态组件上。

Refs

另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名称,然后通过 this.refs.name 来访问对应的 DOM 元素。

比如有一种情况是必须直接操作 DOM 来实现的,你希望一个 <input/> 元素在你清空它的值时 focus,你没法仅仅靠 state 来实现这个功能。

class App extends Component {
  constructor() {
    return { userInput: '' };
  }
​
  handleChange(e) {
    this.setState({ userInput: e.target.value });
  }
​
  clearAndFocusInput() {
    this.setState({ userInput: '' }, () => {
      this.refs.theInput.focus();
    });
  }
​
  render() {
    return (
      <div>
        <div onClick={this.clearAndFocusInput.bind(this)}>
          Click to Focus and Reset
        </div>
        <input
          ref="theInput"
          value={this.state.userInput}
          onChange={this.handleChange.bind(this)}
        />
      </div>
    );
  }
}

如果 ref 是设置在原生 HTML 元素上,它拿到的就是 DOM 元素,如果设置在自定义组件上,它拿到的就是组件实例,这时候就需要通过 findDOMNode 来拿到组件的 DOM 元素。

因为无状态组件没有实例,所以 ref 不能设置在无状态组件上,一般来说这没什么问题,因为无状态组件没有实例方法,不需要 ref 去拿实例调用相关的方法,但是如果想要拿无状态组件的 DOM 元素的时候,就需要用一个状态组件封装一层,然后通过 reffindDOMNode 去获取。

总结

  • 你可以使用 ref 到的组件定义的任何公共方法,比如 this.refs.myTypeahead.reset()
  • Refs 是访问到组件内部 DOM 节点唯一可靠的方法
  • Refs 会自动销毁对子组件的引用(当子组件删除时)

注意事项

  • 不要在 render 或者 render 之前访问 refs
  • 不要滥用 refs,比如只是用它来按照传统的方式操作界面 UI:找到 DOM -> 更新 DOM