3.Vue3基础

Vue3基础

1. 简介

vue2优势:

  • 长期稳定版(2016年9月发布,近140次版本更新迭代)
  • 生态系统,文档系统完善
  • 过往案例项目支撑

vue3优势:

  • 更容易维护
    • 组合式 API
    • 更好的 TypeScript 支持
  • 更快的速度
    • 重写 diff 算法
    • 模板编译优化
    • 更高效的组件初始化
  • 更小的体积
    • 良好的 TreeShaking
    • 按需引入
  • 更优的数据响应式
    • proxy

2. 使用create-vue创建项目

create-vue是官方新的脚手架工具,底层由原 vue2 (vue-cli)的webpack切换成了vite(下一代前端工具链),为开发提供极速响应

  • 前提环境条件:已安装16.0或者更高版本的node.js(node -v)
  • 创建一个vue应用:npm init vue@latest这一指令将会安装并执行create-vue
- Project name: vue3-dome
- Add TypeScript? No
- Add JSX Support? No
- Add Vue Router for single Page Application development?  Yes
- Add Pinia for state management? Yes
- Add Vitest for Unit Testing? No
- Add Cypress for both Unit and End-to-End testing? No
- Add ESLint for code quality? Yes
- Add Prettier for code formatting? No
  • 关键文件等变化:

    • 配置文件:原vue.config.js文件变为vite.config.js文件
    • 入口文件:
    // main.js
    // new Vue() 创建一个应用实例对象
    import { cteateApp } from 'vue'
    import App from './App.vue'
    import './assets/main.css'
    // 1. 以App作为参数生成一个应用实例对象
    // 2. 挂载到id为app的节点上(根目录下index.html文件中)
    createApp(App).mount('#app')
    
    • 根组件App.vue
    <!-- setup:相当于一个开关,容许在script书写组合式API -->
    <script setup>...</script>
    <!-- 不再要求唯一根元素包裹 -->
    <template>...</template>
    <style scoped></style>
    

3. 组合式API - setup选项

3.1 setup选项的写法和执行时机

<script>
// setup执行时机在beforeCreate之前
export default {
    setup() {},
    beforeCreate() {}
}
</script>

3.2 setup选项中写代码的特点

vue3中不再推荐使用this,如果在setup中使用或者打印this是获取不到的(undefined)

<script>
export default {
    setup() {
        // 数据
        const msg = 'this is msg'
        // 函数
        const logMsg = () => {
            console.log(msg)
        }
        return {
            msg,
            logMsg
        }
    }
}
</script>
<template>
	<!-- 使用数据和方法 -->
	{{ msg }}
	<button @click="logMsg">to msg</button>
</template>

3.3 script setup 语法糖

为了编写方便,不用定义一个数据/函数就return一个,所以vue3提供了一个语法糖

原始复杂写法:

<script>
export default {
	setup() {
        // 数据
        const msg = 'this is msg'
        // 函数
        const logMsg = () => {
            console.log(msg)
        }
        return {
            msg,
            logMsg
        }
    }
}
</script>

语法糖写法:

<script setup>
	// 数据
    const msg = 'this is msg'
    // 函数
    const logMsg = () => {
        console.log(msg)
    }
</script>

4. 组合式API - reactive和ref函数

4.1 reactive()

作用:接收对象类型数据的参数传入并返回一个响应式的对象,普通对象不会实现视图更新,所以需要用reactive函数

核心步骤:

<script setup>
	// 1.导入
    import { reactive } from 'vue'
    // 2.执行函数 3.传入一个对象类型参数 变量接收
    const state = reactive({count: 0})
    // 事件函数
    const setCount = () => {
        state.count++
    }
</script>
<template>
	<div>
        <button @click="setCount">{{ state.count }}</button>
    </div>
</template>

4.2 ref()

作用:接收简单类型或者对象类型的数据传入并返回一个响应式的对象‘

核心步骤:

<script setup>
	// 1.导入
    import { ref } from 'vue'
    // 2.执行函数 3.传入参数(简单类型/对象类型) 变量接收
    const count = ref(0)
    // 事件函数
    const setCount = () => {
        // 脚本区域修改ref产生的响应式对象数据,必须通过 .value 属性来操作
        count.value++
    }
</script>
<template>
	<div>
        <button @click="setCount">{{ count }}</button>
    </div>
</template>

4.3 总结

  • reactive和ref函数的共同作用是用函数调用的方式生成响应式数据
  • reactive和ref的区别:
    • reactive不能处理简单类型的数据
    • ref参数类型支持更好,但是必须通过 .value访问修改
    • ref函数的内部实现依赖于reactive函数
  • 在实际工作中使用ref函数更加灵活

5. 组合式API - computed

计算属性基本思想和vue2的完全一致,组合式API下的计算属性只是修改了写法

核心步骤:

<script setup>
	// 1.导入
    import { ref, computed } from 'vue'
    // 原始响应式数组
    const list = ref([1, 2, 3, 4, 5, 6, 7, 8])
    // 2.执行函数,return计算之后的值,变量接收
    const computedList = computed(() => {
        return list.filter(item => item > 2)
    })
</script>
<template>
	<div>原始响应式数组 - {{ list }}</div>
	<div>通过计算属性返回的数组 - {{ computedList }}</div>
</template>

计算属性中不应该有“副作用”(比如异步请求/修改dom),可交给watch

避免直接修改计算属性的值(计算属性应该是只读的)

6. 组合式API - watch

作用:侦听一个或多个数据的变化,数据变化时执行回调函数

两个额外参数:

  • immediate(立即执行)
  • deep(深度侦听)

6.1 基本使用

侦听单个数据

<script setup>
	import { ref, watch } from 'vue'
    const count = ref(0)
    const setCount = () => {
        count.value++
    }
    // ref对象在watch中不需要加.value
    watch(count, (newVal, oldVal) => {
        console.log('count变化了', newVal, oldVal)
    })
</script>
<template>
	<div>
        <button @click="setCount">{{count}}</button>
    </div>
</template>

侦听多个数据:同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调

<script setup>
	import { ref, watch } from 'vue'
    const count = ref(0)
    const changeCount = () => {
        count.value++
    }
    const name = ref('zs')
    const changeName = () => {
        name.value = 'ls'
    }
    // watch侦听多个数据
    watch(
        [count, name], 
        (
    		[newCount, newName],
        	[oldCount, oldName]
    	) => {
        	console.log('count或name变化了', [newCount, newName], [oldCount, oldName])
    	}
    )
</script>
<template>
	<div>
        <button @click="changeCount">修改count-{{ count }}</button>
    </div>
	<div>
        <button @click="changeName">修改name-{{ name }}</button>
    </div>
</template>

6.2 immediate 立即执行

说明:在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调

<script setup>
	import { ref, watch } from 'vue'
    const count = ref(0)
    const setCount = () => {
        count.value++
    }
    // 立即执行
    watch(count, () => {
        connsole.log('count发生了变化')
    }, {
        immediate: true
    })
</script>

6.3 deep 深度侦听

默认机制:通过watch监听的ref对象默认是浅层侦听,直接修改嵌套的对象属性是不会触发回调执行的,需要开启deep选项

<script setup>
	import { ref, watch } from 'vue'
    const data = ref({count: 0})
    const changeData = () => {
        // 直接修改属性不会触发侦听回调
        data.value.count++
    }
    watch(data, (newVal, oldVal) => {
        console.log('data.count改变了', newVal, oldVal)
    },{
        deep: true // 加上deep可触发侦听对象属性
    })
</script>
<template>
	<div>
        <button @click="changeData">{{ data.count }}</button>
    </div>
</template>

注意:deep存在性能损耗,尽量不开启deep

6.4 精准侦听

在不开启deep的前提下,精准侦听对象属性,只有指定属性变化时才执行侦听回调

<script setup>
	import { ref, watch } from 'vue'
    const info = ref({name: 'zs', age: 18})
    const changeInfo = () => {
        info.value.age++
    }
    watch(
        () => info.value.age, 
        (newVal, oldVal) => {
        	console.log('age改变了', newVal, oldVal)
    	}
    )
</script>
<template>
	<div>
        <button @click="changeInfo">{{info.name}} - {{info.age}}</button>
    </div>
</template>

7. 组合式API - 生命周期函数

vue3(选项式API)与(组合式API)生命周期函数区别

选项式API组合式API说明
beforeCreate/createdsetup创建
beforeMountonBeforeMount挂载之前
mountedonMounted挂在成功
beforeUpdateonBeforeUpdate更新之前
updatedonUpdated更新成功
beforeUnmountonBeforeUnmount卸载之前
unmountedonUnmounted卸载成功

生命周期函数是可以执行多次的,多次执行时传入的回调会在时机成熟时依次执行

<script setup>
	import { onMounted } from 'vue'
    onMounted(() => {
        console.log('组件挂载完毕mounted执行了1')
    })
    onMounted(() => {
        console.log('组件挂载完毕mounted执行了3')
    })
    onMounted(() => {
        console.log('组件挂载完毕mounted执行了2')
    })
    // 控制台依次打印 ...1 ...3 ...2
</script>

8. 组合式API - 父子通信

8.1 父传子 - defineProps({属性名: 类型})

vue2中基本思想:

  • 父组件中给子组件绑定属性
  • 子组件内部通过props选项接收

vue3中具体操作:

<!-- 父组件 -->
<script setup>
    // setup语法糖下局部组件无需注册直接可以使用
	import sonComVue from './son-com.vue'
    const count = 18
</script>
<template>
	<div>我是父组件</div>
	<!-- 1.绑定属性 msg -->
	<sonComVue msg="this is msg" :count="count"></sonComVue>
</template>

<!-- 子组件 -->
<script setup>
	// 2.通过 defineProps宏函数接收子组件传递的数据
    const props = defineProps({
        msg: String,
        count: Number
    })
    console.log(props) // {msg: 'this is msg', count: 18}
</script>
<template>
	<div>我是子组件</div>
	<div>父组件传入的数据-{{msg}}-{{count}}</div>
</template>

8.2 子传父 - defineEmits([事件名称])

vue2中基本思想:

  • 父组件中给子组件标签通过@绑定事件
  • 子组件内部通过$emit方法触发事件

vue3中具体操作:

<!-- 父组件 -->
<script setup>
	// 引入子组件
    import sonComVue from './son-com.vue'
    const getMsg = (msg) => {
        console.log(msg)
    }
</script>
<template>
	<div>我是父组件</div>
	<!-- 1.绑定自定义事件 -->
	<sonComVue @getMsg="getMsg"></sonComVue>
</template>

<!-- 子组件 -->
<script setup>
	// 2.通过defineEmits宏函数生成emit方法
    const emit = defineEmits(['getMsg'])
    const setMsg = () => {
        // 3.触发自定义事件并传递参数
        emit('getMsg', 'this is son msg')
    }
</script>
<template>
	<div>我是子组件</div>
	<button @click="setMsg">触发自定义事件</button>
</template>

9. 组合式API - 模板引用

9.1 概念

通过ref标识获得真实的dom对象或者组件实例对象

<!-- 父组件 -->
<script setup>
	import { ref } from 'vue'
    import TestCom from './test-com.vue'
    // 1.调用ref函数得到ref对象
    const h1Ref = ref(null)
    const testComRef = ref(null)
    // 组件挂载完成后才能获取
    onMounted(() => {
        console.log(h1Ref.value) // <h1 ref="h1Ref">我是dom标签h1</h1>
        console.log(testComRef.value)
    })
</script>
<template>
	<!-- 2.通过ref标识绑定ref对象 -->
	<h1 ref="h1Ref">我是dom标签h1</h1>
	<TestCom ref="testComRef"/>
</template>

9.2 defineExpose() - 暴露组件内部的属性和方法

默认情况下在<script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法允许访问

<!-- 父组件 -->
<script setup>
	import { ref } from 'vue'
    import TestCom from './test-com.vue'
    const testComRef = ref(null)
    onMounted(() => {
        console.log(testComRef.value)
    })
</script>
<template>
	<TestCom ref="testComRef"/>
</template>

<!-- 子组件 -->
<script setup>
	import { ref } from 'vue'
    const name = ref('test name')
    const setName = () => {
        name.value = 'test new name'
    }
    defineExpose({
        name,
        setName
    })
</script>
<template>
	<div>我是test组件</div>
</template>

10. 组合式API - provide和inject

作用和场景:顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信

10.1 跨层传递数据

  • 顶层组件通过provide函数提供数据
  • 底层组件通过inject函数获取数据
<!-- 顶层组件 -->
<script setup>
    import RoomMsgItem from './room-msg-item.vue'
    // 组件嵌套关系
    // RoomPage -> RoomMsgItem -> RoomMsgComment
	import { provide, ref } from 'vue'
    // 1.顶层组件传递普通数据
    provide('data-key', 'this is room data')
    // 1.顶层组件传递响应式数据
    const count = ref(0)
    provide('count-key', count)
</script>
<template>
	<div>
        <p>顶层组件</p>
        <RoomMsgItem />
    </div>
</template>

<!-- 底层组件 -->
<script setup>
	import { inject } from 'vue'
    // 2.底层组件接收普通数据
    const roomData = inject('data-key')
    // 2.底层组件接收响应式数据
    const countData = inject('count-key')
</script>
<template>
	<div>
        <p>底层组件</p>
        <p>来自顶层组件中的数据:{{ roomData }}</p>
        <p>来自顶层组件的响应式数据:{{ countData }}</p>
    </div>
</template>

10.2 跨层传递方法

顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据

谁的数据谁负责修改

<!-- 顶层组件 -->
<script setup>
    import RoomMsgItem from './room-msg-item.vue'
    // 组件嵌套关系
    // RoomPage -> RoomMsgItem -> RoomMsgComment
	import { provide, ref } from 'vue'
    const count = ref(0)
    provide('count-key', count)
    // 1.传递方法
    const setCount = () => {
        count.value++
    }
    provide('setCount-key', setCount)
</script>
<template>
	<div>
        <p>顶层组件</p>
        <RoomMsgItem />
    </div>
</template>

<!-- 底层组件 -->
<script setup>
	import { inject } from 'vue'
    const countData = inject('count-key')
    // 2.接收方法
    const setCount = inject('setCount-key')
</script>
<template>
	<div>
        <p>底层组件</p>
        <p>来自顶层组件的响应式数据:{{ countData }}</p>
        <button @click="setCount">修改顶层组件的数据</button>
    </div>
</template>

11. Pinia状态管理库

Pinia是 vue 的专属的最新状态管理库,是vuex状态管理工具的替代品

  • 提供了更加简单的API(去掉了mutation)
  • 提供符合组合式风格的API(和vue3新语法统一)
  • 去掉了 modules 的概念,每个 store 都是一个独立的模块
  • 搭配Typescript一起使用提供可靠的类型推断

Pinia中文文档

11.1 action

// counter.js
// 导入一个方法 defineStore
import { defineStore } from 'pinia'
import { ref } from 'vue'
import axios from 'axios'
export const useCounterStore = defineStore('counter', () => {
    // 定义数据(state)
    const count = ref(0)
    // 定义修改数据的方法(同步action)
    const increment = () => {
        count.value++
    }
    
    // 异步action
    const getListAPI = 'http://XXX/list'
    const list = ref([])
    const getList = async() => {
        const res = await axion.get(getListAPI)
        list.value = res.data.data
    }
    // 以对象的方式return供组件使用
    return {
        count,
        increment,
        getList
    }
})

action中实现异步和组件中定义数据和方法的风格完全一致

<script setup>
	// 1.导入useCounterStore方法
    import { useCounterStore } from '@/stores/counter'
    // 2.执行方法得到store实例对象
    const counterStore = useCounterStore()
    onMounted(() => {
        counterStore.getList()
        console.log('异步获取的数据:', counterStore.list)
    })
</script>
<template>
	<button @click="counterStore.increment">{{ counterStore.count }}</button>
</template>

11.2 getters

Pinia中的getters直接使用computed函数进行模拟

// counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
    const count = ref(0)
    const increment = () => {
        count.value++
    }
    // getter定义
    const doubleCount = computed(() => {
        count.value*2
    })
    return {
        count,
        increment,
        doubleCount
    }
})
<script setup>
    import { useCounterStore } from '@/stores/counter'
    const counterStore = useCounterStore()
</script>
<template>
	<button @click="counterStore.increment">{{ counterStore.count }}</button>
	<p>getter数据变化-{{counterStore.doubleCount}}</p>
</template>

11.3 storeToRefs

使用stroeToRefs函数可以辅助保持数据(store+getter)的响应式结构

storeToRefs函数只负责数据的解构(state/getter),方法无需storeToRefs函数包裹,可直接结构赋值

若直接对数据进行解构赋值,会导致响应式丢失,视图不再更新

// counter.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
    const count = ref(0)
    const increment = () => {
        count.value++
    }
    // getter定义
    const doubleCount = computed(() => {
        count.value*2
    })
    return {
        count,
        increment,
        doubleCount
    }
})
<script setup>
    import { useCounterStore } from '@/stores/counter'
    import { storeToRefs } from 'pinia'
    // 保持响应式更新
    const { count, increment } = storeToRefs(useCounterStore())
    const { doubleCount } = useCounterStore()
</script>
<template>
	<button @click="increment">{{ count }}</button>
	<p>getter数据变化-{{ doubleCount }}</p>
</template>
  • 23
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值