精读《Vue3.0 Function API》

1. 引言

Vue 3.0 的发布引发了轩然大波,让咱们解读下它的 function api RFC 详细了解一下 Vue 团队是怎么想的吧!前端

首先官方回答了几个最受关注的问题:vue

Vue 3.0 是否有 break change,就像 Python 3 / Angular 2 同样?react

不,100% 兼容 Vue 2.0,且暂未打算废弃任何 API(将来也不)。以前有草案试图这么作,但因为用户反馈太猛,被撤回了。git

Vue 3.0 的设计盖棺定论了吗?github

没有呀,此次精读的稿子就是 RFC(Request For Comments),翻译成中文就是 “意见征求稿”,还在征求你们意见中哦。npm

这 RFC 咋这么复杂?api

RFC 是写给贡献者/维护者的,要考虑许多边界状况与细节,因此固然会复杂不少喽!固然 Vue 自己使用起来仍是很简单的。性能优化

Vue 自己 Mutable + Template 就注定了是个用起来简单(约定 + 天然),实现起来复杂(解析 + 双绑)的框架。微信

此次改动很像在模仿 React,为啥不直接用 React?闭包

首先 Template 机制仍是没变,其次模仿的是 Hooks 而不是 React 所有,若是你不喜欢这个改动,那你更不会喜欢用 React。

PS: 问这个问题的人,必定没有同时理解 React 与 Vue,其实这两个框架到如今差异蛮大的,后面精读会详细说明。

下面正式进入 Vue 3.0 Function API 的介绍。

2. 概述

Vue 函数式基本 Demo:

<template>
  <div>
    <span>count is {{ count }}</span>
    <span>plusOne is {{ plusOne }}</span>
    <button @click="increment">count++</button>
  </div>
</template>

<script>
import { value, computed, watch, onMounted } from 'vue'

export default {
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}
</script>
复制代码

函数式风格的入口是 setup 函数,采用了函数式风格后能够享受以下好处:类型自动推导、减小打包体积。

setup 函数返回值就是注入到页面模版的变量。咱们也能够返回一个函数,经过使用 value 这个 API 产生属性并修改:

import { value } from 'vue'

const MyComponent = {
  setup(props) {
    const msg = value('hello')
    const appendName = () => {
      msg.value = `hello ${props.name}`
    }
    return {
      msg,
      appendName
    }
  },
  template: `<div @click="appendName">{{ msg }}</div>`
}
复制代码

要注意的是,value() 返回的是一个对象,经过 .value 才能访问到其真实值。

为什么 value() 返回的是 Wrappers 而非具体值呢?缘由是 Vue 采用双向绑定,只有对象形式访问值才能保证访问到的是最终值,这一点相似 React 的 useRef() API 的 .current 规则。

那既然全部 value() 返回的值都是 Wrapper,那直接给模版使用时要不要调用 .value 呢?答案是否认的,直接使用便可,模版会自动 Unwrapping:

const MyComponent = {
  setup() {
    return {
      count: value(0)
    }
  },
  template: `<button @click="count++">{{ count }}</button>`
}
复制代码

接下来是 Hooks,下面是一个使用 Hooks 实现得到鼠标实时位置的例子:

function useMouse() {
  const x = value(0)
  const y = value(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

// in consuming component
const Component = {
  setup() {
    const { x, y } = useMouse()
    const { z } = useOtherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
复制代码

能够看到,useMouse 将全部与 “处理鼠标位置” 相关的逻辑都封装了进去,乍一看与 React Hooks 很像,可是有两个区别:

  1. useMouse 函数内改变 xy 后,不会从新触发 setup 执行。
  2. x y 拿到的都是 Wrapper 而不是原始值,且这个值会动态变化。

另外一个重要 API 就是 watch,它的做用相似 React Hooks 的 useEffect,但实现原理和调用时机其实彻底不同。

watch 的目的是监听某些变量变化后执行逻辑,好比当 id 变化后从新取数:

const MyComponent = {
  props: {
    id: Number
  },
  setup(props) {
    const data = value(null)
    watch(() => props.id, async (id) => {
      data.value = await fetchData(id)
    })
  }
}
复制代码

之因此要 watch,由于在 Vue 中,setup 函数仅执行一次,因此不像 React Function Component,每次组件 props 变化都会从新执行,所以不管是在变量、props 变化时若是想作一些事情,都须要包裹在 watch 中。

后面还有 unwatching、生命周期函数、依赖注入,都是一些语法定义,感兴趣能够继续阅读原文,笔者就不赘述了。

3. 精读

对于 Vue 3.0 的 Function API + Hooks 与 React Function Component + Hooks,笔者作一些对比。

Vue 与 React 逻辑结构

React Function Component 与 Hooks,虽然在实现原理上,与 Vue3.0 存在 Immutable 与 Mutable、JSX 与 Template 的区别,但逻辑理解上有着相通之处。

const MyComponent = {
  setup(props) {
    const x = value(0)

    const setXRandom = () => {
      x.value = Math.random()
    }

    return { x, setXRandom }
  },
  template: ` <button @onClick="setXRandom"/>{{x}}</button> `
}
复制代码

虽然在 Vue 中,setup 函数仅执行一次,看上去与 React 函数彻底不同(React 函数每次都执行),但其实 Vue 将渲染层(Template)与数据层(setup)分开了,而 React 合在了一块儿。

咱们能够利用 React Hooks 将数据层与渲染层彻底隔离:

// 相似 vue 的 setup 函数
function useMyComponentSetup(props) {
  const [x, setX] = useState(0)

  const setXRandom = useCallback(() => {
    setX(Math.random())
  }, [setX])

  return { x, setXRandom }
}

// 相似 vue 的 template 函数
function MyComponent(props: { name: String }) {
  const { x, setXRandom } = useMyComponentSetup(props)

  return (
    <button onClick={setXRandom}>{x}</button>
  )
}
复制代码

这源于 JSX 与 Template 的根本区别。JSX 使模版与 JS 能够写在一块儿,所以数据层与渲染层能够耦合在一块儿写(也能够拆分),但 Vue 采起的 Template 思路使数据层强制分离了,这也使代码分层更清晰了。

而实际上 Vue3.0 的 setup 函数也是可选的,再配合其支持的 TSX 功能,与 React 真的只有 Mutable 的区别了:

// 这是个 Vue 组件
const MyComponent = createComponent((props: { msg: string }) => {
  return () => h('div', props.msg)
})
复制代码

咱们很难评价 Template 与 JSX 的好坏,但为了更透彻的理解 Vue 与 React,须要抛开 JSX&Template,Mutable&Immutable 去看,其实去掉这两个框架无关的技术选型,React@16 与 Vue@3 已经很是像了。

Vue3.0 的精髓是学习了 React Hooks 概念,所以正好能够用 Hooks 在 React 中模拟 Vue 的 setup 函数。

关于这两套技术选型,已是相对完美的组合,不建议在 JSX 中再实现相似 Mutable + JSX 的花样来(由于喜欢 Mutable 能够用 Vue 呀):

  • Vue:Mutable + Template
  • React:Immutable + JSX

真正影响编码习惯的就是 Mutable 与 Immutable,使用 Vue 就坚决使用 Mutable,使用 React 就坚决使用 Immutable,这样能最大程度发挥两套框架的价值。

Vue Hooks 与 React Hooks 的差别

先看 React Hooks 的简单语法:

const [ count, setCount ] = useState(0)

const setToOne = () => setCount(1)
复制代码

Vue Hooks 的简单语法:

const count = value(0)

const setToOne = () => count.value = 1
复制代码

之因此 React 返回的 count 是一个数字,是由于 Immutable 规则,而 Vue 返回的 count 是个对象,拥有 count.value 属性,也是由于 Vue Mutable 规则致使,这使得 Vue 定义的全部变量都相似 React 中 useRef 定义变量,所以不存 React capture value 的特性。

关于 capture value 更多信息,能够阅读 精读《Function VS Class 组件》 Capute Value 介绍

另外,对于 Hooks 的值变动机制也不一样,咱们看 Vue 的代码:

const Component = {
  setup() {
    const { x, y } = useMouse()
    const { z } = useOtherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
复制代码

因为 setup 函数仅执行一次,怎么作到当 useMouse 致使 xy 值变化时,能够在 setup 中拿到最新的值?

在 React 中,useMouse 若是修改了 x 的值,那么使用 useMouse 的函数就会被从新执行,以此拿到最新的 x,而在 Vue 中,将 Hooks 与 Immutable 深度结合,经过包装 x.value,使得当 x 变动时,引用保持不变,仅值发生了变化。因此 Vue 利用 Proxy 监听机制,能够作到 setup 函数不从新执行,但 Template 从新渲染的效果。

这就是 Mmutable 的好处,Vue Hooks 中,不须要 useMemo useCallback useRef 等机制,仅需一个 value 函数,直观的 Mutable 修改,就能够实现 React 中一套 Immutable 性能优化后的效果,这个是 Mutable 的魅力所在。

Vue Hooks 的优点

笔者对 RFC 中对 Vue、React Hooks 的对比作一个延展解释:

首先最大的不一样:setup 仅执行一遍,而 React Function Component 每次渲染都会执行。

Vue 的代码使用更符合 JS 直觉。

这句话直截了当戳中了 JS 软肋,JS 并不是是针对 Immutable 设计的语言,因此 Mutable 写法很是天然,而 Immutable 的写法就比较别扭。

当 Hooks 要更新值时,Vue 只要用等于号赋值便可,而 React Hooks 须要调用赋值函数,当对象类型复杂时,还需借助第三方库才能保证进行了正确的 Immutable 更新。

对 Hooks 使用顺序无要求,并且能够放在条件语句里。

对 React Hooks 而言,调用必须放在最前面,并且不能被包含在条件语句里,这是由于 React Hooks 采用下标方式寻找状态,一旦位置不对或者 Hooks 放在了条件中,就没法正确找到对应位置的值。

而 Vue Function API 中的 Hooks 能够放在任意位置、任意命名、被条件语句任意包裹的,由于其并不会触发 setup 的更新,只在须要的时候更新本身的引用值便可,而 Template 的重渲染则彻底继承 Vue 2.0 的依赖收集机制,它无论值来自哪里,只要用到的值变了,就能够从新渲染了。

不会再每次渲染重复调用,减小 GC 压力。

这确实是 React Hooks 的一个问题,全部 Hooks 都在渲染闭包中执行,每次重渲染都有必定性能压力,并且频繁的渲染会带来许多闭包,虽然能够依赖 GC 机制回收,但会给 GC 带来不小的压力。

而 Vue Hooks 只有一个引用,因此存储的内容就很是精简,也就是占用内存小,并且当值变化时,也不会从新触发 setup 的执行,因此确实不会形成 GC 压力。

必需要总包裹 useCallback 函数确保不让子元素频繁重渲染。

React Hooks 有一个问题,就是彻底依赖 Immutable 属性。而在 Function Component 内部建立函数时,每次都会建立一个全新的对象,这个对象若是传给子组件,必然致使子组件没法作性能优化。 所以 React 采起了 useCallback 做为优化方案:

const fn = useCallback(() => /* .. */, [])
复制代码

只有当第二个依赖参数变化时才返回新引用。但第二个依赖参数须要 lint 工具确保依赖老是正确的(关于为什么要对依赖诚实,感兴趣能够移步 精读《Function Component 入门》 - 永远对依赖诚实)。

回到 Vue 3.0,因为 setup 仅执行一次,所以函数自己只会建立一次,不存在多实例问题,不须要 useCallback 的概念,更不须要使用 lint 插件 保证依赖书写正确,这对开发者是实实在在的友好。

不须要使用 useEffect useMemo 等进行性能优化,全部性能优化都是自动的。

这也是实在话,毕竟 Mutable + 依赖自动收集就能够作到最小粒度的精确更新,根本不会触发没必要要的 Rerender,所以 useMemo 这个概念也不须要了。

useEffect 也须要传递第二个参数 “依赖项”,在 Vue 中根本不须要传递 “依赖项”,因此也不会存在用户不当心传错的问题,更不须要像 React 写一个 lint 插件保证依赖的正确性。(这也是笔者想对 React Hooks 吐槽的点,React 团队如何保障每一个人都安装了 lint?就算装了 lint,若是 IDE 有 BUG,致使没有生效,随时可能写出依赖不正确的 “危险代码”,形成好比死循环等严重后果)

4. 总结

经过对比 Vue Hooks 与 React Hooks 能够发现,Vue 3.0 将 Mutable 特性完美与 Hooks 结合,规避了一些 React Hooks 的硬伤。因此咱们能够说 Vue 借鉴了 React Hooks 的思想,但创造出来的确实一个更精美的艺术品。

但 React Hooks 遵循的 Immutable 也有好的一面,就是每次渲染中状态被稳定的固化下来了,不用担忧状态忽然变动带来的影响(其实反而要注意状态用不变动带来的影响),对于数据记录、程序运行的稳定性都有较高的可预期性。

最后,对于喜欢 Mutable 的开发者,Vue 3.0 是你的最佳选择,基于 React + Mutable 搞的一些小轮子作到顶级可能还不如 Vue 3.0。对于 React 开发者来讲,坚持大家的 Immutable 信仰吧,Vue 3.0 已经将 Mutable 发挥到极致,只有将 React Immutable 特性发挥到极致才能发挥 React 的最大价值。

讨论地址是:精读《Vue3.0 Function API》 · Issue #173 · dt-fe/weekly

若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证

相关文章
相关标签/搜索