一、ref(基本类型数据,对象类型数据)
1.作用:定义响应式变量
2.语法:let xxx = ref(初始值)
3.返回值:一个RefImpl的实例对象,简称ref对象,ref对象的value属性是响应式的。
4.注意:js中操作数据需要写xxx.vslue,模板上不需要.value,直接使用。let xxx = ref(初始值),xxx不是响应式的,xxx.value是响应式的。
二、reactive(对象类型数据)
1.作用:定义响应式变量
2.语法:let xxx = reactive(源对象)
3.返回值:一个Proxy的实例对象,简称响应式对象。
4.注意:reactive定义的响应式数据是深层次的。
三、ref对比reactive
1.ref 用来定义:基本类型数据,对象类型数据
reactive用来定义:对象类型数据
2.区别
ref创建的变量必须使用.value
reactive重新分配一个新对象,会失去响应式。(可以使用Object.assign去整体替换)
3.使用原则
若需要一个基本类型的响应式数据,必须使用ref。
若需要一个响应式对象,层级不深,ref,reactive都可以。
若需要一个响应式对象,层级较深,推荐使用reactive。
四、toRefs和toRef
1.作用:将一个响应式对象中的每一个属性,转换为ref对象。
2.toRefs与toRef功能一致,但toRefs可以批量转换。
<template>
<div>
{{ name }}{{ age }}
</div>
</template>
<script setup>
import { reactive, toRefs, toRef } from 'vue'
let person=reactive({name:'zs',age:18})
//解构赋值后失去响应式
let {name,age} = toRefs(person)
console.log('name,age',name.value,age.value);
let n1 = toRef(person, 'age')
console.log(n1.value);
</script>
五、computed
<template>
<div>
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
全名:<span>{{ fullName }}</span>
全名:<span>{{ fullName }}</span><br>
<button @click="changeFullName">修改名字</button>
</div>
</template>
<script setup>
import { ref,computed} from 'vue'
let firstName=ref('z')
let lastName=ref('s')
// 定义的fullName是一个计算属性,而且是只读的
// let fullName = computed(()=>{
// console.log('1');
// return firstName.value.slice(0,1).toUpperCase()+firstName.value.slice(1)+lastName.value.slice(0,1).toUpperCase()+lastName.value.slice(1)
// })
// 定义的fullName是一个计算属性,可读可写
let fullName = computed({
get(){
console.log('1')
return firstName.value.slice(0,1).toUpperCase()+firstName.value.slice(1)+'-'+lastName.value.slice(0,1).toUpperCase()+lastName.value.slice(1)
},set(val){
let [str1,str2]=val.split('-');
firstName.value=str1;
lastName.value=str2
}
})
let changeFullName = ()=>{
fullName.value='li-si'
}
</script>
六、watch
1.作用:监视数据的变化
2.只能监视以下四种数据
ref定义的数据,reactive定义的数据,getter函数(一个函数,返回一个值),一个包含上述内容的数组。
a.监听ref定义的基本类型数据,对象类型数据
监视的是对象的地址值,想要监视内部属性的变化,需要deep属性为true
<template>
<div>
<p>监视ref定义的基本类型数据{{ sum }} <button @click="sum+=1">+1</button></p>
<p>监视ref定义的对象类型数据{{ person.name }} {{ person.age }}
<button @click="person.name+='~'">changeName</button>
<button @click="person.age+=1">changeAge</button>
<button @click="changePerson">changePerson</button>
</p>
</div>
</template>
<script setup>
import { ref,watch} from 'vue'
let sum=ref(0)
const stopWatch= watch(sum,(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
if(newVal>10){
stopWatch()
}
})
let person=ref({
name:'zs',
age:18
})
let changePerson = ()=>{
person.value={
name:'ls',
age:90
}
}
//监视的是person的地址值,想要监视内部属性的变化,需要deep属性为true
watch(person,(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
},{
deep:true,
immediate:true
})
</script>
b.监听reactive定义的对象类型数据
监视的是reactive定义的对象类型数据,默认开启深度监听,该深度监听不能关闭
<template>
<div>
<p>监视reactive定义的对象类型数据{{ person.name }} {{ person.age }}
<button @click="person.name+='~'">changeName</button>
<button @click="person.age+=1">changeAge</button>
<button @click="changePerson">changePerson</button>
</p>
</div>
</template>
<script setup>
import { reactive,watch} from 'vue'
let person=reactive({
name:'zs',
age:18
})
let changePerson = ()=>{
Object.assign( person,{
name:'ls',
age:90
})
}
//监视的是reactive定义的对象类型数据,默认开启深度监听,该深度监听不能关闭
watch(person,(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
},{
// deep:false,
immediate:true
})
</script>
c.监视ref或reactive定义的对象类型数据中的某个属性
<template>
<div>
<p>监视响应式对象的的某个属性{{person.name}}{{ person.car.c1 }} {{ person.car.c2 }}
<button @click="person.name='ls'">changeName</button>
<button @click="person.car.c1='大宝马'">changeC1</button>
<button @click="person.car.c2='大奔驰'">changeC2</button>
<button @click="changeCar">changeCar</button>
</p>
</div>
</template>
<script setup>
import { reactive,watch} from 'vue'
let person=reactive({
name:'zs',
age:18,
car:{
c1:'宝马',
c2:'奔驰'
}
})
let changeCar = ()=>{
person.car={
c1:'爱玛',
c2:'绿驹'
}
}
//监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
watch(()=>person.name,(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
})
//监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也可以写成函数式,更推荐写函数式,对象监视的是地址值,对象内部需要手动开启深度监听。
watch(()=>person.car,(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
},{
deep:true
})
</script>
4.监视多个对象
<template>
<div>
<p>监视多个数据{{person.name}}{{ person.car.c1 }} {{ person.car.c2 }}
<button @click="person.name='ls'">changeName</button>
<button @click="person.car.c1='大宝马'">changeC1</button>
<button @click="person.car.c2='大奔驰'">changeC2</button>
<button @click="changeCar">changeCar</button>
</p>
</div>
</template>
<script setup>
import { reactive,watch} from 'vue'
let person=reactive({
name:'zs',
age:18,
car:{
c1:'宝马',
c2:'奔驰'
}
})
let changeCar = ()=>{
person.car={
c1:'爱玛',
c2:'绿驹'
}
}
//监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也可以写成函数式,更推荐写函数式,对象监视的是地址值,对象内部需要手动开启深度监听。
watch([()=>person.name,()=>person.car],(newVal,oldVal)=>{
console.log('newVal,oldVal', newVal,oldVal);
},{
deep:true
})
</script>
七、watchEffect
立即运行一个函数,同时响应式地追踪其依赖,并在依赖更新时重新执行该函数。
watch对比watchEffect
都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch需要明确指出监视的数据
watchEffect不用明确指出监视的数据,函数中用到哪些属性,就会监听哪些属性。
<template>
<div>
<p>watchEffect{{water.temp}}{{ water.height }}
<button @click="water.temp+=10">温度+10</button>
<button @click="water.height+=10">高度+10</button>
</p>
</div>
</template>
<script setup>
import { reactive,watchEffect} from 'vue'
let water=reactive({
temp:0,
height:0,
})
watchEffect(()=>{
if(water.temp>=60||water.height>=80)
console.log('water.temp,water.height',water.temp,water.height);
})
</script>
八、标签的ref属性
//父组件
<template>
<HelloWorld ref="helloRef" />
<button @click="logHelloRef">test</button>
</template>
<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
const helloRef = ref(null);
const logHelloRef = () => {
console.log('helloRef', helloRef.value);
};
</script>
//子组件
<template>
<div>
<p>watchEffect{{water.temp}}{{ water.height }}
<button @click="water.temp+=10">温度+10</button>
<button @click="water.height+=10">高度+10</button>
</p>
</div>
</template>
<script setup>
import { reactive,watchEffect,defineExpose} from 'vue'
let water=reactive({
temp:0,
height:0,
})
watchEffect(()=>{
if(water.temp>=60||water.height>=80)
console.log('water.temp,water.height',water.temp,water.height);
})
defineExpose({water})
</script>
九、props
defineProps可以省略不引入
//父组件
<template>
<HelloWorld :a="a" :b="list" c=123 />
</template>
<script setup>
import { ref,reactive } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
let a=ref('aaaaaaaaaaa');
let list=reactive([
{
msg:'aa',
num:0
},
{
msg:'bb',
num:1
},
]);
</script>
//子组件
<template>
<div>
{{ props.a }}{{ props.c }}
<ul>
<li v-for="item in b" :key="item.num">{{ item.num }}{{ item.msg }}</li>
</ul>
</div>
</template>
<script setup>
import { defineProps} from 'vue'
const props=defineProps({
a:{
type:String,
required:true,
default:'abc'
},
b:{
type:Array,
required:true,
default:[]
},
c:{
type:Number,
required:true,
default:0
}
})
</script>
十、生命周期
<template>
<HelloWorld v-if="isShow"/>
<button @click="isShow=!isShow">flag</button>
</template>
<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
let isShow = ref(true)
</script>
<template>
<div>
{{ sum }}
<button @click="sum+=1">+1</button>
</div>
</template>
<script setup>
import {ref, onBeforeMount, onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
let sum = ref(0)
console.log('创建');
onBeforeMount(()=>{
console.log('挂载前');
})
onMounted(()=>{
console.log('挂载完毕');
})
onBeforeUpdate(()=>{
console.log('更新前');
})
onUpdated(()=>{
console.log('更新完毕');
})
onBeforeUnmount(()=>{
console.log('卸载前');
})
onUnmounted(()=>{
console.log('卸载完毕');
})
</script>
十一、hooks
组合式函数约定用驼峰命名法命名,并以“use”作为开头
<template>
<div>
{{ sum }}{{ bigSum }}
<button @click="add">+1</button>
</div>
</template>
<script setup>
import useSum from '../hooks/useSum'
let {sum,add,bigSum}=useSum()
</script>
useSum.js
import { onMounted, ref, computed } from 'vue'
export default function() {
//数据
let sum = ref(0);
//方法
const add = () => {
sum.value += 1
};
//计算属性
let bigSum = computed(() => {
return sum.value * 10
});
//钩子
onMounted(() => {
add()
})
return { sum, add, bigSum }
}
十二、路由
1.params写法
传递params参数时,若使用to的对象写法,必须使用name配置项,不能用path。
传递params参数时,需要提前在规则中占位。
路由的props配置,让路由组件更方便的收到参数,将路由参数作为props传给组件
路由配置router.js
import Home from '../components/HelloWorld.vue';
import News from '../components/NewsView.vue';
import Detail from '../components/DetailView.vue';
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/news',
component: News,
children: [{
path: 'detail/:id/:title/:content',
name: 'detail',
component: Detail,
props: true
}]
}
]
})
export default router;
路由跳转(三种写法)
<template>
<div>
<ul>
<li v-for="news in list" :key="news.id">
<button @click="gotoDetail(news)">{{ news.title }}</button>
<router-link :to="`/news/detail/${news.id}/${news.title}/${news.content}`">{{ news.title }}</router-link>
<router-link :to="{
name:'detail',
params:{
id:news.id,
title:news.title,
content:news.content
}
}
">{{ news.title }}</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const list = reactive([
{
id:'efadsiub01',
title:'新闻1',
content:'xxxxxxxxxxxxxxxxxx1'
},
{
id:'efadsiub02',
title:'新闻2',
content:'xxxxxxxxxxxxxxxxxx2'
},
{
id:'efadsiub03',
title:'新闻3',
content:'xxxxxxxxxxxxxxxxxx3'
}
])
const router = useRouter()
const gotoDetail=(news)=>{
router.push({
name:'detail',
params:{
id:news.id,
title:news.title,
content:news.content
}
})
}
</script>
//接收参数
<template>
<div>
<p>序号:{{ route.params.id }}</p>
<p>标题:{{ route.params.title }}</p>
<p>内容:{{ route.params.content }}</p>
<p>序号:{{ id }}</p>
<p>标题:{{ title }}</p>
<p>内容:{{ content }}</p>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute()
defineProps(['id','title','content'])
</script>
2.query写法
//路由配置router.js
import Home from '../components/HelloWorld.vue';
import News from '../components/NewsView.vue';
import Detail from '../components/DetailView.vue';
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/news',
component: News,
children: [{
path: 'detail',
name: 'detail',
component: Detail,
props(route) {
return route.query
}
}]
}
]
})
export default router;
//路由跳转并传参
<template>
<div>
<ul>
<li v-for="news in list" :key="news.id">
<button @click="gotoDetail(news)">{{ news.title }}</button>
<router-link :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">{{ news.title }}</router-link>
<router-link :to="{
name:'detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
}
">{{ news.title }}</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const list = reactive([
{
id:'efadsiub01',
title:'新闻1',
content:'xxxxxxxxxxxxxxxxxx1'
},
{
id:'efadsiub02',
title:'新闻2',
content:'xxxxxxxxxxxxxxxxxx2'
},
{
id:'efadsiub03',
title:'新闻3',
content:'xxxxxxxxxxxxxxxxxx3'
}
])
const router = useRouter()
const gotoDetail=(news)=>{
router.push({
name:'detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
})
}
</script>
//接收参数
<p>内容:{{ route.query.content }}</p>
<p>序号:{{ id }}</p>
<p>标题:{{ title }}</p>
<p>内容:{{ content }}</p>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute()
defineProps(['id','title','content'])
</script>
十三、pinia
1.搭建pinia
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
2.存储和读取数据(state,actions,getters)
store/count.js
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
state() {
return {
sum: 0,
school: '第一学校',
address: '西安',
time: new Date()
}
},
actions: {
//this就是当前仓库
increment(val) {
console.log('increment');
this.sum += val
}
},
getters: {
bigSum: (state) => {
return state.sum * 10
},
formatDate(state) {
const date = state.time;
const year = date.getFullYear(); // 年
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月,注意月份从0开始
const day = String(date.getDate()).padStart(2, '0'); // 日
const hours = String(date.getHours()).padStart(2, '0'); // 小时
const minutes = String(date.getMinutes()).padStart(2, '0'); // 分
const seconds = String(date.getSeconds()).padStart(2, '0'); // 秒
return {
'YYYY-MM-DD': `${year}-${month}-${day}`,
'DD/MM/YYYY': `${day}/${month}/${year}`,
'MM-DD-YYYY': `${month}-${day}-${year}`,
'YYYY/MM/DD HH:mm:ss': `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`,
'YYYY.MM.DD': `${year}.${month}.${day}`
};
}
}
})
<template>
<div>
<h2>求和:{{ countStore.sum }},放大10倍:{{ bigSum }}</h2>
<p>
学校:{{ countStore.school }}
</p>
<p>{{time}}</p>
<p>,{{ formatDate }}</p>
<p>位置:{{ countStore.address }}</p>
<button @click="add">批量修改</button>
</div>
</template>
<script setup>
import { ref,toRefs } from 'vue';
import { useCountStore } from '../store/count';
import {storeToRefs} from 'pinia'
const countStore=useCountStore()
//解构赋值丢失了响应式
// const {sum,school,address} = countStore
//store中所有属性和方法都包裹成ref对象
// const {sum,school,address} = toRefs(countStore)
//storeToRefs只会关注store中的数据,不会对方法进行包裹
const {sum,school,address,time,bigSum,formatDate} = storeToRefs(countStore)
//以下两种方式都可以拿到state中的数据
console.log('sum',countStore.sum);
console.log('sum',countStore.$state.sum);
const add = ()=>{
//第一种修改方式
// countStore.sum+=1;
//第二种修改方式
// countStore.$patch({
// sum: 1,
// school: '第二学校',
// address: '西安1'
// })
//第三种修改方式
countStore.increment(30)
}
</script>
3.使用$subscribe订阅数据的变化
store/talk.js
import { defineStore } from 'pinia'
import axios from 'axios'
import { nanoid } from 'nanoid'
export const useTalkStore = defineStore('talk', {
state() {
return {
list: JSON.parse(localStorage.getItem('talkList')) || []
}
},
actions: {
async getLove() {
let res = await axios.get('https://v1.hitokoto.cn')
let obj = {
id: nanoid(),
title: res.data.hitokoto
}
this.list.unshift(obj)
}
}
})
<template>
<div>
<button @click="getLoveFn">获取一句土味情话</button>
<ul>
<li v-for="item in list" :key="item.id">{{ item.title }}</li>
</ul>
</div>
</template>
<script setup>
import {useTalkStore } from '../store/talk'
import {storeToRefs} from 'pinia'
const talkStore=useTalkStore()
talkStore.$subscribe((mutate,state)=>{
console.log('talkStore保存的数据发生了变化',mutate,state);
localStorage.setItem('talkList',JSON.stringify(state.list))
})
const {list}=storeToRefs(talkStore)
const getLoveFn=()=>{
talkStore.getLove()
}
</script>
4.store组合式写法
import { defineStore } from 'pinia'
import axios from 'axios'
import { nanoid } from 'nanoid'
import { reactive } from 'vue'
export const useTalkStore = defineStore('talk', () => {
const list = reactive(JSON.parse(localStorage.getItem('talkList')) || [])
async function getLove() {
let res = await axios.get('https://v1.hitokoto.cn')
let obj = {
id: nanoid(),
title: res.data.hitokoto
}
list.unshift(obj)
}
return { list, getLove }
})