面向將來編程,如何在 Vue2 中使用 Vue3 的語法[實踐篇]

封面圖來源網上,侵刪javascript

面向將來編程(Future-Oriented Programming),vue-function-api 提供開發者在 Vue2.x 中使用 Vue3 的語法邏輯開發應用。(爲方便如下以 Vue2 表示)html

本文不對文檔 api 對過多說明,僅討論在項目實踐中遇到的問題。比較二者的區別是對 Vue3 寫法最快的瞭解,下面經過對比同一個功能在 Vue2 與 Vue3 的區別。vue

場景是這樣的,一個列表頁,上部分是條件篩選,下部分是 table,table 行內會有刪除,修改(以 dialog 形式展現)功能,template 部分無區別,這裏不作贅述。java

準備工做

安裝 vue-function-apireact

npm install vue-function-api --save
 # 或者
yarn add vue-function-api
複製代碼

在項目中引入 main.jsgit

import Vue from 'vue'
import Element from 'element-ui'
import { plugin } from 'vue-function-api' // <--- 引入 vue-function-api

import App from './App'
import router from './router'
import store from './store'

Vue.use(Element)
Vue.use(plugin) // <--- 這樣就能夠在單文件組件中使用函數式 API了

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
複製代碼

準備工做後,就能夠安安靜靜的作比較了。github

data

在 Vue2 中,首先要定義 data, data 中咱們定義一個 query 對象用於條件篩選,citys 用於條件篩選中城市(動態獲取下拉),statusMap 用於篩選條件中狀態的下拉,list 用於 table。vue-router

export default {
  data() {
    return {
      query: {
        city: null,
        name: null,
        status: null
      },
      citys: [],
      list: [],
      statusMap:  [{
        value: 1,
        label: '啓用'
      }, {
        value: 2,
        label: '禁用'
      }]
    }
  }
複製代碼

Vue3 中是這樣定義vuex

import { value } from 'vue-function-api'

export default {
  setup() {
    const query = value({
      city: null,
      name: null,
      status: null
    })
    const citys = value([])
    const list = value([])
    const statusMap = value([{
      value: 1,
      label: '啓用'
    }, {
      value: 2,
      label: '禁用'
    }])

    return {
      query,
      citys,
      list,
      statusMap
    }
  }
}
複製代碼

引入 setup, 在其中作 data 作的事情,細心的同窗會發如今 setup 後須要 return 所定義的變量,其中也並不是要將全部的變量都放入 return 中,放入 return 中是爲了可以在 template 中使用,即只需 return template 中依賴的變量shell

computed

上文中說到 table 中有刪除,編輯功能,正常的系統中這類功能是須要有權限的人才能操做,這裏咱們假設已經將 permissions 存入 vuex 中,使用時須要經過 mapGetters 取出 permissions(此處爲舉例這樣實現,正常的權限要比這個複雜)。

在 Vue2 中是這樣寫

import { mapGetters } from 'vuex'

export default {
  // 此處先省略 data 部分...
  computed: {
    ...mapGetters(['permissions']),
    canUpdate() {
      return this.permissions.includes('update')
    },
    canDelete() {
      return this.permissions.includes('delete')
    },
  }
  // ...
}

複製代碼

Vue3:目前 vue-function-api 還不支持 vuex map 的方式 導出 vuex 中的狀態,這裏咱們從 Vue root 中取出 store 中的數據

import { computed } from 'vue-function-api'

export default {
  setup(props, ctx) {
    const { $store } = ctx.root
    // 此處先省略 data 部分...

    const permissions = computed(() => $store.getters.permissions)
    const canUpdate = () => permissions.includes('update')
    const canDelete = () => permissions.includes('delete')

    return {
      canUpdate,
      canDelete // <--- 這裏只導出 template 的依賴
    }
  }
}
複製代碼

在 template 中, 編輯,刪除的按鈕上 分別加入 v-if="canUpdate", v-if="canDelete",便可實現對應權限的人才能看到對應的按鈕,瞭解更多 context

methods & lifecycle

基於上文中的描述,由於是一個列表頁,因此結合 lifecycle 一塊兒作一下對比。

在 Vue2 中

export default {
  // ...

  methods: {
    async fetchCity() {
      const response = await fetchCityApi()
      this.citys = response.data
    },

    async fetchList() {
      const response = await fetchListApi(this.query)
      this.list = response.data
    },

    async delete(id) {
      const response = await deleteItem(id)
      const { status, msg } = response.data
      if (status !== 'ok') return this.$message.error(msg)
      this.$message({
        type: 'success',
        message: '刪除成功'
      })
    },

    confirm(id) {
      this.$confirm(`確認刪除`, '提示', {
        confirmButtonText: '肯定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.delete(id)
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消'
        })
      })
    },

    detail(id) {
      this.$router.push({
        path: '/detail',
        query: { id }
      })
    }
  }
}

  created() {
    this.fetchCity()
    this.fetchList()
  }
}
複製代碼

接下來看 Vue3 中實現

import { computed, onCreated } from 'vue-function-api'

export default {
  setup(props, ctx) {
    const { $message, $router, $confirm, $store } = ctx.root // <--- setup() 中不可使用 this 訪問當前組件實例, 能夠經過 setup 的第二個參數 context 來訪問 vue2.x API 中實例上的屬性。
    // 此處先省略 data 部分...

    // method
    const fetchCity = async () => {
      const response = await fetchCityApi()
      citys.value = response.data // <--- 劃重點,經過 value() wrap初始化的變量在 setup 內部引用值或者修改值都要加 .value
    },

    const fetchList = async () => {
      const response = await fetchListApi(query.value)
      list.value = response.data
    },

    const delete = async id => {
      const response = await deleteItem(id)
      const { status, msg } = response.data
      if (status !== 'ok') return $message.error(msg)
      $message({
        type: 'success',
        message: '刪除成功'
      })
    },

    confirm(id) {
      $confirm(`確認刪除`, '提示', {
        confirmButtonText: '肯定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delete(id)
      }).catch(() => {
        $message({
          type: 'info',
          message: '已取消'
        })
      })
    },

    detail(id) {
      $router.push({
        path: '/detail',
        query: { id }
      })
    }

    // lifecycle
    onCreated(() => {
      fetchCity()
      fetchList()
    })

    return {
      fetchCity,
      fetchList,
      confirm,
      detail // <--- 這裏只導出 template 的依賴
    }
  }
}
複製代碼

是否是感受切換到 Vue3 也沒那麼難

watch

watch 的例子在一個列表頁中可能沒有什麼使用場景,上文中說到列表頁上面是一個條件篩選,有城市下拉,狀態下拉,固然正常狀況下,咱們都是經過 select 的 change 事件調用 fetchList,這裏我就爲了舉例而這麼寫,實際項目本身衡量利弊。

在 Vue2 中

export default {
  // ...

  watch: {
    query: {
      handler: function(val) {
        this.fetchList()
      },
      { deep: true }
    }
  }

  // 或者不嫌麻煩這樣寫...
  watch: {
    'query.city': function(val) {
      this.fetchList()
    },
    'query.name': function(val) {
      this.fetchList()
    },
    'query.status': function(val) {
      this.fetchList()
    }
  }

  // ...
}
複製代碼

在 Vue3 中

import { watch } from 'vue-function-api'

export default {
  setup() {
    // ...

    watch(
      query,
      val => {
        fetchList()
      },
      { deep: true }
    )

    // 或者

    watch(
      () => query.value,
      val => {
        fetchList()
      },
      { deep: true }
    )

    // ...
  }
}
複製代碼

經過以上對比,你應該對 Vue3 寫法有必定理解了,由於是以一個列表頁爲 demo對比,沒有用到 provideinject,想了解的同窗能夠到 provide, inject 還有 state 至關於 Vue.observable.

對比完整代碼

Vue2

export default {
  data() {
    return {
      query: {
        city: null,
        name: null,
        status: null
      },
      citys: [],
      list: [],
      statusMap:  [{
        value: 1,
        label: '啓用'
      }, {
        value: 2,
        label: '禁用'
      }]
    }
  },
  computed: {
    ...mapGetters(['permissions']),
    canUpdate() {
      return this.permissions.includes('update')
    },
    canDelete() {
      return this.permissions.includes('delete')
    },
  },
  methods: {
    async fetchCity() {
      const response = await fetchCityApi()
      this.citys = response.data
    },

    async fetchList() {
      const response = await fetchListApi(this.query)
      this.list = response.data
    },

    async delete(id) {
      const response = await deleteItem(id)
      const { status, msg } = response.data
      if (status !== 'ok') return this.$message.error(msg)
      this.$message({
        type: 'success',
        message: '刪除成功'
      })
    },

    confirm(id) {
      this.$confirm(`確認刪除`, '提示', {
        confirmButtonText: '肯定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.delete(id)
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消'
        })
      })
    },

    detail(id) {
      this.$router.push({
        path: '/detail',
        query: { id }
      })
    }
  },
  created() {
    this.fetchCity()
    this.fetchList()
  },
  watch: {
    query: {
      handler: function(val) {
        this.fetchList()
      },
      { deep: true }
    }
  }

複製代碼

Vue3

import { value, computed, onCreated } from 'vue-function-api'

export default {
  setup(props, ctx) {
    const { $store, $message, $router, $route } = ctx.root
    // reactive state
    const query = value({
      city: null,
      name: null,
      status: null
    })
    const citys = value([])
    const list = value([])
    const statusMap = value([{
      value: 1,
      label: '啓用'
    }, {
      value: 2,
      label: '禁用'
    }])

    // computed
    const permissions = computed(() => $store.getters.permissions)
    const canUpdate = () => permissions.includes('update')
    const canDelete = () => permissions.includes('delete')

    // method
    const fetchCity = async () => {
      const response = await fetchCityApi()
      citys.value = response.data
    },

    const fetchList = async () => {
      const response = await fetchListApi(query.value)
      list.value = response.data
    },

    const delete = async id => {
      const response = await deleteItem(id)
      const { status, msg } = response.data
      if (status !== 'ok') return $message.error(msg)
      $message({
        type: 'success',
        message: '刪除成功'
      })
    },

    confirm(id) {
      $confirm(`確認刪除`, '提示', {
        confirmButtonText: '肯定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delete(id)
      }).catch(() => {
        $message({
          type: 'info',
          message: '已取消'
        })
      })
    },

    detail(id) {
      $router.push({
        path: '/detail',
        query: { id }
      })
    }

    // watch
    watch(
      query,
      val => {
        fetchList()
      },
      { deep: true }
    )

    // lifecycle
    onCreated(() => {
      fetchCity()
      fetchList()
    })

    return {
      query,
      citys,
      list,
      statusMap,
      canUpdate,
      canDelete,
      fetchCity,
      fetchList,
      confirm,
      detail
    }
  }
}
複製代碼

最後

這裏貼一下 setup 中的第二個參數 context,對象中的屬性是 2.x 中的 vue 實例屬性的一個子集。完整的屬性列表:

  • parent
  • root
  • refs
  • slots
  • attrs
  • emit

像其餘庫 vuex,vue-router,ElementUI 的一些 $方法能夠從 root 中取得

export default {
  setup(props, ctx) {
    const { $store, $router, $route, $message, $confirm } = ctx.root
  }
}
複製代碼

最後的最後

筆者已用到正式環境,目前來看尚未什麼問題,引用 vue-function-api 官方的說明

vue-function-api 會一直保持與 Vue3.x 的兼容性,當 3.0 發佈時,您能夠無縫替換掉本庫。 vue-function-api 的實現只依賴 Vue2.x 自己,不論 Vue3.x 的發佈與否,都不會影響您正常使用本庫。 因爲 Vue2.x 的公共 API 限制,vue-function-api 沒法避免的會產生一些額外的內存負載。若是您的應用並不工做在極端內存環境下,無需關心此項。

Vue Function API
經過基於函數的 API 來複用組件邏輯

相關文章
相關標籤/搜索