Vue 3 的优势
- 更容易维护(组合式API)
- 更快的速度
- 更小的体积
- 更优的数据响应
创建 Vue 3 项目
- 前提环境条件:已安装 16.0 或更高版本的 Node.js
node -v
- 创建一个 Vue 应用(下面的指令将会安装并执行 create-vue )
npm init vue@latest
setup
组合式API - setup选项
- setup 函数在什么时候执行?
<script>
export default {
setup() {
console.log('setup函数比八大钩子中最早执行的beforeCreate函数还要早一点')
},
beforeCreate() {
console.log('我是beforeCreate函数')
}
}
</script>
- setup 函数的"原始复杂写法"
<script>
export default {
// 在 setup 的最后加上 return,才能使用 数据 和 函数
setup() {
// 数据
const message = 'hello world'
// 函数
const logMessage = () => {
console.log(message)
}
return {
message,
logMessage
}
}
}
</script>
- setup 函数的"语法糖写法"
<script setup>
// 数据
const message = 'hello world'
// 函数
const logMessage = () => {
console.log(message)
}
</script>
<template>
<div>{{ message }}</div>
<button @click="logMessage">点我</button>
</template>
<style>
</style>
reactive
接收一个对象类型的数据,返回一个响应式的对象
<script setup>
// reactive:接收一个对象类型的数据,返回一个响应式的对象
import { reactive } from 'vue'
// 对象类型
const state = reactive({
count: 100
})
// 进行 +1 的函数
const setCount = () => {
state.count++
}
</script>
<template>
<div>{{ state.count }}</div>
<button @click="setCount">+1</button>
</template>
ref
接收简单类型或者对象类型的数据传入并返回一个响应式的对象(推荐统一使用 ref )
<script setup>
// ref:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
import { ref } from 'vue'
// 简单类型 或者 复杂数据类型
const count = ref(100)
// 注意:
// 1. 在脚本中访问数据,需要通过 .value 的形式
// 2. 在template中,.value不需要加
const setCount = () => {
count.value++
}
console.log(count.value)
</script>
<template>
<div>{{ count }}</div>
<button @click="setCount">+1</button>
</template>
<style>
</style>
computed
计算属性函数
<script setup>
// const 计算属性 = computed(() => {
// return 计算返回结果
// })
import { computed, ref } from "vue";
// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
console.log(list.value) // 打印list的值
// 过滤 >5 的数据
const computedList = computed(() => {
return list.value.filter(item => item > 5)
})
</script>
<template>
<div>
<div>原始数据:{{ list }}</div>
<div>计算后的数据:{{ computedList }}</div>
</div>
</template>
watch
作用:监听一个或者多个数据的变化,数据变化时执行回调函数
<script setup>
import { ref, watch } from "vue";
const count = ref(0)
const nickname = ref('张三')
const changeCount = () => {
count.value++
}
const changeNickname = () => {
nickname.value += '哈'
}
// 1. 监视单个数据的变化
// watch(ref对象, (newValue, oldValue) => {...})
// 2. 监视多个数据的变化
// watch([ref对象1, ref对象2], (newArr, oldArr) => {...})
</script>
<template>
<div>{{ count }}</div>
<button>改数字</button>
<div>{{ nickname }}</div>
<button>改昵称</button>
</template>
immediate
<script setup>
import { ref, watch } from "vue";
const count = ref(0)
const nickname = ref('张三')
const changeCount = () => {
count.value++
}
const changeNickname = () => {
nickname.value += '哈'
}
// immediate 一进入页面就立即执行
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { immediate: true })
</script>
<template>
<div>{{ count }}</div>
<button>改数字</button>
<div>{{ nickname }}</div>
<button>改昵称</button>
</template>
deep
- 集体式的深度监视,只要有一个变化了就会执行函数
<script setup>
import { ref, watch } from "vue";
const count = ref(0)
const nickname = ref('张三')
const changeCount = () => {
count.value++
}
const changeNickname = () => {
nickname.value += '哈'
}
// deep 深度监视,默认 watch 进行的是浅层监视
// const ref1 = ref(简单类型) 可以直接监视
// const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
watch(count, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
immediate: true,
deep: true
})
</script>
<template>
<div>{{ count }}</div>
<button>改数字</button>
<div>{{ nickname }}</div>
<button>改昵称</button>
</template>
- 精确的为某一个元素对象进行深度监视,它变化了才会触发函数
<script setup>
import { ref, watch } from "vue";
const userInfo = ref({
name: 'zs',
age: 18
})
const setUserInfo = () => {
userInfo.value.age++
}
// 深度监视之"精准定位"
watch(() => { userInfo.value.age }, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
</script>
<template>
<div>{{ userInfo.age }}</div>
<button @click="setUserInfo">改年龄</button>
</template>
生命周期
选项式API | 组合式API |
---|---|
beforeCreate/created | setup |
beforeMount | onBeforeMount |
mount | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
<script setup>
import { onMounted } from 'vue';
// beforeCreate 和 created 的相关代码一律放在 setup 中
const getList = () => {
console.log('发送请求成功')
}
getList()
// 如果有些代码需要在 mounted 生命周期中执行,就调用对应函数
onMounted(() => { console.log('mounted生命周期函数 - 逻辑1') })
// 可以使用多次 mounted,并不会冲突,它会按顺序执行
onMounted(() => { console.log('mounted生命周期函数 - 逻辑2') })
</script>
<template>
<div></div>
</template>
<style>
</style>
父子数据传递
父级内容
<script setup>
// 父传子
// 1. 给子组件,添加属性的方式传值
// 2. 在子组件,通过props接收
import { ref } from "vue";
import SonCom from '@/components/son-com.vue' // 局部组件(导入进来就能用)
const money = ref(100)
const makeMoney = () => {
money.value++
}
const reduce = () => {
money.value--
}
</script>
<template>
<div>
<h3>这是父组件的地盘</h3>
<hr>
<SonCom info="父组件的余额: " :num="money" @reduceMoney="reduce"></SonCom>
<hr>
<h3>还是父组件的地盘</h3>
<div>当前余额: {{ money }}</div>
<button @click="makeMoney">点击赚钱</button>
</div>
</template>
<style>
</style>
子级内容
<script setup>
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以:此处需要借助于"编译器宏"函数接收子组件传递的数据
const props = defineProps({
info: String,
num: Number
})
console.log(props.info)
console.log(props.num)
const emit = defineEmits(['reduceMoney'])
const payment = () => {
emit('reduceMoney', 5) // 通过emit触发父组件<SonCom>标签的reduceMoney属性
}
</script>
<template>
<div>我是子组件</div>
<div>{{ info }}{{ num }}</div> <!-- 对于props传递过来的数据,模板中可以直接使用 -->
<button @click="payment">点击消费</button>
</template>
<style>
</style>
模板引用
父级内容
<script setup>
import TestCom from "@/components/test-com.vue";
import { onMounted, ref } from "vue";
// 模板引用 (可以用来获取dom元素,也可以用来获取组件)
// 1. 调用ref函数,生成一个对象
const inp = ref(null)
// 3. 在加载渲染完DOM后,才能访问到绑定的元素
// 这时候我们需要使用到 生命周期钩子函数"onMounted"
onMounted(() => {
console.log(inp.value) // 输出绑定的元素内容 <input type="text">
inp.value.focus() // 实现:进入页面立即聚焦输入框
})
// 4. 使用同样的方法,获取组件
const testRef = ref(null)
// 6. 渲染结束后,当用户点击按钮,将触发下面"获取组件的函数"
const getCom = () => {
console.log(testRef.value) // 输出该组件对象
console.log(testRef.value.num) // 输出该组件对象中的一些数据
console.log(testRef.value.sayHi()) // 输出该组件对象中的一些数据
}
</script>
<template>
<div>
<!-- 2. 通过ref属性进行标识,完成绑定 -->
<input ref="inp" type="text">
<button>点击让输入框聚焦</button>
</div>
<!-- 5. 通过ref属性进行标识,完成绑定 -->
<TestCom ref="testRef"></TestCom>
<button @click="getCom">点击获取组件</button>
</template>
子级内容
<script setup>
const num = 999
const sayHi = () => { console.log('hi') }
// 上面定义的数据或方法,在 <script setup> 语法糖下,只能在内部自己使用
// 它不会开放给父组件访问
// 为了解决这种默认的限制,我们可以通过 defineExpose 编译宏指定哪些属性和方法允许父组件访问
defineExpose({
num,
sayHi
})
</script>
<template>
<div>我是用于测试的文本,当前数量: {{ num }}</div>
</template>
跨层传递普通数据
基本介绍
- 顶层组件通过 provide 函数提供数据
provide('key', 顶层组件中的数据)
- 底层组件通过 inject 函数获取数据
const message = inject('key')
具体演示
- 顶层组件
<script setup>
import CenterCom from '@/components/center.vue'
import { provide, ref } from 'vue'
// 1. 跨层级传递"普通数据"
provide('theme-color', 'That is pink')
// 2. 跨层级传递"响应式数据"
const money = ref(999)
provide('userMoney', money)
// 3. 跨层级传递"函数"
const earning = () => { money.value++ }
provide('earningFn', earning)
</script>
<template>
<div>
<h3>我是顶层组件</h3>
<CenterCom></CenterCom>
</div>
</template>
- 中间层中间
<script setup>
import BottomCom from '@/components/bottom.vue'
</script>
<template>
<div>
<h3>我是中间层组件</h3>
<BottomCom></BottomCom>
</div>
</template>
- 底层组件
<script setup>
import { inject } from 'vue';
// 接收顶层组件传递过来的数据
const themeColor = inject('theme-color')
const money = inject('userMoney')
const earning = inject('earningFn')
</script>
<template>
<div>
<h3>我是底层组件</h3>
<div>颜色:{{ themeColor }}</div>
<div>余额:{{ money }}</div>
<button @click="earning">点我赚钱</button>
</div>
</template>
Vue3.3 新特性
defineOptions
在 Vue3.3 中,引入了 defineOption 宏。
顾名思义,主要是用来定义 Options API 的选项。
可以用 defineOptions 定义任意的选项,props,emit,expose,slots除外,
因为这些可以使用 defineProps,defineEmit、defineXXX来做到
<script setup>
// 1. defineOptions 里面的代码,与 setup 平级
defineOptions({
name: 'LoginIndex',
// ... 这里写更多自定义属性
})
// 2. 这里写其他需要写在 setup 里面的代码
// ...
</script>
<template>
<h3>Hello World</h3>
</template>
defineModel
原版的操作
- 父级组件
<script setup>
import MyInput from "@/components//temp.vue"
import { ref } from "vue";
const txt = ref('123456')
</script>
<template>
<div>
<MyInput v-model="txt"></MyInput>
{{ txt }}
</div>
</template>
- 子级组件
<script setup>
defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
type="text"
:value="modelValue"
@input="e => emit('update:modelValue', e.target.value)"
>
</template>
使用 defineModel
- 修改 vite.config.js 配置文件,在 vue( ) 括号里面加上
{ script:{ defineModel: true } }
,具体如下所示:
vue({
script: {
defineModel: true
}
}),
- 重启服务
- 具体使用方法,如下所示
<script setup>
import MyInput from "@/components//temp.vue"
import { ref } from "vue";
const txt = ref('123456')
</script>
<template>
<div>
<MyInput v-model="txt"></MyInput>
{{ txt }}
</div>
</template>
<script setup>
import { defineModel } from "vue";
const modelValue = defineModel()
</script>
<template>
<input
type="text"
:value="modelValue"
@input="e => modelValue = e.target.value"
>
</template>
Pinia
概念简述
Pinia 是 Vue 的最新状态管理工具,是 Vuex 的替代品
回顾 Vuex 涉及哪些概念:
state
mutaions
actions
getters
modules
Pinia 涉及的概念:
state
actions
getters
注意:在实际开发项目的时候,关于Pinia的配置,可以在项目创建时自动添加,而手动添加的方式如下:
- 安装 Pinia 包:
npm install pinia
或npm i pinia
- 配置 Pinia:
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 导入 Pinia 包
import App from './App.vue'
const pinia = createPinia() // 创建 Pinia 实例
const app = createApp(App) // 创建根实例
app.use(pinia) // pinia 插件的安装配置
app.mount('#app') // 视图的挂载
基本使用(store)
在 src 文件夹下创建一个 store 文件夹,在 store 里面新建一个 js 文件,这里演示为 counter.js
import { defineStore } from "pinia"
import { computed, ref } from "vue"
// 定义store
// defineStore(仓库的唯一标识,() => { ... })
export const useCounterStore = defineStore('counter', () => {
// 声明数据 state
const count = ref(999)
// 声明操作数据的方法 action
const addCount = () => count.value++
const reduceCount = () => count.value--
// 声明基于数据派生的计算属性 getters
const double = computed(() => count.value * 2)
// 与上面类似
const msg = ref('hello pinia')
const changeMsg = () => { msg.value += '_plus' }
// 返回要被访问的数据
return {
count,
double,
addCount,
reduceCount,
msg,
changeMsg
}
})
- src/App.vue
<script setup>
import Son1Com from "@/components/Son1Com.vue";
import Son2Com from "@/components/Son2Com.vue";
import { useCounterStore } from "@/store/counter.js"
const counterStore = useCounterStore()
</script>
<template>
<div>
<h1>我是App.vue根组件 - {{ counterStore.count }} - {{ counterStore.double }}</h1>
<button @click="counterStore.changeMsg">改变</button>
{{ counterStore.msg }}
<hr>
<Son1Com></Son1Com>
<Son2Com></Son2Com>
</div>
</template>
- src/components/Son1Com.vue
<script setup>
import { useCounterStore } from "@/store/counter.js"
const counterStore = useCounterStore()
</script>
<template>
<div>
<h3>我是Son1Com子组件 - {{ counterStore.count }} <button @click="counterStore.addCount">+</button></h3>
</div>
</template>
- src/components/Son2Com.vue
<script setup>
import { useCounterStore } from "@/store/counter.js"
const counterStore = useCounterStore()
</script>
<template>
<div>
<h3>我是Son2Com子组件 - {{ counterStore.count }} <button @click="counterStore.reduceCount">-</button></h3>
</div>
</template>
异步实现(action)
- src/store/channel.js
import { defineStore } from "pinia"
import { ref } from "vue";
import axios from "axios"
export const useChannelStore = defineStore('channel', () => {
const channelList = ref([])
const getList = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
// 解构: 在返回的信息中找到真正需要的数据
const { data: { data: { channels } } } = res
channelList.value = channels
}
return {
channelList,
getList
}
})
- src/App.vue
<script setup>
import { useChannelStore } from "@/store/channel.js"
const channelStore = useChannelStore()
</script>
<template>
<div>
<button @click="channelStore.getList">获取数据频道</button>
<ul>
<li v-for="item in channelStore.channelList" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
持久化存储插件
官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
AI 插件
Github Copilot 插件安装步骤:
- 登录 GitHub,试用 Copilot
- 打开 VScode,搜索并安装插件 Copilot
Github Copilot 插件使用说明:
- 删除键:表示不接受本次 AI 的智能生成代码
- Tab键:表示接收本次 AI 的智能生成代码
- Ctrl + Enter:查看更多方法