046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject
props
{{ foo }}
props单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
- prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter'])// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
- 需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size'])// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
Prop 校验
defineProps({// 基础类型检查// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)propA: Number,// 多种可能的类型propB: [String, Number],// 必传,且为 String 类型propC: {type: String,required: true},// Number 类型的默认值propD: {type: Number,default: 100},// 对象类型的默认值propE: {type: Object,// 对象或数组的默认值// 必须从一个工厂函数返回。// 该函数接收组件所接收到的原始 prop 作为参数。default(rawProps) {return { message: 'hello' }}},// 自定义类型校验函数propF: {validator(value) {// The value must match one of these stringsreturn ['success', 'warning', 'danger'].includes(value)}},// 函数类型的默认值propG: {type: Function,// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数default() {return 'Default function'}}
})
pinia(状态管理)
import { defineStore } from 'pinia'export const useTodos = defineStore('todos', {state: () => ({/** @type {{ text: string, id: number, isFinished: boolean }[]} */todos: [],/** @type {'all' | 'finished' | 'unfinished'} */filter: 'all',// 类型将自动推断为 numbernextId: 0,}),getters: {finishedTodos(state) {// 自动补全! ✨return state.todos.filter((todo) => todo.isFinished)},unfinishedTodos(state) {return state.todos.filter((todo) => !todo.isFinished)},/*** @returns {{ text: string, id: number, isFinished: boolean }[]}*/filteredTodos(state) {if (this.filter === 'finished') {// 调用其他带有自动补全的 getters ✨return this.finishedTodos} else if (this.filter === 'unfinished') {return this.unfinishedTodos}return this.todos},},actions: {// 接受任何数量的参数,返回一个 Promise 或不返回addTodo(text) {// 你可以直接变更该状态this.todos.push({ text, id: this.nextId++, isFinished: false })},},
})
pinia的封装与引入
import { toStr } from '@/utils/Json';
import { createPinia, type PiniaPluginContext } from 'pinia'; // 引入pinia
import { toRaw } from 'vue';
const pinia = createPinia(); // 创建// 定义缓存管理用的配置数据类型
interface IStorageOption {key: string;keeps: {sessions: string[];locals: string[];};
}
// 缓存配置数据
const storageOption: IStorageOption = {key: '__pinia__', //缓存名key的前置字符串keeps: {sessions: [], // 采用seesionStorage缓存的keylocals: ['model', 'model00411'], // 采用localStorage缓存的key},
};
// 储存pinia的值
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));
}
// 从缓存中获取pinia的值
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);});
}
// 处理pinia缓存需要的中间件
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';
// 通过状态管理,得到一个ref的值
const modelStore = ModelStore().modelStore;
provide和inject
Provide (提供)
import { provide } from 'vue'provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
Inject (注入)
import { inject } from 'vue'// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const message = inject('message', '这是默认值')
建议尽可能将任何对响应式状态的变更都保持在供给方组件中
import { provide, ref } from 'vue'const location = ref('North Pole')function updateLocation() {location.value = 'South Pole'
}provide('location', {location,updateLocation
})
三种方式的传值,按需使用
- 父子组件使用props
- 有层级关系的多个组件provide
- 跨无关联组件使用pinia