Vue3 初體驗(中),巨坑的事件繫結

jinzhuming發表於2020-10-09

就在剛剛,吃飯的時候突然發現 vue3 的一個很神奇的地方,具體看程式碼:

export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <Button />
      <CountValue
        countValue={count.value}
        onClick={() => {
          *console*.log(“click”)
        }}>
        你好
      </CountValue>
    </div>
  )
}

function CountValue(
  { countValue, onClick }: { countValue: number; onClick: () => void },
  { slots }: { slots: any },
) {
  return (
    <div>
      <button
        onClick={() => {
          onClick1()
        }}>
        按鈕
      </button>
      {slots.default()}
      {countValue}
    </div>
  )
}

那麼你認為當我點選按鈕的時候,會觸發 onClick 事件幾次?一次吧?
並不!
他會觸發兩次!!!
demo

你以為到這就結束了?
並不!!!
不但點選按鈕會觸發,點選子元件(CountValue 元件)任何一個地方他都會觸發!!!
我一度懷疑是不是我的程式碼出了 bug,反覆檢查發現,只要是繫結的和原生事件同名的事件,他都會在根節點上自動繫結!!!
這簡直是一個神坑操作,我查閱了一下,目前網上提到的人並不多,但是普遍認為這是一個很坑的操作。我也是這個時候才明白了 defineComponent 根本不是給 vue 模板用的,是 vue 自己造出來為了讓你宣告事件用的,只有你在 defineComponent 裡明確宣告的事件他才不會繫結,否則他都會強制繫結到根節點去。參考這個程式碼,是沒有問題的

const CountValue = defineComponent({
  props: {
    onClick: {
      type: Function,
    },
    countValue: Number,
  },
  setup(props) {
    return () => (
      <div>
        <button
          onClick={() => {
            if (props.onClick) {
              props.onClick()
            }
          }}>
          按鈕
        </button>
        {props.countValue}
      </div>
    )
  },
})

必須要強制宣告**props**,他才不會強制繫結到根節點。
我真的無法理解這是個什麼設計,既然有了 Typescript,有了 props 這種 js 原生支援的傳遞方式,你為什麼非要抱著自己搞出來的這套不放呢?

{onClick, countValue}: {onClick: () => void; countValue: number}

Ts 這麼簡潔的 props 型別難道不比那自己搞出來的更好用嗎?
換句話說,就算想保留這套,為什麼又要強制繫結到元件根節點去呢?如果我需要做這個操作我自己不會來嗎,如果我不需要這個操作又被強制繫結不會出問題嗎?
同時這意味著哪怕你寫一個最簡單的小函式元件也要套一層這個噁心人的函式,傳遞進去一個物件,否則你就要千萬千萬注意不要起和原生事件同名的事件名,還要注意你取的名字未來瀏覽器也不會佔用,否則出問題是早晚的事情。
更令人擔憂的是,如果真的有人傳入進來一個我並沒有做任何事件繫結(也不打算繫結)的事件,這個事件又被強制繫結到了根節點,是否會出現在我意料之外的情況?這意味著程式碼根本不由我控制,除非我預先預料到使用者可能繫結的任何事件,都提前做好處理,或者使用者完全按照我的文件使用,不出現任何意外。而這兩者都顯然是不可能的。
而且問題還不在這,問題在於這麼搞極大限制了高階函式,每個高階函式必須要完整的宣告所有的 props,否則就有可能會出現 props 的事件被繫結到根節點的問題。但事實上在高階函式里,我關注你其他的點嗎?並不啊,我為什麼非要再重複宣告一遍呢? 如果是多個不同的子元件我想共用一個高階函式做一個操作呢?
就目前而言除了放棄 on 開頭 改用 bindClick 這種之外,我沒有發現什麼好的規避這個設定的方法。
補充:剛剛仔細翻看了文件,發現 vue 官方給了一個 inheritAttrts 可以控制是否繫結,看了下這個 api,是從 vue2.4 加入的,當時我已經不用 vue 了難怪不知道,但是這個正如我之前說的,必須要套一層 defineComponent 才能宣告,而且還要每次強制手動宣告,同時對於高階元件依舊沒什麼用。另外這個屬性在 vue2vue3 甚至會出現表現不一致的情況(2 這個無論怎麼設定不會影響 classstyle,而在 3 則會),這簡直無力吐槽… 真的是和 vue 對於 data 裡陣列的設計一樣迷。不知道這個屬性未來是否會做修改,感覺大機率不會。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章