Vue 初體驗(上)

jinzhuming發表於2020-10-09

背景

事實上我已經有幾年沒有使用 Vue 了,尤其是在 react釋出 hooks 之後,我已經全面轉入了 functional component 開發,好處是顯而易見的,不用再去頭疼 this new 之類的一系列問題了,也不用再去背下一個個不同又冗長的生命週期(我已經差不多忘了他們),我一直認為這樣的開發才是最自然最貼近 js 的狀態,只要掌握 js 即可,其他多餘的並不關心。當然了,麻煩也自然不少,尤其是 hooks 設計上帶來的一些新的問題(比如閉包),對於 js 老手而言自然不是問題,但是對於新手或者對於 hooks 不熟練的人,這就非常容易踩坑了。直到最近發現 vue3 似乎釋出了第一個正式版,Vue 官網的文件也正式更新了 3.0,早就有聽說 Vue在 3.0 也已經全面支援了 functional component,以及相對於 hooks 的「改進版」,所以這次就來體驗體驗。


實踐

首先依舊是 Vue cli 建立一個專案,我使用的是 vue ui,我一直覺得 vue 在這方面乾的不錯,vue ui 使用起來很方便。建立一個專案之後,我發現似乎並不能原生支援 tsx,查詢了一下,需要安裝@vue/babel-plugin-jsx 這個 babel 外掛,安裝之後就可以直接引入 tsx 檔案了,就像這樣。
code

這裡需要注意,Vue 取消了暴露 property ,這也就意味著以前很多庫和程式碼都無法正常使用,vue3 雖然大多數語法上都相容了 vue2,雖然我很支援取消對於 property 的支援,但是類似於這種破壞性更新也意味著最起碼短時間內 vue3 別期望能有太多庫支援了。我看了一下之前我用的 element ui,果然不支援了…
接著看一下 tsx,我試了一下基本和 react 沒有太大區別

export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <Hello />
    </div>
  )
}
function Hello() {
  return <div>hello</div>
}

沒錯這就是我想要的效果,像寫 js 一樣寫 UI,接著嘗試一下更新的新 api,因為目前關於 vue3 jsx 寫法文章並不多,以為 relative 應該等同於 reactuseState ,所以按照寫 react hooks 的方式嘗試寫了一下。

export const App = () => {
  const count = reactive({ value: 1 })
  return (
    <div style={{ color: “red” }}>
      <button
        onClick={() => {
          count.value += 1
        }}>
        +1
      </button>
      {count.value}
      <Hello />
    </div>
  )
}

這個時候發現頁面雖然能夠渲染出正確的值,但是點選之後 count 永遠都是 1,難道每次渲染 relative 都會重新生成值? 這怎麼和預想的不一樣。隨後查詢資料發現原來要這麼寫

export const App = defineComponent(() => {
  const count = reactive({ value: 1 })
  return () => (
    <div style={{ color: “red” }}>
      <button
        onClick={() => {
          count.value += 1
        }}>
        +1
      </button>
      {count.value}
    </div>
  )
})

沒錯,需要用 defineComponent 作為高階元件套一層,並且返回的需要是一個函式,而不是 react 那樣可以直接返回一個 dom。老實說這就很奇怪了,為什麼要套個 defineComponent,他做了什麼?查詢了一下原始碼,原來 defineComponent 並沒有實際意義,只是為了 Typescript 的型別推導。那麼如果目前我們不需要型別推導的話,大概這麼寫也是可以的

const count = reactive({ value: 1 })
export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <button
        onClick={() => {
          count.value += 1
        }}>
        +1
      </button>
      {count.value}
    </div>
  )
}

嘗試了一下,果然可以,並且也不會出現每次重新整理的時候 reactive 都重新生成一次的問題,思考了一下,這裡的 jsx 檔案應該等同於 vue 模板裡的 setupsetup 只生成一次,後續只重新渲染 setup 返回的函式(App),而不會重新渲染整個 setup(jsx檔案),所以我們只需要返回一個渲染 domrelative 應該定義在渲染的函式外部。
多個元件傳參則和 react 一樣,但是需要注意,這裡並沒有 reactchildren,依舊需要用第二個引數的 slots 來渲染子節點,這就讓我感覺很奇怪了,既然用 jsx,就算為了相容模板需要 slots,那也應該做一下 children 相容才對,這並不是一個很困難的事情,只需要把 children 指向 slots.default 即可,不知道以後的版本會不會做這個處理。

export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <button
        onClick={() => {
          count.value += 1
        }}>
        +1
      </button>
      <CountValue countValue={count.value}>你好</CountValue>
    </div>
  )
}

function CountValue(
  { countValue }: { countValue: number },
  { slots }: { slots: any },
) {
  return (
    <div>
      {slots.default()}
      {countValue}
    </div>
  )
}

demo
同時我發現這樣完全可以實現 Typescript 的型別推導
ts 型別推導

這就讓我很奇怪,為什麼明明可以實現型別推倒還需要加個 defineComponent 呢,隨後我去查了一下,原來 defineComponent 是這麼寫使用的
code
code

這個作者給出的寫法據說是 Vue 作者給出的推薦寫法,但是我並不太理解為什麼需要這麼寫,明明直接使用 jsx 函式就已經很舒服了,我個人推測 defineComponent 更多還是為了 vue 模板寫法或者這種物件的寫法而搞出的東西,如果純使用 jsxfunctional component 寫法,應該是不需要這個的。
接著我突然想到,如果是這麼寫,其實子元件父元件完全可有其他狀態傳遞方式,比如:

const count = reactive({ value: 1 })
export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <Button />
      <CountValue countValue={count.value}>你好</CountValue>
    </div>
  )
}

function CountValue(
  { countValue }: { countValue: number },
  { slots }: { slots: any },
) {
  return (
    <div>
      {slots.default()}
      {countValue}
    </div>
  )
}

function Button() {
  return (
    <button
      onClick={() => {
        count.value += 1
      }}>
      +1
    </button>
  )
}
export const App = () => {
  return (
    <div style={{ color: “red” }}>
      <Button />
      <CountValue />
    </div>
  )
}

function CountValue() {
  return (
    <div>
      {count.value}
    </div>
  )
}

function Button() {
  return (
    <button
      onClick={() => {
        count.value += 1
      }}>
      +1
    </button>
  )
}

疑惑

並且因為 Vue 的響應式原理,父元件在使用了 count 併發生變化的時候,並不會連帶著子元件 Button 也發生變化,Button 是不會重複 Render 的,但是每次點選卻能拿到最新的值,每次點選的時候只有 CountValue 這種實際渲染了值的元件在發生 Render,這意味著實際上 Vue 哪怕是用 jsx 寫法,也原生自帶了 shouldComponentUpdate 的最佳化?
這是否意味著,其實 relative 自帶一個全域性狀態?思考了一下,從 js 角度來講,確實是這樣,目前尚不得知這樣的寫法是否會有什麼坑,是否被官方推薦,畢竟目前的資料實在太少,可能還需要後續實踐這次就先寫到這吧。

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

相關文章