046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject
props
<script setup>
const props = defineProps(['foo'])
const props = defineProps({
foo: String
})
console.log(props.foo)
</script>
<template>
<div>{{ foo }}</div>
</template>
props单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
- prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])
const counter = ref(props.initialCounter)
- 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])
const normalizedSize = computed(() => props.size.trim().toLowerCase())
Prop 校验
defineProps({
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
},
propD: {
type: Number,
default: 100
},
propE: {
type: Object,
default(rawProps) {
return { message: 'hello' }
}
},
propF: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
},
propG: {
type: Function,
default() {
return 'Default function'
}
}
})
pinia(状态管理)
import { defineStore } from 'pinia'
export const useTodos = defineStore('todos', {
state: () => ({
todos: [],
filter: 'all',
nextId: 0,
}),
getters: {
finishedTodos(state) {
return state.todos.filter((todo) => todo.isFinished)
},
unfinishedTodos(state) {
return state.todos.filter((todo) => !todo.isFinished)
},
filteredTodos(state) {
if (this.filter === 'finished') {
return this.finishedTodos
} else if (this.filter === 'unfinished') {
return this.unfinishedTodos
}
return this.todos
},
},
actions: {
addTodo(text) {
this.todos.push({ text, id: this.nextId++, isFinished: false })
},
},
})
pinia的封装与引入
import { toStr } from '@/utils/Json';
import { createPinia, type PiniaPluginContext } from 'pinia';
import { toRaw } from 'vue';
const pinia = createPinia();
interface IStorageOption {
key: string;
keeps: {
sessions: string[];
locals: string[];
};
}
const storageOption: IStorageOption = {
key: '__pinia__',
keeps: {
sessions: [],
locals: ['model', 'model00411'],
},
};
function setStorage(
key: string,
val: any,
storageType: Storage = localStorage
) {
const obj = { v: val };
console.log(666.789, key);
storageType.setItem(storageOption.key + key, toStr(obj));
}
function getStorage(key: string, storageType: Storage = localStorage) {
const val = storageType.getItem(storageOption.key + key);
const obj = val ? JSON.parse(val as string) : null;
return obj?.v || null;
}
function doStorage(
key: string,
store: any,
storageType: Storage = localStorage
) {
const storageResult = getStorage(key, storageType);
if (storageResult) {
console.log(666.7007, '读取啦', storageResult, key, toRaw(store.$state));
store.$patch(() => {
store.$state = { ...storageResult };
});
}
store.$subscribe(() => {
console.log(666.7002, '更新啦', key, toRaw(store.$state));
setStorage(key, toRaw(store.$state), storageType);
});
}
function useStorage() {
return (ctx: PiniaPluginContext) => {
const { store } = ctx;
const sid = store.$id;
if (storageOption.keeps.locals.includes(sid)) {
doStorage(sid, store, localStorage);
} else if (storageOption.keeps.sessions.includes(sid)) {
doStorage(sid, store, sessionStorage);
}
};
}
pinia.use(useStorage());
export default pinia;
引入
import { createApp } from 'vue';
import App from './App.vue';
import pinia from './stores';
const app = createApp(App);
app.use(pinia);
app.mount('#app');
项目中使用方法
import { defineStore } from 'pinia';
const modelStoreTmp = {
type: 1,
data: {
val: 3,
item: { a: 1, b: 2 },
},
};
export default defineStore('piniaName', {
state: () => {
return {
modelStore: modelStoreTmp,
};
},
actions: {},
});
import ModelStore from './store';
const modelStore = ModelStore().modelStore;
provide和inject
Provide (提供)
import { provide } from 'vue'
provide( 'message', 'hello!')
Inject (注入)
import { inject } from 'vue'
const message = inject('message', '这是默认值')
建议尽可能将任何对响应式状态的变更都保持在供给方组件中
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
三种方式的传值,按需使用
- 父子组件使用props
- 有层级关系的多个组件provide
- 跨无关联组件使用pinia