搜索
您的当前位置:首页正文

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

来源:知库网

写在前面

本篇文章发布于2020.01.14,目前Vue3.0正处于Alpha测试阶段,如果后续更新/发布的话,会考虑更新这篇文章的内容。

阅读依赖

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

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

Vue3.0介绍

快速开始

首先把代码拉到本地(默认使用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:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue3.0 sample</title>
  <!--  在浏览器下template可以这么写了  -->
  <script type="text/x-template" id="greeting">
    <h3>{{ message }}</h3>
  </script>
  <script src="vue.global.js"></script>
</head>
<body>
  <div id="app"></div>
  <script type="text/javascript">
    const { createApp } = Vue;
    const myApp = {
      template: '#greeting',
      data() {
        return {
          message: 'Hello Vue3.0'
        }
      }
    }
    createApp().mount(myApp, '#app')
  </script>
</body>
</html>

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

Hello Vue3.0

使用webpack构建3.0的sfc

<template>
  <img src="./logo.png">
  <h1>Hello Vue 3!</h1>
  <button @click="inc">Clicked {{ count }} times.</button>
</template>

<script type="text/javascript">
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0); // 响应式数据
    // 事件回调不需要任何处理,直接作为对象方法返回即可;
    const inc = () => {
      count.value++
    }
    return {
      count,
      inc
    }
  }
}
</script>

与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>
    <!--  设置checkbox控制组件开关  -->
    <input id="key-counter" type="checkbox" v-model="enableKeyPressCounter">
    <label for="key-counter">check to enable keypress counter</label>
    <key-press-counter v-if="enableKeyPressCounter" key-name="Enter" />
</template>

<script type="text/javascript">
  import { ref } from 'vue'
  import KeyPressCounter from './KeyPressCounter.vue';

  export default {
    components: {
      KeyPressCounter // 组件用法和原来一致
    },
    setup() {
      return {
        enableKeyPressCounter: ref(false), // 是否开启组件
      }
    }
  }
</script>

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

<script>
 <template>
  <h3>Listening keypress: {{ keyName }}</h3>
  <p>Press {{ pressCount }} times!</p>
  <p>Is odd times: {{ isOdd }}</p>
</template>

<script type="text/javascript">
  import { onMounted, onUnmounted, ref, effect, computed } from 'vue';

  /**
   * 创建一个键盘按键监听钩子
   * @param keyName 按键名称
   * @param callback 监听回调
   */
  const useKeyboardListener = (keyName, callback) => {
    const listener = (e) =>{
      console.log(`你按下了${e.key}键`) // 用来验证监听时间是否开启/关闭
      if (e.key !== keyName) {
        return;
      }
      callback()
    }
    // 当组件渲染时监听
    onMounted(()=>{
      document.addEventListener('keydown', listener)
    })
    // 当组件拆解时监听
    onUnmounted(()=>{
      document.removeEventListener('keydown', listener)
    })
  }

  export default {
    name: "EnterCounter",
    /**
     * @param props 父组件传入的props
     * @return { Object } 返回的对象可以在template中引用
     */
    setup(props) {
      const { keyName } = props
      const pressCount = ref(0)
      // hooks调用
      useKeyboardListener(keyName, ()=>{
        pressCount.value += 1;
      })
      // watch的用法,可以看到,现在无需声明字段或者source,vue自动追踪依赖
      effect(()=>{
        if (pressCount.value && pressCount.value % 5 === 0) {
          alert(`you have press ${pressCount.value} times!`)
        }
      })
      // computed的用法,基本是原来的配方
      const isOdd = computed(()=> pressCount.value % 2 === 1)

      return {
        keyName,
        pressCount,
        isOdd
      }
    }
  }
</script>

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

<template>
  <div>
    <h3>Listening keypress: {{ keyName }}</h3>
    <p>Press {{ pressCount }} times!</p>
    <p>Is odd times: {{ isOdd }}</p>
  </div>
</template>

<script type="text/javascript">
  let listener

  export default {
    name: "EnterCounter",
    props: {
      keyName: String
    },
    computed: {
      isOdd() {
        return this.pressCount % 2 === 1;
      }
    },
    data() {
      return {
        pressCount: 0
      }
    },
    mounted() {
      listener = (e) =>{
        if (e.key !== this.keyName) {
          return;
        }
        this.callback()
      }
      document.addEventListener('keydown', listener)
    },
    beforeUnmount() {
      document.removeEventListener('keydown', listener)
    },
    watch: {
      pressCount(newVal) {
        if (newVal && newVal % 5 === 0) {
          alert(`you have press ${newVal} times!`)
        }
      }
    },
    methods: {
      callback() {
        this.pressCount += 1;
      }
    }
  }
</script>

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

总结

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