Vue3快速入门指南:从Vue2升级到Vue3

目录

单文件组件

main.js实例化

生命周期

声明响应式:ref,reactive

computed和方法

watch

watchEffect

模板引用ref

组件

props和emit

暴露组件方法defineExpose

依赖注入provide / inject


单文件组件

  先看一下vue3单文件代码大体的样子。这是官网对vue3组件的简单示例:

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

        这是我们熟悉的template,script和style三部分,很明显,script的风格已经从面向对象的vue2变为了函数式和钩子的写法,有些类似于原生js和react hooks,官方的叫法是组合式API。

注意这是3.2版本开始的script setup语法糖,如果你看到这样的代码:

import { ref } from 'vue'

export default {
  // `setup` 是一个特殊的钩子,专门用于组合式 API。
  setup() {
    const count = ref(0)

    // 将 ref 暴露给模板
    return {
      count
    }
  }
}

        这是在Vue 3.0发布初期的写法,在setup钩子中,所有的模板变量和方法都需要手动return。目前推荐使用第一种写法,更加简洁和方便,注意要在script标签上加上setup

main.js实例化

挂载和插件的语法改成函数式,示例:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

注意:虽然vue3向下兼容vue2语法,但main.js的写法是一定要改的。

生命周期

vue3全部生命周期钩子:

示例:

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

        大部分钩子和之前的效果一样,命名改成了on+驼峰大写,比如mounted变成了onMounted,有几个例外是:

        1.beforeCreate,created已删除,setup函数代替了Vue 2中的created钩子

        2.destroyed,beforeDestroy分别改名为onUnmounted,onBeforeUnmount

声明响应式:ref,reactive

<template>
  <button @click="increment">
    {{ count }}
  </button>
  <button @click="increment2">
    {{ state.count }}
  </button>
</template>

<script setup>
import { ref, reactive } from 'vue'

const count = ref(0)
const state = reactive({ count: 0 })

function increment() {
  count.value++
}

function increment2() {
  state.count++
}
</script>

        相比以往所有响应式变量都挂在一个data里的写法,现在你可以自由地声明。上图两种写法所实现的效果是相同的。

ref可以传入任何类型,但需要注意在script部分需要使用.value来读取,在模板部分则无需使用。reactive不需要在意.value的问题,但局限有1.只适用于对象类型 2.不能直接覆盖整个对象 3.解包属性会丢失响应性。

// 正确的用法
const state = reactive({ count: 0 })
state.count++
state.sum = 99

// 或者用ref
const count = ref(0)

const state2 = ref({count: 0})
state2.value = {count: 0, sum: 99}
state2.value = null

// 错误,只能用于对象数组等类型
let state = reactive(0)

// 错误,不能直接覆盖
let state = reactive({ count: 0 })
state = {count: 0, sum: 99} 

// 这样也是错的,会丢失响应性
let state = reactive({ count: 0 })
state = reactive({count: 0, sum: 99})

// 错误!解包后count丢失响应性
let {count} = state
count++

        总体来说reactive限制比较多,官方更推荐用ref。在定义对象并且确定只需要更改属性的时候可以用reactive,更简洁。

computed和方法

        除了变成函数式风格以外,作用都和之前一致,computed有响应式依赖缓存,method没有,这点也还是一样。官网的例子:

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>
// 方法
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

watch

watch接收一个监听数据源,一个回调函数。有以下3种写法:

const x = ref(0)
const y = ref(0)

// 第一种 直接传入监听项
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// 第二种 getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 第三种 可以用数组来监听多个
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

第一种写法虽然最简单但是不能用来监听对象的单个属性,此时一般用第二种:

// 监听整个对象时是深度监听 属性变更时会触发
const obj = reactive({key1: 0, key2: 0})

watch(obj, (newObj) => {
  console.log(`obj is ${newObj}`)
})

// 错误,因为 watch() 得到的参数是一个 number
const obj = reactive({ count: 0 })

watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})


// 第二种 可以用来监听响应式对象的某个属性
watch(
  () => obj.key1,
  (key1) => {
    console.log(`obj.key1: ${key1}`)
  }
)
// 如果要深度监听整个对象需要加上deep
watch(
  () => obj,
  (obj) => {
    console.log(`obj: ${obj}`)
  }, {
    deep: true  
  }
)

deep和immediate和之前的用法一样,使用immediate在监听源变化之前就初始执行一次回调。

watchEffect

        这个新增的方法可以让你省去手动传递依赖项,在回调里自动跟踪。简单来说,会自动找到回调里用到的依赖,不用自己指定了。下面这个示例会自动跟踪todoId.value,每次变化时重新发送fetch请求。

watchEffect(async () => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${todoId.value}`
  )
  data.value = await response.json()
})

        对于这种只有一个依赖项的例子来说,watchEffect() 的好处相对较小。但是对于有多个依赖项的侦听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。此外,如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect() 可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。

        watchEffect在多个依赖项监听时很好用。比如有个接口请求了多个参数,在参数变动时都需要重新查询接口,就可以直接用这个自动跟踪,比深度监听也更省性能。

   watchEffect 仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪。

注意只会跟踪回调里同步代码的依赖,有异步方法时只有第一个await里同步代码访问的属性才会被追踪,这个例子里就是await里的todoId,除此之外的属性都不会被跟踪。

模板引用ref

        概念和之前的ref一样,不过需要手动用ref声明一个同名的来存放引用。注意这里只能用ref不能用reactive。

<script setup>
import { ref, onMounted } from 'vue'

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

v-for时的模板引用见我的这篇:vue3在循环列表里动态加上ref-CSDN博客

组件

单文件组件会默认导出,导入时也是直接可用,不需要手动注册

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

props和emit

和vue2的用法基本一致,不过需要用defineProps和defineEmits手动声明

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
  • defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。

  • defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。

  • defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推导。

  • 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。

总结一下需要注意的:

1.defineProps和defineEmits是编译器宏,不用手动导入。

2.不能引用局部变量,比如下面这个例子是错误的。

const STRINGTYPE = {
    type: String,
    required: true
}
const props = defineProps({
  foo: STRINGTYPE,
  foo2: STRINGTYPE
})

但是可以使用导入的变量:

<template>
  <h1>{{ greeting }}</h1>
</template>

<script setup>
import { someValue } from './someModule'; // 导入的绑定

const props = defineProps({
  greeting: someValue // 正确使用导入的变量
});
</script>

暴露组件方法defineExpose

defineExpose和上面一样,不用手动导入,vue2里的调用组件方法是

this.$refs.someComponent.someFun()

现在则是要在组件里手动暴露

子组件ChildComponent :

<script setup >
const props = defineProps({
  initialCount: Number
});

let count = props.initialCount;

const increaseCount = () => {
  count++;
};

defineExpose({
  increaseCount
});
</script>

父组件用ref来引用子组件,然后.value可以调用暴露出来的方法。

<template>
  <div>
    <ChildComponent ref="childComponentRef"></ChildComponent>
    <button @click="onIncreaseChildCount">Increase Child Count</button>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';

const childComponentRef = ref(null);
const onIncreaseChildCount = () => {
  childComponentRef.value.increaseCount();
};
</script>

依赖注入provide / inject

        provide和inject可以用于在父组件与多层嵌套的子组件之间进行数据传递,不需要通过props来逐层传递。用法非常简单,只要provide和inject的参数名对应就可以传值。

提供provide的顶层组件: 

<!-- ParentComponent.vue -->
<template>
  <ChildComponent></ChildComponent>
</template>

<script setup>
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';

provide('message', 'Hello from ParentComponent');
</script>

 中间的一个示例组件:

<!-- ChildComponent.vue -->
<template>
  <GrandChildComponent></GrandChildComponent>
</template>

<script setup>
import GrandChildComponent from './GrandChildComponent.vue';
</script>

 最后在这个组件用inject接收到了provide的值:

<!-- GrandChildComponent.vue -->
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { inject } from 'vue';
const message = inject('message');
</script>

        现在你已经对Vue3和Vue2的常用语法差异有了一定了解,是不是比想象中更容易上手呢?后面我会更新vue-router4、element-plus等第三方库和它们在Vue2和Vue3中使用的差异。

【Vue3】element-plus按需自动导入的配置 以及icon不显示的解决方案_vite element plus icon 按需自动导入不显示-CSDN博客 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值