——————————————————————

诱因

首先 setup 函数有冗长返回语句的问题已经在

<script setup>

提案中作出了很棒的解决。这一点我相信随着时间推移会有越来越多的人认同并且实践。该提案已经被官方确定并作为下个版本的正式 Feature 了,所以即使你在 vite 的控制台打印信息里看到了 experimental 也无需担心了。

至于我们为什么要用 setup,我想既然同学们要了解 Vue 3,应该对这个大版本要做和已经的事情有一个更全面的认知:

Vue 3 的任务是:补短板 + 提上限

短板何在?

在今天我们看到的大部分较大的国内互联网公司所提供的 Web 产品中,使用的构建框架都是 React,我自己是字节,而我惊奇的发现 VueConf 2021 上这么多位分享的大佬竟是出自我们这个用 React 更多的公司,有同学玩笑戏称 “字节把懂 Vue 的都抓去写 React 了”。

而我之前在腾讯微信支付数据中心实习过一小段时间,那里有一些内部平台系统为了开发快速简单,选择了容易上手的 Vue,但普遍项目量级都还不算特别大。

一个令人好奇的,与此相关的问题出现了:为什么大公司不敢用 Vue ?

据我自己的体验来看,可以分为以下两点,这应该就是 Vue 过去的短板:

  1. 2.x 版本对 TypeScript 的支持是硬伤,而 TypeScript 对大型项目的保障能力是被普遍认可的,看到 Vue 没法支持,在选择技术栈时很容易放弃它。
  2. 一旦项目体量变大,Vue 的代码会变得更难以维护,真正在实践中 Options API 虽然在组件层面上,每个内容的职责都很清晰,这是 data,那是 method,但是从跨组件的角度来看就没那么好了,因此 Vue 2 缺少一种真正更好的抽象逻辑的办法,而非将代码搬到独立的 .js 文件里来缩减 SFC 代码行数。

解决这两个问题的思路也很明确,尤大也在很多视频演讲中提到了:

  1. 函数是对类型最友好的,输入、输出的类型都是确定的,易推导的。若要这样做,显然是用 TypeScript 重写一遍 Vue 更好。
  2. 虽然我们的页面内容被划分成了一个个的组件,但是我们思考的逻辑却不应该僵死地被他们框住,不需要在每个组件中罗列他们本身的职责,而是让组件去适配、去载入我们可多处复用的逻辑。Vue 需要为开发者提供一套新的框架内 API,使得程序员们能创造出更容易复用的 “业务 API”。若要这样做,显然需要一套新的函数式响应式 API,这些东西将成为我们书写业务逻辑的 “原语”

这些更新内容都在 Vue 3 中完成了,通过一些社区中新的插件对 TypeScript 集成,我们还获得了更好的 DX 体验。

总结

所以请题主莫要纠结于 setup 本身,因为把所有逻辑不加抽象、不加简化地都写在它其中,本就是不对的。setup 只是为我上面说的 组件载入逻辑 提供了一个入口,而你不应该把所有东西都摆在门口。

Options API 对一些小的,功能简单纯粹且独特的组件还是能用上的。另外如果你喜欢它,Vue 也没有删掉对其的支持。

——————————————————————

看了一圈大家的回答,还有题主本身对 “ 请看提问描述第一句 ” 的诉苦,我想说,教他 具体问题具体分析 你直接说这句话就行了,因为这就是真理,既然你懂得它,就大方地和身边滥用或错用功能的同学交流即可。

如果是想认认真真写很多代码,那就用 composition API 好好组织设计,如果只是单纯糊几个业务,options API 也能 handle 住。

——————————————————————

对产品来说 “分层大于组件化”。组件化对基础设施(组件库) 开发者有绝对重要的意义,对业务开发者却很有限,相反前端框架在业务的分层上却始终没找到什么好办法。

以后端 spring 开发来举例,业务开发人员写一个 user 模块,顺序一般是 UserBean -> UserDao -> UserService -> UserController, 他们会按照分层的方式来写。如果是前端写一个 user 模块, 通常顺序是 userList -> userAdd -> userEdit -> userDel, 而在每一个 userXxx 功能内,有些做得比较好的,又会拆成事件处理函数、api 函数、computed 函数, 这样实际上每个组件内部又维护一套 mvc 的结构。这样的做法对抽象和重构是有很大阻碍的

就我个人的理解,用 setup 写,最容易把业务内容抽出来成为一个 userService,能够从面向领域模型的方向上思考问题。这种方式对大型业务的组织是有好处的。

——————————————————————

你的业务组件内 setup 函数应该只用于初始化这个组件

setup() {
    const form = useForm()
    const v$ = useVuelidate(form.data, rules)
    const table = useTable()
    const pagination = usePagination(table)

    init(form,table,pagination)

    return {
        ...form,
        v$,
        ...table,
        ...pagination
    }
}

阅读代码只需要阅读各个 hooks

看起来你还没有这样做,那么看到这里你应该悟了,可以试试更换一下写业务组件的思路了

——————————————————————

vue3 的 setup 分两个阶段:

阶段一:

export default {
    props: ['user'],

    data() {
      return {
        repositories: [],
        filters: {},
        searchQuery: '',
      }
    },

    computed: {
      filteredRepositories() {},
      repositoriesMatchingSearchQuery() {},
    },

    watch: {
      user: 'getUserRepositories',
    },

    methods: {
      getUserRepositories() {},
      updateFilters() {},
    },
    mounted() {
      this.getUserRepositories()
    },
  }

阶段二 (改进):

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'

// 在我们的组件中
setup (props) {
  // 使用 `toRefs` 创建对 props 中的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在 user prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })

  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

在阶段一的时候, 虽然有不少的好处 (比如组合式什么的), 但却有很多让人感觉上不爽的地方, 有些操作感觉上像脱裤子放屁, 多此一举.

不过在阶段二的改进中, 这些问题得了很大的改进.

就本人来说, 一开始从 VUE2 转到 VUE3 的阶段一, 有点不习惯, 后来慢慢也离不开了. 到了阶段二, 感觉更是比起以前来方便多.

——————————————————————

现在使用vue3只有一个感受,封装hooks真的会上瘾 。

——————————————————————