Vue 3.0 前瞻Vue Function API新特性体验


Posted in Javascript onAugust 12, 2019

最近 Vue 官方公布了 Vue 3.0 最重要的RFC:Function-based component API,并发布了兼容 Vue 2.0 版本的 plugin:vue-function-api,可用于提前体验 Vue 3.0 版本的 Function-based component API。笔者出于学习的目的,提前在项目中尝试了vue-function-api。

笔者计划写两篇文章,本文为笔者计划的第一篇,主要为笔者在体验 Vue Function API 的学习心得。第二篇计划写阅读vue-function-api的核心部分代码原理,包括setup、observable、lifecycle。

本文阅读时间约为15~20分钟。

概述

Vue 2.x 及以前的高阶组件的组织形式或多或少都会面临一些问题,特别是在需要处理重复逻辑的项目中,一旦开发者组织项目结构组织得不好,组件代码极有可能被人诟病为“胶水代码”。而在 Vue 2.x 及之前的版本,解决此类问题的办法大致是下面的方案:

  • mixin
  • 函数式组件
  • slots

笔者维护的项目也需要处理大量复用逻辑,在这之前,笔者一直尝试使用mixin的方式来实现组件的复用。有些问题也一直会对开发者和维护者造成困惑,如一个组件同时mixin多个组件,很难分清对应的属性或方法写在哪个mixin里。其次,mixin的命名空间冲突也可能造成问题。难以保证不同的mixin不用到同一个属性名。为此,官方团队提出函数式写法的意见征求稿,也就是RFC:Function-based component API。使用函数式的写法,可以做到更灵活地复用组件,开发者在组织高阶组件时,不必在组件组织上考虑复用,可以更好地把精力集中在功能本身的开发上。

注:本文只是笔者使用vue-function-api提前体验 Vue Function API ,而这个 API 只是 Vue 3.0 的 RFC,而并非与最终 Vue 3.x API 一致。发布后可能有不一致的地方。

在 Vue 2.x 中使用

要想提前在Vue 2.x中体验 Vue Function API ,需要引入vue-function-api,基本引入方式如下:

import Vue from 'vue';
import { plugin as VueFunctionApiPlugin } from 'vue-function-api';

Vue.use(VueFunctionApiPlugin);

基本组件示例

先来看一个基本的例子:

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

<script>
import Vue from 'vue';
import { value, computed, watch, onMounted } from 'vue-function-api';

export default {
  setup(props, context) {
    // 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函数是Vue Function API 构建的函数式写法的主逻辑,当组件被创建时,就会被调用,函数接受两个参数,分别是父级组件传入的props和当前组件的上下文context。看下面这个例子,可以知道在context中可以获取到下列属性值:

const MyComponent = {
  props: {
    name: String
  },
  setup(props, context) {
    console.log(props.name);
    // context.attrs
    // context.slots
    // context.refs
    // context.emit
    // context.parent
    // context.root
  }
}

value & state

value函数创建一个包装对象,它包含一个响应式属性value:

Vue 3.0 前瞻Vue Function API新特性体验

那么为何要使用value呢,因为在JavaScript中,基本类型并没有引用,为了保证属性是响应式的,只能借助包装对象来实现,这样做的好处是组件状态会以引用的方式保存下来,从而可以被在setup中调用的不同的模块的函数以参数的形式传递,既能复用逻辑,又能方便地实现响应式。

直接获取包装对象的值必须使用.value,但是,如果包装对象作为另一个响应式对象的属性,Vue内部会通过proxy来自动展开包装对象。同时,在模板渲染的上下文中,也会被自动展开。

import { state, value } from 'vue-function-api';
const MyComponent = {
  setup() {
    const count = value(0);
    const obj = state({
      count,
    });
    console.log(obj.count) // 作为另一个响应式对象的属性,会被自动展开

    obj.count++ // 作为另一个响应式对象的属性,会被自动展开
    count.value++ // 直接获取响应式对象,必须使用.value

    return {
      count,
    };
  },
  template: `<button @click="count++">{{ count }}</button>`,
};

如果某一个状态不需要在不同函数中被响应式修改,可以通过state创建响应式对象,这个state创建的响应式对象并不是包装对象,不需要使用.value来取值。

watch & computed

watch和computed的基本概念与 Vue 2.x 的watch和computed一致,watch可以用于追踪状态变化来执行一些后续操作,computed用于计算属性,用于依赖属性发生变化进行重新计算。

computed返回一个只读的包装对象,和普通包装对象一样可以被setup函数返回,这样就可以在模板上下文中使用computed属性。可以接受两个参数,第一个参数返回当前的计算属性值,当传递第二个参数时,computed是可写的。

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

const count = value(0);
const countPlusOne = computed(() => count.value + 1);

console.log(countPlusOne.value); // 1

count.value++;
console.log(countPlusOne.value); // 2

// 可写的计算属性值
const writableComputed = computed(
  // read
  () => count.value + 1,
  // write
  val => {
    count.value = val - 1;
  },
);

watch第一个参数和computed类似,返回被监听的包装对象属性值,不过另外需要传递两个参数:第二个参数是回调函数,当数据源发生变化时触发回调函数,第三个参数是options。其默认行为与 Vue 2.x 有所不同:

  • lazy:是否会在组件创建时就调用一次回调函数,与 Vue 2.x 相反,lazy默认是false,默认会在组件创建时调用一次。
  • deep:与 Vue 2.x 的 deep 一致
  • flush:有三个可选值,分别为 'post'(在渲染后,即nextTick后才调用回调函数),'pre'(在渲染前,即nextTick前调用回调函数),'sync'(同步触发)。默认值为'post'。
// double 是一个计算包装对象
const double = computed(() => count.value * 2);

watch(double, value => {
  console.log('double the count is: ', value);
}); // -> double the count is: 0

count.value++; // -> double the count is: 2

当watch多个被包装对象属性时,参数均可以通过数组的方式进行传递,同时,与 Vue 2.x 的vm.$watch一样,watch返回取消监听的函数:

const stop = watch(
  [valueA, () => valueB.value],
  ([a, b], [prevA, prevB]) => {
    console.log(`a is: ${a}`);
    console.log(`b is: ${b}`);
  }
);
stop();

注意:在RFC:Function-based component API初稿中,有提到effect-cleanup,是用于清理一些特殊情况的副作用的,目前已经在提案中被取消了。

生命周期

所有现有的生命周期都有对应的钩子函数,通过onXXX的形式创建,但有一点不同的是,destoryed钩子函数需要使用unmounted代替:

import { onMounted, onUpdated, onUnmounted } from 'vue-function-api';

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!');
    });
    onUpdated(() => {
      console.log('updated!');
    });
    // destroyed 调整为 unmounted
    onUnmounted(() => {
      console.log('unmounted!');
    });
  },
};

一些思考

上面的详解部分,主要抽取的是 Vue Function API 的常见部分,并非RFC:Function-based component API的全部,例如其中的依赖注入,TypeScript类型推导等优势,在这里,由于篇幅有限,想要了解更多的朋友,可以点开RFC:Function-based component API查看。个人也在Function-based component API讨论区看到了更多地一些意见:

  • 由于底层设计,在setup取不到组件实例this的问题,这个问题在笔者尝试体验时也遇到了,期待正式发布的 Vue 3.x 能够改进这个问题。
  • 对于基本类型的值必须使用包装对象的问题:在 RFC 讨论区,为了同时保证TypeScript类型推导、复用性和保留Vue的数据监听,包装属性必须使用.value来取值是讨论最激烈的
  • 关于包装对象value和state方法命名不清晰可能导致开发者误导等问题,已经在Amendment proposal to Function-based Component API这个提议中展开了讨论:
setup() {
  const state = reactive({
    count: 0,
  });

  const double = computed(() => state.count * 2);

  function increment() {
    state.count++;
  }

  return {
    ...toBindings(state), // retains reactivity on mutations made to `state`
    double,
    increment,
  };
}
  • 引入reactive API 和 binding API,其中reactive API 类似于 state API , binding API 类似于 value API。
  • 之前使用的方法名state在 Vue 2.x 中可能被用作组件状态对象,导致变量命名空间的冲突问题,团队认为将state API 更名为 reactive 更为优雅。开发者能够写出const state = ... ,然后通过state.xxxx这种方式来获取组件状态,这样也相对而言自然一些。
  • value方法用于封装基本类型时,确实会出现不够优雅的.value的情况,开发者可能会在直接对包装对象取值时忘记使用.value,修正方案提出的 reactive API,其含义是创建响应式对象,初始化状态state就使用reactive创建,可保留每项属性的getter和setter,这么做既满足类型推导,也可以保留响应式引用,从而可在不同模块中共享状态值的引用。
  • 但reactive可能导致下面的问题,需要引入binding API。 解决,如使用reactive创建的响应式对象,对其使用拓展运算符...时,则会丢失对象的getter和setter,提供toBindings方法能够保留状态的响应式。

下一篇文章中,笔者将阅读vue-function-api的核心部分代码原理,包括setup、observable、lifecycle等,从内部探索 Vue Function API 可能带给我们的改变。

当然,目前 Vue Function API 还处在讨论阶段,Vue 3.0 还处在开发阶段,还是期待下半年 Vue 3.0 的初版问世吧,希望能给我们带来更多的惊喜。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
禁止js文件缓存的代码
Apr 09 Javascript
AspNet中使用JQuery boxy插件的确认框
May 20 Javascript
原生JS实现仿淘宝网左侧商品分类菜单效果代码
Sep 10 Javascript
Bootstrap每天必学之按钮
Nov 26 Javascript
Javascript模仿淘宝信用评价实例(附源码)
Nov 26 Javascript
JS对象的深度克隆方法示例
Mar 16 Javascript
使用angular帮你实现拖拽的示例
Jul 05 Javascript
ES6扩展运算符的用途实例详解
Aug 20 Javascript
webpack4.x打包过程详解
Jul 18 Javascript
解决vue elementUI中table里数字、字母、中文混合排序问题
Jan 07 Javascript
JavaScript实现好看的跟随彩色气泡效果
Feb 06 Javascript
Vue提供的三种调试方式你知道吗
Jan 18 Vue.js
微信小程序实现页面分享onShareAppMessage
Aug 12 #Javascript
react实现antd线上主题动态切换功能
Aug 12 #Javascript
vue从一个页面跳转到另一个页面并携带参数的解决方法
Aug 12 #Javascript
解析原来浏览器原生支持JS Base64编码解码
Aug 12 #Javascript
在vue-cli 3中给stylus、sass样式传入共享的全局变量
Aug 12 #Javascript
微信小程序实现页面跳转传递参数(实体,对象)
Aug 12 #Javascript
微信小程序实现传递多个参数与事件处理
Aug 12 #Javascript
You might like
社区(php&amp;&amp;mysql)三
2006/10/09 PHP
在数据量大(超过10万)的情况下
2007/01/15 PHP
分页详解 从此分页无忧(PHP+mysql)
2007/11/23 PHP
Zend Guard一些常见问题解答
2008/09/11 PHP
CI(CodeIgniter)框架配置
2014/06/10 PHP
Javascript条件判断使用小技巧总结
2008/09/08 Javascript
在浏览器中获取当前执行的脚本文件名的代码
2011/07/19 Javascript
利用JS自动打开页面上链接的实现代码
2011/09/25 Javascript
Javascript堆排序算法详解
2014/12/03 Javascript
jquery Deferred 快速解决异步回调的问题
2016/04/05 Javascript
Jquery 整理元素选取、常用方法一览表
2016/11/26 Javascript
详解JavaScript 中getElementsByName在IE中的注意事项
2017/02/21 Javascript
Bootstrap学习笔记之进度条、媒体对象实例详解
2017/03/09 Javascript
vue 自定义全局方法,在组件里面的使用介绍
2018/02/28 Javascript
在node中使用jwt签发与验证token的方法
2019/04/03 Javascript
es6中reduce的基本使用方法
2019/09/10 Javascript
小程序如何获取多个formId实现详解
2019/09/20 Javascript
vue和iview实现Scroll 数据无限滚动功能
2019/10/31 Javascript
vue中的.$mount('#app')手动挂载操作
2020/09/02 Javascript
Python3中的2to3转换工具使用示例
2015/06/12 Python
Python中的字典与成员运算符初步探究
2015/10/13 Python
Python 实现使用dict 创建二维数据、DataFrame
2018/04/13 Python
详解python中init方法和随机数方法
2019/03/13 Python
超简单使用Python换脸实例
2019/03/27 Python
python解释器安装教程的方法步骤
2020/07/02 Python
内科护士实习自我鉴定
2013/10/17 职场文书
机械电子工程毕业生自荐信
2013/11/23 职场文书
高一生物教学反思
2014/01/17 职场文书
物业保安员岗位职责制度
2014/01/30 职场文书
《望庐山瀑布》教学反思
2014/04/22 职场文书
实习报告评语
2014/04/26 职场文书
新教师岗前培训方案
2014/06/05 职场文书
学校党的群众路线教育实践活动对照检查材料
2014/09/24 职场文书
公司向个人借款协议书范本
2014/10/09 职场文书
学校端午节活动总结
2015/02/11 职场文书
Python OpenCV 彩色与灰度图像的转换实现
2021/06/05 Python