Pinia
Pinia是vue的状态管理库。像一些想要多个组件公用的数据,可以交给pinia管理
pinia存储数据
pinia存取数据,要放在store目录下的各个ts文件中。例:
import { defineStore } from "pinia";
export const useCountStore = defineStore('Count',{
state(){
return {
sum:6
}
}
})
import { defineStore } from "pinia";
export const useTalkStore = defineStore('Talk',{
state(){
return {
talkList:[
{id:'001',title:'羊粑粑'},
{id:'002',title:'oi'},
{id:'003',title:'乌拉'}
]
}
}
})
将要存储的数据放在state配置项中,此配置项要写做一个函数,然后return所有要由pinia存储的数据。
导出的那个部分一般规范写作useXxxStore。
获取pinia存储的数据
例:
<script lang="ts" setup>
import { ref } from 'vue';
import {useCountStore} from '@/store/Count';
const countStore = useCountStore()
let n = ref(1)
function add(){
countStore.sum+=n.value
}
function dec(){
countStore.sum-=n.value
}
</script>
要先引入store下的ts文件默认导出的那个useXxxStore,比如此处是useCountStore,用变量接收后,此变量的名字一般是将前面的use去掉。
要获取pinia存储的数据,直接 xxxStore.数据名 获取即可。获取出来的数据是响应式的。
修改pinia存储的数据
修改pinia存储的数据有三种方式:
-
第一种方式就像上面的案例中的add和dec方法一样,直接xxxStore.数据名 = ……
-
第二种方式是使用patch的方式,可以同时修改多个pinia存储的数据。
countStore.$patch({ sum:99, school:'井冈山大学', address:'吉安' })
-
第三种方式是使用actions配置项的方式,actions配置项里面放置的是一个一个的方法,用于响应组件中的“动作”。
import { defineStore } from "pinia"; export const useCountStore = defineStore('Count',{ actions:{ imcrement(n:number){ this.sum += number } }, state(){ return { sum:6 } } })
要调用state中存储的数据,直接this.数据名即可。
使用:
function add(){ // countStore.sum+=n.value countStore.imcrement() }
如果觉得使用xxxStore.数据名比较不美观,可以将响应的数据解构出来,但是,解构的时候要让其数据是响应式的,要使用到一个函数:storeToRefs()
<template>
<div class="count">
<h2>当前求和为:{{ sum }}</h2>
<select v-model="n">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="add">加</button>
<button @click="dec">减</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {useCountStore} from '@/store/Count';
import { storeToRefs } from 'pinia';
const countStore = useCountStore()
let {sum} = storeToRefs(countStore)
let n = ref(1)
function add(){
countStore.imcrement(n.value)
}
function dec(){
countStore.sum-=n.value
}
</script>
如此,解构出来的sum就是一个响应式的值。
getters
要是想要对pinia中存储的数据进行加工,类似于计算属性,可以使用getters配置项:
import { defineStore } from "pinia";
export const useCountStore = defineStore('Count',{
actions:{
imcrement(n:number){
this.sum += n
}
},
state(){
return {
sum:6
}
},
getters:{
bigSum(state){
return state.sum * 10
}
//也可以写作 :state => state.sum * 10
}
})
如上,要取出这个bigSum数据,和state中的数据的获取方式是一样的,也是上面那三种方式。
$subscribe的使用
subscibe的作用类似于watch监视
通过 store 的 $subscribe()
方法侦听 state
及其变化
talkStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
localStorage.setItem('talk',JSON.stringify(talkList.value))
})
mutate是修改前的数据,state是修改后的数据。可以在其中做一些类似于监视的逻辑处理。
store组合式写法
将defineStore函数的第二个参数写作箭头函数的格式,在其中写组合式的形式的ts代码:
import { defineStore } from "pinia";
import axios from 'axios';
import {nanoid} from 'nanoid';
import { reactive } from "vue";
export const useTalkStore = defineStore('Talk',()=>{
const talkList = reactive([
{id:'001',title:'羊粑粑'},
{id:'002',title:'oi'},
{id:'003',title:'乌拉'}
]);
async function getAllTalk(){
let result = await axios.post('<https://tenapi.cn/v2/yiyan>')
console.log(result)
let {data} = result
let obj = {id:nanoid(),title:data}
talkList.unshift(obj)
console.log(talkList)
}
return {talkList,getAllTalk}
})
组件通信
方式1:props
父组件可以利用props将数据传给子组件,子组件可以通过父组件props传过来的方法,将数据传给父组件。例:
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车:{{ car }}</h4>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<Child :car="car" :sendToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
// 数据
let car = ref('奔驰')
let toy = ref('')
// 方法
function getToy(value:string){
toy.value = value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<h4>父给的车:{{ car }}</h4>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>
<script setup lang="ts" name="Child">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收props
defineProps(['car','sendToy'])
</script>
方式二:自定义事件
父组件也可以定义自定义事件让子组件接收,让子组件去触发这个自定义事件,完成数据的传递。
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<!-- 给子组件Child绑定事件 -->
<Child @send-toy="saveToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
let toy = ref('')
// 用于保存传递过来的玩具
function saveToy(value:string){
console.log('saveToy',value)
toy.value = value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emit('send-toy',toy)">把玩具给父组件</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])
</script>
方式三:mitt
可以使用mitt,来绑定事件。
首先,先写好emitter.ts,使用mitt获取得到一个emitter对象暴露出去。
// 引入mitt
import mitt from 'mitt';
// 调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()
// 暴露emitter
export default emitter
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<Child1/>
<Child2/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
</script>
子组件1:
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emitter.emit('send-toy',toy)">玩具给弟弟</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import {ref} from 'vue'
import emitter from '@/utils/emitter';
// 数据
let toy = ref('奥特曼')
</script>
子组件2:
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>哥哥给的玩具:{{ toy }}</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import {ref,onUnmounted} from 'vue'
import emitter from '@/utils/emitter';
// 数据
let computer = ref('联想')
let toy = ref('')
// 给emitter绑定send-toy事件
emitter.on('send-toy',(value:any)=>{
toy.value = value
})
// 在组件卸载时解绑send-toy事件
onUnmounted(()=>{
emitter.off('send-toy')
})
</script>
如此便能实现兄弟组件的数据传递。
方式四:使用v-model
组件的v-model,底层其实还是使用自定义事件来实现的。例如:
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>{{ username }}</h4>
<h4>{{ password }}</h4>
<!-- v-model用在html标签上 -->
<!-- <input type="text" v-model="username"> -->
<!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> -->
<!-- v-model用在组件标签上 -->
<!-- <AtguiguInput v-model="username"/> -->
<!-- <AtguiguInput
:modelValue="username"
@update:modelValue="username = $event"
/> -->
<!-- 修改modelValue -->
<AtguiguInput v-model:ming="username" v-model:mima="password"/>
</div>
</template>
<script setup lang="ts" name="Father">
import { ref } from "vue";
import AtguiguInput from './AtguiguInput.vue'
// 数据
let username = ref('zhansgan')
let password = ref('123456')
</script
子组件:即输入框的那个组件
<template>
<input
type="text"
:value="ming"
@input="emit('update:ming',(<HTMLInputElement>$event.target).value)"
>
<br>
<input
type="text"
:value="mima"
@input="emit('update:mima',(<HTMLInputElement>$event.target).value)"
>
</template>
<script setup lang="ts" name="AtguiguInput">
defineProps(['ming','mima'])
const emit = defineEmits(['update:ming','update:mima'])
</script>
方式五:$attrs
如果父组件传给子组件props的数据,而子组件没有接收,那这些没有被接收的值就会变成$attrs的数据。例:
父组件:给子组件传递了六个props数据,有四个没被接收
<template>
<div class="father">
<h3>父组件</h3>
<h4>a:{{a}}</h4>
<h4>b:{{b}}</h4>
<h4>c:{{c}}</h4>
<h4>d:{{d}}</h4>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value:number){
a.value += value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(6)">点我将爷爷那的a更新</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>
如此便可以实现多代通信。
方式六:$refs和$parent
$refs用于:父传子
$parent用于:子传父
$refs值为对象,包含所有被ref属性标识的DOM元素活组件实例。
$parent值为对象,当前组件的父组件的实例对象。
例:
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产:{{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref,reactive } from "vue";
let c1 = ref()
let c2 = ref()
// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的
/* let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x) */
// 数据
let house = ref(4)
// 方法
function changeToy(){
c1.value.toy = '小猪佩奇'
}
function changeComputer(){
c2.value.computer = '华为'
}
function getAllChild(refs:{[key:string]:any}){
console.log(refs)
for (let key in refs){
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({house})
</script>
子组件1:
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<h4>书籍:{{ book }} 本</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent:any){
parent.house -= 1
}
// 把数据交给外部
defineExpose({toy,book})
</script>
子组件2:
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>书籍:{{ book }} 本</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import { ref } from "vue";
// 数据
let computer = ref('联想')
let book = ref(6)
// 把数据交给外部
defineExpose({computer,book})
</script>
要让父组件能访问到子组件的信息,要将对应的数据使用defineExpose暴露出去。
同时,要让子组件能访问到父组件的信息,也要将对应的数据用defineExpose暴露出去。
方式七:provide-inject
provide-inject可以用于祖孙之间传递数据。provide传递数据使用k-v的形式,第一个参数写key,第二个参数写value。inject获取数据的时候,第一个参数指定要获取的key,第二个参数可以指定默认值,指定默认值后,可以使得ts校验直接通过默认值的格式去判断。
例:
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>银子:{{ money }}万元</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<Child/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref,reactive,provide} from 'vue'
let money = ref(100)
let car = reactive({
brand:'奔驰',
price:100
})
function updateMoney(value:number){
money.value -= value
}
// 向后代提供数据
provide('moneyContext',{money,updateMoney})
provide('car',car)
</script>
子组件:
<template>
<div class="child">
<h3>我是子组件</h3>
<GrandChild/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>银子:{{ money }}</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<button @click="updateMoney(6)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from "vue";
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
let car = inject('car',{brand:'未知',price:0})
</script>
方式八:pinia
见以上关于pinia的内容
方式九:插槽
默认插槽
直接在子组件的双标签中,写上页面结构,此页面结构读取父组件的值。子组件中的页面结构上写上slot标签,如此包在子组件的双标签中的页面结构就会替代掉slot标签。例:
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category title="热门游戏列表">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
<Category title="今日美食城市">
<img :src="imgUrl" alt="">
</Category>
<Category title="今日影视推荐">
<video :src="videoUrl" controls></video>
</Category>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Category from './Category.vue'
import { ref,reactive } from "vue";
let games = reactive([
{id:'asgytdfats01',name:'英雄联盟'},
{id:'asgytdfats02',name:'王者农药'},
{id:'asgytdfats03',name:'红色警戒'},
{id:'asgytdfats04',name:'斗罗大陆'}
])
let imgUrl = ref('<https://z1.ax1x.com/2023/11/19/piNxLo4.jpg>')
let videoUrl = ref('<http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4>')
</script>
子组件:
<template>
<div class="category">
<h2>{{title}}</h2>
<slot>默认内容</slot>
</div>
</template>
<script setup lang="ts" name="Category">
defineProps(['title'])
</script>
具名插槽
使用v-slot属性,并指定一个slot的名称,然后加在 组件标签 或者 template标签 上,使用 v-slot:名称 的方式也可以简写为#名称。接着子组件中,插槽slot标签指定下name属性,写上此插槽的名称。便会根据名称匹配插槽。默认插槽的名称为default。
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category>
<template v-slot:s2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template v-slot:s1>
<h2>热门游戏列表</h2>
</template>
</Category>
<Category>
<template v-slot:s2>
<img :src="imgUrl" alt="">
</template>
<template v-slot:s1>
<h2>今日美食城市</h2>
</template>
</Category>
<Category>
<template #s2>
<video video :src="videoUrl" controls></video>
</template>
<template #s1>
<h2>今日影视推荐</h2>
</template>
</Category>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Category from './Category.vue'
import { ref,reactive } from "vue";
let games = reactive([
{id:'asgytdfats01',name:'英雄联盟'},
{id:'asgytdfats02',name:'王者农药'},
{id:'asgytdfats03',name:'红色警戒'},
{id:'asgytdfats04',name:'斗罗大陆'}
])
let imgUrl = ref('<https://z1.ax1x.com/2023/11/19/piNxLo4.jpg>')
let videoUrl = ref('<http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4>')
</script>
子组件:
<template>
<div class="category">
<slot name="s1">默认内容1</slot>
<slot name="s2">默认内容2</slot>
</div>
</template>
作用域插槽
如果要使用插槽,而且不是使用的父组件的数据,而是使用子组件的数据,就要使用作用域插槽。
比如在父组件的插槽部分的template标签或组件标签上,加上v-slot=”xxx”,子组件的slot标签上加上props属性,比如此props写作 :game=“games”,那么这个games变量的值,就会放到xxx的game属性中。
例:
父组件
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Game>
<template v-slot="params">
<ul>
<li v-for="y in params.youxi" :key="y.id">
{{ y.name }}
</li>
</ul>
</template>
</Game>
<Game>
<template v-slot="params">
<ol>
<li v-for="item in params.youxi" :key="item.id">
{{ item.name }}
</li>
</ol>
</template>
</Game>
<Game>
<template #default="{youxi}">
<h3 v-for="g in youxi" :key="g.id">{{ g.name }}</h3>
</template>
</Game>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Game from './Game.vue'
</script>
子组件
<template>
<div class="game">
<h2>游戏列表</h2>
<slot :youxi="games" x="哈哈" y="你好"></slot>
</div>
</template>
<script setup lang="ts" name="Game">
import {reactive} from 'vue'
let games = reactive([
{id:'asgytdfats01',name:'英雄联盟'},
{id:'asgytdfats02',name:'王者农药'},
{id:'asgytdfats03',name:'红色警戒'},
{id:'asgytdfats04',name:'斗罗大陆'}
])
</script>
作用域插槽通常用于,数据在子组件,而结构是由父组件决定的。
如果作用域插槽也是具名的,那么使用v-slot:名称=”xxx”,来指定具名插槽即可