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 相关文章推荐
for 循环性能比较 提高for循环的效率
Mar 19 Javascript
解决JS中乘法的浮点错误的方法
Jan 03 Javascript
使用jquery实现IE下按backspace相当于返回操作
Mar 18 Javascript
深入分析jsonp协议原理
Sep 26 Javascript
JQuery ztree 异步加载实例讲解
Feb 25 Javascript
jQuery焦点图轮播插件KinSlideshow用法分析
Jun 08 Javascript
JavaScript轻松创建级联函数的方法示例
Feb 10 Javascript
vue-router路由参数刷新消失的问题解决方法
Jun 17 Javascript
深入理解vue Render函数
Jul 19 Javascript
移动端效果之Swiper详解
Oct 09 Javascript
原生js实现自定义滚动条组件
Jan 20 Javascript
JavaScript实现页面动态验证码的实现示例
Mar 23 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中操作MySQL数据库的一些要注意的问题
2006/10/09 PHP
php上传apk后自动提取apk包信息的使用(示例下载)
2013/04/26 PHP
浅析PHP原理之变量(Variables inside PHP)
2013/08/09 PHP
PHP编程风格规范分享
2014/01/15 PHP
php页面函数设置超时限制的方法
2014/12/01 PHP
PHP实现WebService的简单示例和实现步骤
2015/03/27 PHP
php时间函数用法分析
2016/05/28 PHP
PHP如何实现订单的延时处理详解
2017/12/30 PHP
javaScript - 如何引入js代码
2021/03/09 Javascript
用Javascript读取中文COOKIE的解决办法
2007/02/15 Javascript
JS小功能(button选择颜色)简单实例
2013/11/29 Javascript
js中settimeout方法加参数
2014/02/28 Javascript
js实现网页多级级联菜单代码
2015/08/20 Javascript
Vue.js每天必学之内部响应式原理探究
2016/09/07 Javascript
etmvc+jQuery EasyUI+combobox多值操作实现角色授权实例
2016/11/09 Javascript
form+iframe解决跨域上传文件的方法
2016/11/18 Javascript
数组Array的一些方法(总结)
2017/02/17 Javascript
mockjs,json-server一起搭建前端通用的数据模拟框架教程
2017/12/18 Javascript
微信小程序switch开关选择器使用详解
2018/01/31 Javascript
vue实现动态添加数据滚动条自动滚动到底部的示例代码
2018/07/06 Javascript
详解vuex之store拆分即多模块状态管理(modules)篇
2018/11/13 Javascript
jQuery实现带3D切割效果的轮播图功能示例【附源码下载】
2019/04/04 jQuery
JavaScript提升机制Hoisting详解
2019/10/23 Javascript
VUE+Element实现增删改查的示例源码
2020/11/23 Vue.js
JavaScript实现缓动动画
2020/11/25 Javascript
python 基础学习第二弹 类属性和实例属性
2012/08/27 Python
python基础教程之对象和类的实际运用
2014/08/29 Python
Python实现备份文件实例
2014/09/16 Python
RetroStage德国:复古服装
2019/02/03 全球购物
璀璨的珍珠、密钉和个性化珠宝:Lily & Roo
2021/01/21 全球购物
奥巴马演讲稿
2014/01/08 职场文书
区优秀教师事迹材料
2014/02/10 职场文书
新年爱情寄语
2014/04/08 职场文书
校园之声广播稿
2015/08/18 职场文书
八年级作文之友谊
2019/12/02 职场文书
python创建字典及相关管理操作
2022/04/13 Python