Vue3的优势
create-vue搭建Vue3项目
node -v
npm init vue@latest 或者是yarn create vue@latest
npm install
Vue3项目目录和关键文件
Vetur插件是Vue2的
Volarr插件是Vue3的
main.js
import './assets/main.css'
// new Vue() 创建一个应用实例 => createApp()
// createRouter() createStore()
// 将创建实例进行了封装,保证每个实例的独立封闭性
import { createApp } from 'vue'
import App from './App.vue'
// createApp(App) 创建实例
// mount设置挂载点 #app(id为app的盒子)
createApp(App).mount('#app')
App.vue
<!-- 加上setup就允许在script中直接编写组合式API -->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>
<template>
<!-- 不再要求唯一根元素 -->
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<!-- 组件不用注册,就可以直接用 -->
<TheWelcome />
</main>
</template>
<style scoped>
</style>
组件不用注册,可以直接使用
组合式API - setup选项
setup执行时机
执行时机,比beforeCreate更早,
所以,获取不到this(this是undefined
setup写法
数据和函数 需要在setup最后return
才能在模板中应用
简便写法:通过setup 语法糖,不用return
组合式API - reactive 和 ref 函数
reactive (不推荐
App.vue
<script setup>
import {reactive} from 'vue'
const state=reactive({
count:100
})
const setCount= ()=>{
state.count++
}
</script>
<template>
<div>{{ state.count }}</div>
<button @click="setCount">+1</button>
</template>
<style></style>
ref (更推荐
本质:在原有传入数据基础上,外层包含了一层对象,包成了复杂类型,底层包成复杂类型之后,再借助reactive 实现响应式,所以,
脚本中访问数据,需要通过 .value
在template中, .vue不需要加(帮我们扒了一层)
所以
推荐以后声明数据,统一用ref => 统一编码规范
App.vue
<script setup>
import {ref} from 'vue'
const count = ref(0)
const obj = ref({
age:18,
name:'slx'
})
console.log(obj.value);
console.log(obj.value.age);
// console.log(count.value)
const countChange = () => {
// count本质上是对象,所以要count.value++ 不是count++
count.value++
}
</script>
<template>
<div>{{ count }}</div>
<button @click="countChange">+1</button>
</template>
<style></style>
获取dom元素不用`$ref`,直接inp.value就可以了
什么是响应式数据,有什么作用
响应式数据是指在数据发生变化时,相关的视图能够自动更新的数据。在 Vue 中,通过将数据转换为响应式对象,当数据发生变化时,Vue 会自动追踪这些变化,并通知相关的视图进行更新。
响应式数据的作用是使开发者能够以声明式的方式编写代码,而不需要手动操作 DOM 或追踪数据变化。它提供了一种简单且高效的方式来维护数据和视图之间的关系,使开发者能够专注于业务逻辑而不是手动的视图更新。
通过响应式数据,当数据发生变化时,相关的视图会自动更新,保持数据和视图的同步。这大大简化了开发过程,提高了代码的可维护性和可读性。
响应式数据在 Vue 中是核心概念之一,它使得开发者能够构建动态、交互式和响应式的应用程序。
在 Vue 2 和 Vue 3 中,响应式数据的处理有什么区别
在 Vue 2 中,使用 data 选项来声明响应式数据,Vue 会将这些数据转换为响应式的对象。当数据发生变化时,Vue 会自动更新相关的视图。
在 Vue 3 中,使用 ref 和 reactive 函数来声明响应式数据。
组合式API - computed
App.vue
<script setup>
import { computed,ref } from 'vue'
const list = ref(
[1,2,3,4,5,6,7,8,9]
)
// 基于list派生一个计算属性,从list中过滤
const computerList = computed(() => {
return list.value.filter((item) => item%2===0)
})
// 定义一个修改数组的方法
const addFn = () =>{
list.value.push(666)
}
</script>
<template>
<div>{{ list }}</div>
<div>{{ computerList }}</div>
<button @click="addFn">+666</button>
</template>
<style></style>
全选反选可以用get,set
组合式API - watch
侦听单个数据
侦听响应式对象的属性值
侦听多个数据
<script setup>
import {ref, watch} from 'vue'
const count = ref(0)
const nickname = ref('slx')
const changeCount = () => {
count.value++
}
const changeName = () => {
nickname.value='hhhhh'
}
// 1.监视单个数据变化
// watch(ref对象,(newValue,oldValue) => { ... } )
// 直接传对象,所以就不要count.value
// watch(count,(newValue, oldValue)=> {
// console.log(newValue, oldValue);
// })
// 2.监视多个数据变化
// watch([ref对象1,ref对象2], (newArr,oldArr) => { ... } )
// 直接传对象,所以就不要count.value
// watch([count,nickname],(newArr, oldArr)=> {
// console.log(newArr, oldArr);
// })
</script>
<template>
<div>{{ count }}</div>
<button @click="changeCount">change-count</button>
<div>{{ nickname }}</div>
<button @click="changeName">change-Name</button>
</template>
<style></style>
配置项
<script setup>
import {ref, watch} from 'vue'
const count = ref(0)
const nickname = ref('slx')
const changeCount = () => {
count.value++
}
const changeName = () => {
nickname.value='hhhhh'
}
// 添加额外配置项
// 3.immediate 立刻执行
watch(count,(newValue, oldValue)=> {
console.log(newValue, oldValue)
},{
immediate:true,
deep:true
})
const userInfo = ref({
name:'ssss',
age:12
})
const setUserInfo = () => {
// 修改userInfo.value ,修改对象的地址,才能监视到,浅层监视
// userInfo.value={name:'qqqq',age:10}
userInfo.value.age++
}
// 4.deep 深度监视,默认watch进行的是 浅层监视
// const ref1 = ref(简单类型) 可以直接监视
// const ref1 = ref(复杂类型) 监视不到复杂类型内部数据的变化
watch(userInfo,(newValue)=> {
console.log(newValue)
},{
deep:true
})
</script>
<template>
<div>{{ userInfo }}</div>
<button @click="setUserInfo">change-userInfo</button>
<div>{{ count }}</div>
<button @click="changeCount">change-count</button>
<div>{{ nickname }}</div>
<button @click="changeName">change-Name</button>
</template>
<style></style>
精确监听对象的某个属性变化
<script setup>
import {ref, watch} from 'vue'
const count = ref(0)
const nickname = ref('slx')
const changeCount = () => {
count.value++
}
const changeName = () => {
nickname.value='hhhhh'
}
const userInfo = ref({
name:'ssss',
age:12
})
const setUserInfo = () => {
// 修改userInfo.value ,修改对象的地址,才能监视到,浅层监视
// userInfo.value={name:'qqqq',age:10}
userInfo.value.age++
}
// 5.精确监视,某一属性
watch(() => userInfo.value.age,(newValue, oldValue) => {
console.log('5555', newValue, oldValue);
})
</script>
<template>
<div>{{ userInfo }}</div>
<button @click="setUserInfo">change-userInfo</button>
<div>{{ count }}</div>
<button @click="changeCount">change-count</button>
<div>{{ nickname }}</div>
<button @click="changeName">change-Name</button>
</template>
<style></style>
浅层与深层监听
在Vue 3中,浅层监听(Shallow Watch)是一种用于监视对象或数组变化的特殊方式。浅层监听只会监视对象或数组的一级属性或元素的变化,而不会递归地监视其内部属性或元素的变化。
浅层监听在使用watch
函数或选项时可以通过设置deep
选项来控制。默认情况下,deep
选项是false
,即浅层监听。当deep
选项为false
时,watch
只会监听对象或数组的一级属性或元素的变化。
下面是一个示例,演示了浅层监听的概念:
<template>
<div>
<p>浅层监听示例:</p>
<button @click="updateData">更新数据</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const data = ref({
name: 'John',
details: {
age: 30,
},
});
// 浅层监听,只会监听data对象的一级属性的变化
watch(data, (newData, oldData) => {
console.log('浅层监听触发');
});
const updateData = () => {
// 修改data对象的一级属性,会触发浅层监听
data.value.name = 'Jane';
};
return {
data,
updateData,
};
},
};
</script>
在上面的示例中,我们使用ref
创建了一个包含嵌套对象的响应式数据data
。然后,我们使用watch
来监听data
对象的变化,但由于默认情况下deep
选项是false
,所以只有当data
对象的一级属性(例如name
)发生变化时,浅层监听才会触发,而不会监听details
内部对象的属性变化。
总结一下,浅层监听在Vue 3中用于监视对象或数组的一级属性或元素的变化,是一种性能优化的方式,可以避免不必要的递归监听。如果需要深度监听对象或数组的内部属性或元素变化,可以设置deep
选项为true
,但要谨慎使用,因为它可能会引发性能问题。
组合式API - 生命周期函数
<script setup>
import { onMounted } from 'vue';
// beforeCreate 和 created 的相关代码
// 一律放在 setup 中执行
const getList = () => {
setTimeout(() => {
console.log('发送请求,获取数据')
}, 2000)
}
// 一进入页面的请求
getList()
// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {
console.log('mounted生命周期函数 - 逻辑1')
})
// 写成函数的调用方式,可以调用多次,并不会冲突,而是按照顺序依次执行
onMounted(() => {
console.log('mounted生命周期函数 - 逻辑2')
})
</script>
<template>
<div></div>
</template>
组合式API - 父子通信
父传子 const props = defineProps({
为什么这里的message="xxxx"
没加: ,是因为它不是响应式的数据
App.vue
// 父传子
// 1. 给子组件,添加属性的方式传值
// 2. 在子组件,通过props接收
<script setup>
import {ref} from 'vue'
import MySon from '@/components/MySon.vue'
const money = ref(100)
const getMoney = () =>{
money.value+=10
}
</script>
<template>
<h3>father</h3>
<button @click="getMoney">+money</button>
<MySon car="宝马" :money="money"></MySon>
</template>
<style></style>
MySon.vue
这里就直接用名字,都没有props.car
<script setup>
// 由于写了setup 所以无法直接配置props选项
// 所以要借助“编译器宏”函数接受子组件传递的数据
// import {ref, watch} from 'vue'
const props = defineProps({
car:String,
money:Number
})
</script>
<template>
<div class="son">
<h4>Son</h4>
<h4>{{ car }} {{ money }}</h4>
</div>
</template>
<style>
.son{
width: 200px;
height: 200px;
background-color: #b17b7b;
}
</style>
子传父 const emit = defineEmits([
App.vue
<script setup>
import {ref} from 'vue'
import MySon from '@/components/MySon.vue'
const money = ref(100)
const getMoney = () =>{
money.value+=10
}
const changeFn = (newMoney) => {
money.value=newMoney
}
</script>
<template>
<h3>father</h3>
<button @click="getMoney">+money</button>
<MySon
@changeMoney="changeFn"
car="宝马" :money="money"></MySon>
</template>
<style></style>
MySon.vue
<script setup>
// 子传父
// 1. 在子组件内部,emit触发事件 (编译器宏获取)
// 2. 在父组件,通过 @ 监听
// import {ref, watch} from 'vue'
const props = defineProps({
car:String,
money:Number
})
const emit = defineEmits(['changeMoney'])
const buy = () => {
emit('changeMoney',5)
}
</script>
<template>
<div class="son">
<h4>Son</h4>
<h4>{{ car }} {{ money }}</h4>
<button @click="buy">花钱</button>
</div>
</template>
<style>
.son{
width: 200px;
height: 200px;
background-color: #b17b7b;
}
</style>
当有多个参数时,就直接放就行
组合式API - 模板引用
通过ref标识获取dom对象或者组件实例对象
显式暴露组件内部的属性和方法defineExpose()
App.vue
<script setup>
import {onMounted, ref} from 'vue'
import TestCom from '@/components/TestCom.vue'
// 模板引用(可以获取dom,也可以获取组件
// 1.调用ref函数,生成ref对象
// 2.通过ref标识,进行绑定
// 3.通过ref对象.value 即可访问到绑定的元素(必须渲染
const inp = ref(null)
onMounted(() => {
inp.value.focus()
})
const clickFn = () => {
inp.value.focus()
}
// --------------------------
const testRef = ref(null)
const getCom = () => {
console.log(testRef.value);
}
</script>
<template>
<input ref="inp" type="text">
<button @click="clickFn" >focus</button>
<TestCom ref="testRef" ></TestCom>
<button @click="getCom">获取组件</button>
</template>
<style></style>
TestCom.vue
<script setup>
import {ref} from 'vue'
const count = 999
defineExpose({
count
})
</script>
<template>
<div>Test
{{ count }}
</div>
</template>
<style></style>
在父组件中使用defineExpose暴露的子组件数据
MySon.vue
<script setup>
import { defineExpose, ref } from 'vue';
const message = ref('Hello from child component');
// 使用defineExpose暴露数据
defineExpose({
message,
})
</script>
<template>
<div>
<p>子组件内容:{{ message }}</p>
</div>
</template>
App.vue
<script setup>
import MySon from './components/MySon.vue';
import { ref, onMounted } from 'vue';
const childRef = ref(null);
const childMessage = ref('');
// 在组件渲染后,访问子组件暴露的数据
onMounted(() => {
childMessage.value = childRef.value.message;
});
</script>
<template>
<div>
<MySon ref="childRef" ></MySon>
<p>父组件从子组件获取的消息:{{ childMessage }}</p>
</div>
</template>
组合式API - provide 和 inject
1. 传递普通数据
2. 传递响应式数据
3.传递方法
App.vue
<script setup>
import {provide, ref} from 'vue'
import CenterCom from './components/CenterCom.vue';
// 1.夸层传递普通数据
provide('theme-color','black')
// 2.夸层传递响应式数据
const count = ref(100)
provide('count',count)
setTimeout(()=>{
count.value=666
},1000)
// 3.跨层级修改函数 =》给子孙后代传递可以修改数据的函数
provide('changeCount', (newCount) => {
count.value=newCount
})
</script>
<template>
<h1>Top</h1>
<CenterCom></CenterCom>
</template>
<style></style>
BottomCom.vue
<script setup>
import {inject, ref} from 'vue'
// 1
const themeColor = inject('theme-color')
// 2
const count = inject('count')
// 3.
const changeCount=inject('changeCount')
const clickFn = () => {
changeCount(1211)
}
</script>
<template>
<h4>Bottom
{{ themeColor }}
{{ count }}
<button @click="clickFn">更新count</button>
</h4>
</template>
<style></style>
CenterCom.vue
<script setup>
import {ref} from 'vue'
import BottomCom from './BottomCom.vue';
</script>
<template>
<h2>Center</h2>
<BottomCom></BottomCom>
</template>
<style></style>
Vue3.3 新特性 - defineOptions
Vue3.3 新特性 - defineModel
还需要配置:
App.vue
<script setup>
import {ref} from 'vue'
import MySon from '@/components/MySon.vue'
const txt = ref('123456')
</script>
<template>
<MySon v-model="txt"></MySon>
{{ txt }}
</template>
<style>
</style>
components / MySon.vue
<script setup>
defineProps({
modelValue:String
})
// 看来必须是modelValue才行,用其他名字。例如txt都不行
const emit = defineEmits(['update:modelValue'])
const changeTxt = (e) => {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input :value="modelValue" @input="changeTxt" type="text" >
</template>
<style>
</style>
Pinia 快速入门
手动添加Pinia到Vue项目
main.js
// import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia() //创建pinia实例
const app = createApp(App) //创建根实例
app.use(pinia) // pinia插件的安装配置
app.mount('#app') // 视图的挂载
App.vue
<script setup>
import Son1Com from '@/components/Son1Com.vue'
import Son2Com from '@/components/Son2Com.vue'
</script>
<template>
<div>App.vue跟组件</div>
<Son1Com></Son1Com>
<Son2Com></Son2Com>
</template>
Son2Com.vue
<script>
</script>
<template>
<div>
Son2
<button>+</button>
</div>
</template>
Son1Com.vue
<script>
</script>
<template>
<div>
Son1
<button>-</button>
</div>
</template>
Pinia基础使用
定义store 组件使用store
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()
console.log(counterStore);
</script>
<template>
<div>App.vue跟组件:
{{ counterStore.count }}
</div>
<Son1Com></Son1Com>
<Son2Com></Son2Com>
</template>
Son1Com.vue
<script setup>
import {useCounterStore } from '@/store/counter.js'
const counterStore = useCounterStore()
</script>
<template>
<div>
Son1:{{ counterStore.count }}
<br>
double:{{ counterStore.double }}
<button @click="counterStore.subCount">-</button>
</div>
</template>
Son2Com.vue
<script setup>
import {useCounterStore } from '@/store/counter.js'
const counterStore = useCounterStore()
</script>
<template>
<div>
Son2:{{ counterStore.count }}
<button @click="counterStore.addCount">+</button>
</div>
</template>
store / counter.js
import { defineStore } from 'pinia'
import {ref,computed } from 'vue'
// 定义store
// defineStore (仓库的唯一标识, () => { ... })
export const useCounterStore = defineStore('counter', ( ) => {
// 声明数据 state
const count = ref(0)
// 声明操作数据的方法 action (普通函数)
const addCount = () => count.value++
const subCount = () => count.value--
// 声明基于数据派生的计算属性getters (computed)
const double = computed(() => count.value*2)
const msg = ref('hello pinia')
return {
count,msg,addCount,subCount,double
}
})
action异步实现
store / channel.js
import { defineStore } from "pinia"
import { ref} from "vue"
import axios from "axios"
// 命名默认是 use + xxx + Store
export const useChannelStore = defineStore('channel',() => {
// 声明数据
const channelList = ref([])
// 声明操作数据方法
const getList = async () =>{
// 支持异步
const {data:{data}} = await axios.get('http://geek.itheima.net/v1_0/channels')
console.log(data.channels)
channelList.value = data.channels
}
return{
channelList,getList
}
// 声明getters相关
})
App.vue
<script setup>
import Son1Com from '@/components/Son1Com.vue'
import Son2Com from '@/components/Son2Com.vue'
import {useCounterStore } from '@/store/counter.js'
import {useChannelStore } from '@/store/channel.js'
const counterStore = useCounterStore()
console.log(counterStore);
const channelStore =useChannelStore()
</script>
<template>
<div>App.vue跟组件:
{{ counterStore.count }}
</div>
<Son1Com></Son1Com>
<Son2Com></Son2Com>
<button @click="channelStore.getList">获取频道数据</button>
<ul>
<li v-for="item in channelStore.channelList" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
storeToRefs方法
将 Vuex 或其他状态管理库中的状态对象转换为响应式的 Ref 对象,以便在 Vue 3 组件内部使用,方法直接解构
App.vue
<script setup>
import { storeToRefs } from "pinia"
import Son1Com from '@/components/Son1Com.vue'
import Son2Com from '@/components/Son2Com.vue'
import {useCounterStore } from '@/store/counter.js'
import {useChannelStore } from '@/store/channel.js'
const counterStore = useCounterStore()
console.log(counterStore);
// 直接解构,数据会丢失响应式
const {count,msg} = storeToRefs(counterStore)
const channelStore = useChannelStore()
// 方法直接解构
const { getList } = channelStore
// 属性要storeToRefs
const {channelList} = storeToRefs(channelStore)
</script>
<template>
<div>App.vue跟组件:{{ count }}
{{msg}}
</div>
<Son1Com></Son1Com>
<Son2Com></Son2Com>
<button @click="getList">获取频道数据</button>
<ul>
<li v-for="item in channelList" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
Pinia 调试
Pinia 持久化
1.安装
cnpm i pinia-plugin-persis
2.将插件添加到 pinia 实例上
main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
// 导入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
const app = createApp(App)
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.mount('#app')
counter.js
import { defineStore } from 'pinia'
import {ref,computed } from 'vue'
// 定义store
// defineStore (仓库的唯一标识, () => { ... })
export const useCounterStore = defineStore('counter', ( ) => {
// 声明数据 state
const count = ref(0)
// 声明操作数据的方法 action (普通函数)
const addCount = () => count.value++
const subCount = () => count.value--
// 声明基于数据派生的计算属性getters (computed)
const double = computed(() => count.value*2)
const msg = ref('hello pinia')
return {
count,msg,addCount,subCount,double
}
}, {
// persist: true,//开启当前模块持久化
persist: {
key:'hm-counter',//修改本地存储的唯一标识
paths:['count'] //存储的是哪些数据
},
}
)