vue3.0入门——第一章

343 阅读5分钟

vue3.0_脚手架

官网 提供了两种构建方式,一种是vite,另一种是vue-cli。本文采用的是前者vite,至于为什么选择 vite,主要是因为它的构建速度更快。 我们依次执行命令:

npm init vite vue3-demo 
cd vue3-demo
npm install
npm run dev

浏览器端打开localhost:3000可以看到,vite+vue3的项目启动成功了:

image.png

vue3.0_新特性

一、组合式API (Composition API)

组合式 API的作用是将同一个逻辑关注点代码收集在一起,这样增强了组件的易读性和可维护性,不必在处理单个逻辑关注点时,不断地跳转相关代码的选项块。

# setup 选项

  • vue3中将setup作为组合式API的入口,在组件创建之前执行
  • setup选项是一个接收两个参数props和context的函数
  • 它返回的所有内容都可用于组件的其余部分(生命周期、计算属性、方法等)以及组件的模板 setup的结构大致如下:
export default {
 components: {  },
 props: {
   user: {
     type: String,
     required: true
   }
 },
 setup(props) {
   console.log(props) // { user: '' }
   // 暴露给 template
   return {} // 这里返回的任何内容都可以用于组件的其余部分
 }
 // 组件的“其余部分”
}

# ref 函数

  • ref为我们的值创建一个响应式引用,返回一个{value: xxx}格式的对象,可以通过该对象的value属性访问或者更改响应式变量的值
  • 它和组合式API结合使用
  • setup返回的ref在模板中访问时是被自动浅解包,不需要在模板中使用.value
<template>
  <div>{{ counter }}</div>
</template>
import { ref } from 'vue'
setup(){
    const counter = ref(0)
    console.log(counter) // { value: 0 }
    console.log(counter.value) // 0
    counter.value++
    console.log(counter.value) // 1 
    return {
      counter
    }
}

# reactive 函数

  • 接受一个对象,返回对象的响应式副本,可以将对象做“深层”的响应式转换
  • 可以解包所有深层的refs,同时维持ref的响应性
const count = ref(1)
const obj = reactive({ count })

// ref 会被解包
console.log(obj.count === count.value) // true

// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2

// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3

# toRefs 函数

toRefs可以获取setup选项中参数props中某一个prop的响应式引用

import { watch, toRefs } from 'vue'

setup (props) {
  // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
  const { counter } = toRefs(props)

  watch(counter, (newValue, oldValue) => { 
     console.log('The new counter value is: ' + counter.value)
  })
  
  ...

  return {
    ...
  }
}

# 加 on 前缀的生命周期函数

  • 生命周期我们并不陌生,然而vue3中,我们可以将生命周期钩子写在setup选项中,只需在钩子前加前缀:on,例如mountedsetup中写成onMounted
  • on前缀的生命周期函数都接受一个回调,当钩子被组件调用时,该回调将被执行
import { ref, onMounted } from 'vue'
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

# watch 函数的响应式更改

从vue中导出的watch函数可以接受3个参数:

  • 一个想要侦听的响应式引用或 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
// 在 counter prop 的响应式引用上设置一个侦听器
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

当counter被修改时,例如counte.value=5,侦听将触发并执行回调 (第二个参数)。 以上等效于下面代码:

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}

# watchEffect 函数

  • 响应式追踪依赖
  • 自动应用和重新应用副应用
    • 自动应用:立即执行传入的一个函数
    • 重新应用副应用:依赖变更时重新运行该函数
  • 组件卸载时自动停止侦听
  • 将在组件更新前执行副作用
  • onInvalidate函数用来清除副应用
  • flush选项可以在组件更新后重新执行副应用

# 独立的computed属性

computed输出的是一个响应式引用

  • 返回一个不可变的响应式ref对象
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误
  • 用来创建可写的ref对象
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

# 总结

以上可以将组合式API概述为:将每一个逻辑关注点(即单独的功能模块)中的逻辑被提取到一个独立组合式函数中,每一个组合式函数都是由从vue中导出的函数(ref、toRefs、watch、computed等)组合而成,它们就像一个个积木一样,被引入到组件中,然后我们可以在组件的setup函数中将这些单独的功能模块里的所有内容(属性,方法等)收集起来以类似API的方式暴露给组件的其余部分(生命周期、计算属性、方法等)以及组件的模板。

二、Teleport

Teleport字面意思为心灵运输(物体、人);远距离传送,所以我们不难猜测它的作用效果类似于slot,可以在模板中传输/挂载/注入组件

  • 有两个参数todisabled
  • 作用是通过to属性将A组件挂载到B组件中需要渲染的位置,而A组件的逻辑不依赖于B组件,保持A组件的独立性
<body>
  <div style="position: relative;">
    <h3>Tooltips with Vue 3 Teleport</h3>
    <div>
      <modal-button></modal-button>
    </div>
  </div>
</body>
const app = Vue.createApp({});

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal!
    </button>

    <div v-if="modalOpen" class="modal">
      <div>
        I'm a modal! 
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </div>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})

以上例子中可以 看到,利用Teleport将弹框组件中的按钮与弹框的触发逻辑绑定在一个组件中,只需要通过to指定需要挂载到的地方即目标元素body,目标元素也可以是一个id或者class查询选择器,modal-button组件就可以被正常地渲染在模板中了,这样逻辑上或者说结构上就比较清爽干净了。

三、片段

vue3中支持在组件内包含多个根节点,直白点来讲,就是在组件内template标签下不需要再包裹一个DOM标签

<!-- demo.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template

四、emits 选项

组件事件中的 emits

1、 emits 选项用来定义一个组件可以向其父组件触发的事件

<template>
  <div>
    <p>{{ text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text'],
    emits: ['accepted']
  }
</script>

2、 任何未在emits中声明的事件监听器都会被算入组件的$attr作为原生事件,被默认绑定到组件的根节点的原生事件监听器上

// 父组件
<my-button v-on:click="handleClick"></my-button>
// 子组件
<template>
  <button v-on:click="$emit('click', $event)">OK</button>
</template>
<script>
export default {
  emits: [] // 不声明事件
}
</script>

以上如果不在emits选项中声明click事件,则该事件会被触发两次:

  • 一次来自子组件的$emit
  • 一次来自根元素上的原生事件监听器 3、emits选项可以接受一个数组或者对象,后者允许配置事件验证(验证函数应返回布尔值)
app.component('custom-form', {
  emits: {
    // 没有验证
    click: null,

    // 验证submit 事件
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
})

五、v-model 的新用法

  • v-model参数的改变:
    • vue2中:组件上的 v-model 使用 value 作为 prop 和 input 作为事件
     <ChildComponent v-model="pageTitle" />
     <!-- 是以下的简写: -->
     <ChildComponent :value="pageTitle" @input="pageTitle = $event" />
    
    • vue3中:组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件
     <ChildComponent v-model="pageTitle" />
     <!-- 是以下的简写: -->
     <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event" />
    
  • 可以在同一组件上使用多个v-model绑定
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
  • 可以自定义v-model的修饰符
    • vue3中使用modelModifiers对象来自定义修饰符,也就是说v-model 的修饰符将通过 modelModifiers prop 提供给组件
    • 当组件的 created 生命周期钩子触发时,modelModifiers prop 会包含 capitalize,且其值为 true

下面提供一个实例,实现每当 <input/> 元素触发 input 事件时,我们都将字符串大写:

// html
<div id="app">
  <my-component v-model.capitalize="myText"></my-component>
  {{ myText }}
</div>
// vue
const app = Vue.createApp({
  data() {
    return {
      myText: ''
    }
  }
})

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) { // true
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
  template: `<input
    type="text"
    :value="modelValue"
    @input="emitValue">`
})

app.mount('#app')