Subscribe on changes!

关于class中使用addEventListener为一个dom元素添加点击事件修改成员属性值,模版没有更新渲染的问题

avatar
May 22nd 2022

Vue version

3.2.25

Link to minimal reproduction

https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiwgb25Nb3VudGVkIH0gZnJvbSAndnVlJ1xuIC8vIOe0r+WKoOaMiemSrnJlZlxuY29uc3QgYnV0dG9uUmVmID0gcmVmKClcbiAgXG4gIC8vIOS6i+S7tuebkeWQrOesrOS6jOS4quWPguaVsOS8oOWFpeS4gOS4quaWueazlVxuY2xhc3MgVGVzdENsYXNzMSB7XG5cdGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuY291bnQgPSAwXG5cdH1cbiAgaW5pdChlbCkge1xuICAgIGVsPy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIHRoaXMuY3VtdWxhdGl2ZSlcbiAgfVxuICBjdW11bGF0aXZlID0gKCkgPT4ge1xuICAgIHRoaXMuY291bnQrK1xuXHR9XG59XG4gIFxuICBcbiAgLy8g5rWL6K+V57G7Mjrkuovku7bnm5HlkKznrKzkuozkuKrlj4LmlbDnm7TmjqXlhpnnrq3lpLTlh73mlbBcbmNsYXNzIFRlc3RDbGFzczIge1xuXHRjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLmNvdW50ID0gMFxuXHR9XG4gIGluaXQoZWwpIHtcbiAgICBlbD8uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB7XG4gICAgICB0aGlzLmNvdW50Kytcblx0XHR9KVxuICB9XG59XG5cbiAgXG4vLyDlrp7kvovljJbkuKTkuKrnsbvlubbnlKjlk43lupTlvI9hcGnljIXoo7lcbmNvbnN0IHRlc3RSZWYxID0gcmVmKG5ldyBUZXN0Q2xhc3MxKCkpXG5cbmNvbnN0IHRlc3RSZWYyID0gcmVmKG5ldyBUZXN0Q2xhc3MyKCkpXG5cbi8vIOetieW+hWRvbeWKoOi9veW5tuS4uuS4pOS4quWunuS+i+WIneWni+WMluS8oOWFpWRvbeiKgueCuVxub25Nb3VudGVkKCgpID0+IHtcbnRlc3RSZWYxLnZhbHVlLmluaXQoYnV0dG9uUmVmLnZhbHVlKVxudGVzdFJlZjIudmFsdWUuaW5pdChidXR0b25SZWYudmFsdWUpXG5cbn0pXG4gIFxuY29uc3QgY2hlY2tJbnN0YW5jZSA9ICgpID0+IHtcblx0Y29uc29sZS5sb2coJ+aIkeaYr3Rlc3RSZWYx5a6e5L6L55qEY291bnTlgLzvvJonLCB0ZXN0UmVmMS52YWx1ZS5jb3VudCkgLy8g5omT5Y2wdGVzdFJlZjHlrp7kvovkuIvpnaLnmoRjb3VudFxufVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPGgxPuabtOaWsOa1i+ivlTHvvJp7eyB0ZXN0UmVmMS5jb3VudCB9fTwvaDE+XG4gIDwhLS0g5rWL6K+V55qE5pe25YCZ6K+36ZqQ6JeP5LiL6Z2i55qE5YWD57SgIC0tPlxuICA8IS0tPGgxPuabtOaWsOa1i+ivlTLvvJp7eyB0ZXN0UmVmMi5jb3VudCB9fTwvaDE+LS0+XG4gIDxoMT7mm7TmlrDmtYvor5Uy77yae3sgdGVzdFJlZjIuY291bnQgfX08L2gxPlxuICA8YnV0dG9uIHJlZj1cImJ1dHRvblJlZlwiIEBjbGljaz1cImNoZWNrSW5zdGFuY2VcIj7ntK/liqA8L2J1dHRvbj5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSJ9

Steps to reproduce

如Vue SFC Playground代码所示,定义了两个类TestClass1 、TestClass2。(这两个类在功能上完全一致)

两个类唯一的区别是在init方法传入一个dom节点,并为dom节点添加点击事件时处理上有所不同。

  // 事件监听第二个参数传入一个方法
class TestClass1 {
    constructor() {
    this.count = 0
    }
  init(el) {
    el?.addEventListener('click', this.cumulative)
  }
  cumulative = () => {
    this.count++
    }
}
  
  
  // 测试类2:事件监听第二个参数直接写箭头函数
class TestClass2 {
    constructor() {
    this.count = 0
    }
  init(el) {
    el?.addEventListener('click', () => {
      this.count++
        })
  }
}

如上图所示: TestClass1 为按钮添加点击事件addEventListener的第二个参数是一个变量指向一个箭头函数(姑且称为‘具名函数’) TestClass2 addEventListener的第二个参数直接写箭头函数

然后分别用ref api包裹使其new出来的实例具有响应式

然后在onMounted钩子总执行两个实例的init方法

在模版上插入

<h1>更新测试1:{{ testRef1.count }}</h1>

然后单击按钮神奇的事情来了。模版似乎没有任何的反应 count值没有更新,于是我继续为按钮添加了一个打印事件打印 testRef1.value.count值。 显示如下 1653231348991 控制台正确的打印出count的值, 而模版却没有更新渲染。

然后我们再在模版中插入 <h1>更新测试2:{{ testRef2.count }}</h1> 再次点击按钮。 结果这次模版上testRef1.count跟着testRef2.count一起渲染出来了。 1653231812068

What is expected?

期望TestClass1类的count也能更新渲染

What is actually happening?

在没有其他渲染更新时TestClass1的count无法单独更新 感觉失去了响应式。两个类几乎没有什么区别

System Info

No response

Any additional comments?

No response

avatar
May 23rd 2022

This is a known issue and expected behavior.

In short, when you use the following syntax:

class Foo {
  constructor() {
    this // <-- this is the raw object and not the reactive proxy
  }
  inc = () => {
    this // <-- same as above
  }

  normal() {
    this // <-- this is the reactive proxy
  }
}

Root cause is because a class' constructor is called before Vue can convert it into a reactive proxy. So the this you are accessing is not reactive. You can change its state, but it won't trigger updates.

There is no way to fix this AFAIK, so the basic rule of thumb is: Avoid initializing methods in class constructors if you are making it reactive.