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 相关文章推荐
一个基于jQuery的树型插件(OrangeTree)使用介绍
May 03 Javascript
jquery批量设置属性readonly和disabled的方法
Jan 24 Javascript
浅谈JavaScript字符集
May 22 Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
Feb 02 Javascript
jQuery.deferred对象使用详解
Mar 18 Javascript
html判断当前页面是否在iframe中的实例
Nov 30 Javascript
axios基本入门用法教程
Mar 25 Javascript
angular.js和vue.js中实现函数去抖示例(debounce)
Jan 18 Javascript
Cocos2d实现刮刮卡效果
Dec 20 Javascript
bootstrap实现嵌套模态框的实例代码
Jan 10 Javascript
react实现复选框全选和反选组件效果
Aug 25 Javascript
VUE中setTimeout和setInterval自动销毁案例
Sep 07 Javascript
微信小程序实现页面分享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之第一天
2006/10/09 PHP
用PHPdig打造属于你自己的Google[图文教程]
2007/02/14 PHP
php采集时被封ip的解决方法
2010/08/29 PHP
php程序的国际化实现方法(利用gettext)
2011/08/14 PHP
Javascript与PHP验证用户输入URL地址是否正确
2014/10/09 PHP
Jquery下attr和removeAttr的使用方法
2010/12/28 Javascript
如何使用jquery控制CSS样式,并且取消Css样式(如背景色,有实例)
2013/07/09 Javascript
简单方法判断JavaScript对象为null或者属性为空
2014/09/26 Javascript
JavaScript焦点事件、鼠标事件和滚轮事件使用详解
2016/01/15 Javascript
jquery实现上传文件大小类型的验证例子(推荐)
2016/06/25 Javascript
JS中对Cookie的操作详解
2016/08/05 Javascript
JavaScript实现三级联动效果
2017/07/15 Javascript
Vue2.0利用vue-resource上传文件到七牛的实例代码
2017/07/28 Javascript
JS解决position:sticky的兼容性问题的方法
2017/10/17 Javascript
基于Vue的ajax公共方法(详解)
2018/01/20 Javascript
Nuxt.js 数据双向绑定的实现
2019/02/17 Javascript
全面解析js中的原型,原型对象,原型链
2021/01/25 Javascript
[48:54]VGJ.T vs infamous Supermajor小组赛D组败者组第一轮 BO3 第二场 6.3
2018/06/04 DOTA
使用Python抓取模板之家的CSS模板
2015/03/16 Python
Python加pyGame实现的简单拼图游戏实例
2015/05/15 Python
Python FTP两个文件夹间的同步实例代码
2018/05/25 Python
python 切换root 执行命令的方法
2019/01/19 Python
Python扫描端口的实现
2021/01/25 Python
纯CSS和jQuery实现的在页面顶部显示的进度条效果2例(仿手机浏览器进度条效果)
2014/04/16 HTML / CSS
css3 transform及原生js实现鼠标拖动3D立方体旋转
2016/06/20 HTML / CSS
使用PDF.JS插件在HTML中预览PDF文件的方法
2018/08/29 HTML / CSS
英国最大的在线照明商店:Litecraft
2020/08/31 全球购物
总经理助理岗位职责
2013/11/08 职场文书
培训演讲稿范文
2014/01/12 职场文书
关于母亲节的感言
2014/02/04 职场文书
标准毕业生自荐信
2014/06/24 职场文书
社区护士演讲稿
2014/08/27 职场文书
2014年十一国庆节活动方案
2014/09/16 职场文书
教师个人教学总结
2015/02/11 职场文书
安全生产协议书
2016/03/22 职场文书
MySQL中B树索引和B+树索引的区别详解
2022/03/03 MySQL