React的事件函数为什么要绑定this


React的事件函数为什么要绑定this

react类组件中,事件处理函数需要 bind(this)

react类组件中,我们需要将 事件处理函数 绑定在组件实例上

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() { // 如果没有bind(this),则调用时此处拿不到类的this.setState
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

为什么需要bind(this)

不绑定的话会出现this 丢失的问题.

这是由 JavaScript 中 this 绑定的方式决定的, 与React的工作方式无关。

class Foo {
  constructor(name){
    this.name = name
  }

  display(){
    console.log(this.name);
  }
}

// 情理之中的示范,输出了this.name lzy
var foo = new Foo('lzy');
foo.display(); // lzy

// 将 函数赋值给新变量, 造成上下文的丢失.. .this,只认调用时的前缀.
// 实际在 React Component 中将处理程序,与为 callback 参数传递相似。
var display = foo.display;
display(); // TypeError: this is undefined

实际上这里就是把堆中的函数地址,传递给了变量,调用变量display时,和foo对象已经毫无关联

调用display时,在display作用域中查找 this.name,相当于在global中查找

示例二

var obj = {
    name: 'lzy',
    display: function () {
        console.log(this); // 'this' 指向 obj
    }
};

var name = "global lzy";
var outerDisplay = obj.display;
outerDisplay(); // 浏览器下运行,输出 global lzy

事件处理函数被传递给新变量后,作用域发生了改变,丢掉了原先的this。

也就是说,react中肯定发生了 函数传递给新的变量 的事件,导致了作用域发生改变.

那么,react中什么时候把事件处理函数,传递给了新的变量呢???

这就要从react对事件绑定 与 触发 的特殊处理说起

React中怎么实现的事件绑定

React createDOM函数中会进行进行真实DOM的创建,随后当前dom上的属性(props)进行处理

<div style="width:100px;" onClick={this.handleClick} xxx='123'>,

其中的 style,onClick,xxx 均在编译阶段被处理为 props下的属性。

处理props,会调用updateProps(),当检测满足/^on[A-Z].*/.test(key)时,此属性被认为是 事件绑定

事件绑定,由addEvent()函数进行处理

  1. 给当前 真实DOM 创建一个 store属性,储存各类事件,键为事件类型(如’click’),值为处理函数.

注意,此时发生了handleClick函数的赋值给新变量store.click,如未bind(this),则原本的this丢失.

  1. 将事件的监听(如’click’)放在 document上,17以后的版本,放在 div#root上.

最顶层的DOM元素自然可以监听其内部的所有事件,

监听到事件后从target向外冒泡,依次触发target及父节点的对应事件handler

换言之,由document监听所有DOM上的事件,而后触发放在 对应DOM 上的store上的对应handler

/**
 * 
 * @param {*} dom 要绑定事件的DOM元素  button
 * @param {*} eventType 事件类型 onclick
 * @param {*} handler 事件处理函数 handleClick
 */
export function addEvent(dom, eventType, handler) {
  let store = dom.store || (dom.store = {})
  //dom.store['onclick']=handleClick
  store[eventType] = handler;//虽然没有给每个子DOM绑定事件,但是事件处理函数还是保存在子DOM上的
  //从17开始,我们不再把事件委托给document.而是委托给容器了 div#root
  if (!document[eventType]) {
    document[eventType] = dispatchEvent; // 如,document.onclick = clickHandler
  }
}
//合成事件的统一代理处理函数
function dispatchEvent(event) {
  let { target, type } = event;//target=button type =click
  let eventType = `on${type}`;//onclick
  let syntheticEvent = createSyntheticEvent(event);
  updateQueue.isBatchingUpdate = true;//事件函数执行前先设置批量更新模式为true
  //在此我们要模拟React事件的冒泡
  while (target) {
    let { store } = target;
    let handler = store && store[eventType]
    handler && handler(syntheticEvent);
    //在执行handler的过程中有可能会阻止冒泡
    if (syntheticEvent.isPropagationStopped) {
      break;
    }
    target = target.parentNode;
  }
  updateQueue.isBatchingUpdate = false;
  updateQueue.batchUpdate();
}

bind 的最佳实践

在定义函数阶段使用箭头函数

class App extends React.Component {
    constructor(props) {
        super(props);
    }
    handleClick = () => {
        console.log('this > ', this);
    }
    render() {
        return (
        <div onClick={this.handleClick}>test</div>
        )
    }
}

此外,可以在 构造函数中使用bind,在render中使用箭头函数,在render中使用bind

组件每次执行render将会重新分配函数这将会影响性能。特别是在你做了一些性能优化之后,它会破坏PureComponent性能。不推荐使用。

为什么vue 和angular中不需要bind

vue源码中methods下的所有函数,都被 显式的调用bind() 绑定在了当前组件对象上.

猜测angular也有类似处理

for (const key in methods) {
    // ...
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) // 内部有.bind(this)
}

文章作者: 罗紫宇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 罗紫宇 !
  目录