【Vue3.0】还学得动吗?赶紧和我过一遍用法吧!

写在前面

2020.4.20 更新:目前Vue3.0 已经出于 Beta 测试阶段,即可以对开发者开放使用了,官方工具链如vue-routervuex 等仍处于 Alpha 阶段,距离正式发布还有一段时间。
本篇文章发布于2020.01.14,如果后续更新/发布的话,会考虑更新这篇文章的内容。

阅读依赖

本篇文章默认读者已经了解以下知识,没有掌握的请去补课~

  • Vue2.x的用法
  • npm的安装与构建,npx局部安装替代全局安装方法(了解最好,非必须)
  • git相关操作
  • react hooks相关知识(了解最好,非必须)

Vue3.0介绍

其他文章已经说得差不多了,几个核心点就是Proxy/函数式API/TS支持以及模板编译优化,不再赘述,想看源码的去github拉代码即可:
https://github.com/vuejs/vue-next

快速开始

使用 @vue/cli 构建

需要 vue-cli 版本大于 3.x ,如果是2.x版本,得先升级依赖。
的这里只介绍全局安装方法:

npm i -g @vue/cli
vue create my-vue3-app

注意:目前不建议选择 Typescript 预设,因为 vuex vue-router 等工具尚未完全支持(当然精力旺盛的可以自己实现)。
项目初始化完毕,进入 my-vue3-app 目录,执行 vue-next 安装命令:

vue add vue-next

这个命令可以自动运行安装脚本,将你当前 vue 项目升级为 vue 3.0 项目:

  Installing vue-cli-plugin-vue-next...

yarn add v1.22.4
[1/4] Resolving packages...
[2/4] Fetching packages...

success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
Done in 24.71s.
✔  Successfully installed plugin: vue-cli-plugin-vue-next


  Invoking generator for vue-cli-plugin-vue-next...
⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-vue-next
 vue-next  Installed vuex 4.0.
 vue-next  Documentation available at https://github.com/vuejs/vuex/tree/4.0
 vue-next  Installed vue-router 4.0.
 vue-next  Documentation available at https://github.com/vuejs/vue-router-next
 vue-next  Installed @vue/test-utils 2.0.
 vue-next  Documentation available at https://github.com/vuejs/vue-test-utils-next

happy coding.

从源码构建

首先把代码拉到本地(默认使用master分支即可),在根目录下执行npm install && npm run build, 就能在/packages/vue/dist下得到打包后的文件:

vue.cjs.js
vue.esm-bundler.j s
vue.esm.prod.js
vue.global.prod.js
vue.cjs.prod.js
vue.esm.js
vue.global.js
vue.runtime.esm-bundler.js

浏览器引入使用

如果在纯浏览器环境下,我们选用上面的vue.global.js作为依赖,因为它包含了开发提示以及template编译器。直接来一段小demo:




  
  Vue3.0 sample
  
  
  


  

浏览器中打开,你将看到如下文字:

Hello Vue3.0

可以看到:new Vue变成了createApp,不再接受option参数,而是搬到了mount方法中。我们的template字符串,现在可以使用text/x-template的脚本格式引入,至于其他的用法,基本和2.0一模一样
另外,源码仓库中 /packages/vue/examples目录下,提供了几个官方示例,有兴趣的可以去参阅

使用webpack构建3.0的sfc

仔细读读仓库中的readme.md,我们发现尤大已经很贴心地为我们做了一个webpack构建项目的最小实践: https://github.com/vuejs/vue-next-webpack-preview
同样的操作:拉代码->构建->运行后,看到的是一个点击计数器的基本用法:




与2.0不同了,这个setup方法就是3.0的新API,定义了一个响应式属性count和方法inc,将他们作为对象返回,就能在template中使用。其中,ref是对一个原始值添加响应式(装箱),通过.value获取和修改(拆箱),对应2.0中的data,如果需要对一个对象/数组添加响应式,则需要调用reactive()方法

import { reactive } from 'vue'
export default {
  setup() {
    return {
      foo: reactive({ a: 1, b: 2 })
    }
  }
}

拓展实践

为了更进一步理解setup,我们改造一下点击计数器——键盘计数器。
要实现的目标和思路为:

  • 将计数器变成一个组件,由外部控制开启/关闭: componentrefv-if的使用
  • 计数器监听某个键盘按键,按键名称由父组件作为props传入(如Enter,Space等): setup(props)获取
  • 组件渲染(onMounted)后开始监听,组件拆卸(onUnmounted)后取消监听:生命周期钩子在3.0中的用法
  • 添加is-odd文本,表示按键次数是否为奇数:computedvue3.0中的用法
  • 按键次数为5的倍数(0除外)时,弹出alert窗口:watch在vue3.0中的用法

Talk is cheap, show me the code

首先是改造App.vue父组件导入key-press-enter子组件,注意看template有何变化





可以发现:template现在可以像jsx一样作为碎片引入,不需要添加根元素了(当然#app根容器还是需要的)
接着是子组件KeyPressCounter.vue


以后编写组件就是setup一把梭了!是不是越来越像react hooks了?
对比一下传统写法:





当然,声明式vs函数式,不能说哪个一定比另外一个好。尤大依然为我们保持了传统api,这也意味着从2.0迁移到3.0,付出的成本是非常平滑的。

使用 Vuex hooks

初始化

首先定义全局的 Store :

import { createStore } from 'vuex';

export default createStore({
  state: {
    username: 'Xiaoming',
  },
  modules: {
    foo: {
      namespaced: true,
      state: {
        bar: 'baz',
      },
      modules: {
        nested: {
          namespaced: true,
          state: {
            final: 'you\'ve done',
          },
        },
      },
    },
  },
});

接着在组件根实例中注入

import { createAPP } from 'vue'
import Store from './store'
import APP from './views/App.vue'

createApp(APP)
  .use(Store) // 以插件安装形式注入
  .mount('#app')

如何在setup中引用

目前 vuex 的辅助函数 mapStatemapGettermapMutationmapAction,需要绑定组件实例上下文,而 setup 函数中无法访问组件实例,所以这些辅助函数在 setup 中应该用不上了,取而代之的应该是 useStateuseGetteruseMutationuseAction ,目前官方尚未实现。这里根据 vuex 源码写一个兼容3.0的 useState ,抛砖引玉(代码有点长,可以跳着看):

import { computed } from 'vue';
import { useStore } from 'vuex';

const normalizeNamespace = (fn) => (namespace, map) => {
  let appliedMap = map;
  let appliedNamespace = namespace;
  if (typeof appliedNamespace !== 'string') {
    appliedMap = namespace;
    appliedNamespace = '';
  } else if (namespace.charAt(appliedNamespace.length - 1) !== '/') {
    appliedNamespace += '/';
  }
  return fn(appliedNamespace, appliedMap);
};

const normalizeMap = (map) => (Array.isArray(map)
  ? map.map((item) => ({ key: item, val: item }))
  : Object.entries(map).map(([key, val]) => ({ key, val })));

export const useState = normalizeNamespace((namespace, states) => {
  const res = {};
  // 将源码中引用的 this.$store 替换成全局store
  const store = useStore();
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = (computed(() => {
      let { state, getters } = store;
      if (namespace) {
        // eslint-disable-next-line no-underscore-dangle
        const module = store._modulesNamespaceMap[namespace];
        if (!module) {
          return;
        }
        state = module.context.state;
        getters = module.context.getters;
      }
      // eslint-disable-next-line consistent-return
      return typeof val === 'function'
        ? val.call(store, state, getters)
        : state[val];
    }));
    // mark vuex getter for devtools
    res[key].vuex = true;
  });
  return res;
}, true);

编写完毕,在实际组件中使用:




输出结果:

Hello Xiaoming | baz | you've done

总结

  • template支持碎片,即除根组件外,子组件无需声明根元素。
  • 传统的组件option api,现在可以用setup来实现,不仅比以前变得更加灵活,在类型分析上(typescript)将会支持得更好
  • 大部分api如ref/reactive/onMounted等方法,现在支持按需导入,对于tree-shaking优化有利
  • setup使开发者不必再关心令人头疼的this问题
  • setup是一把双刃剑,如果你的思路足够清晰,那么它将会是你抽象逻辑的利器。反之使用不当同样也会让你的代码变成意大利面条
  • vuex 的辅助函数将在未来以 useXXXX 的形式 兼容 setup 函数。

你可能感兴趣的:(【Vue3.0】还学得动吗?赶紧和我过一遍用法吧!)