046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject
046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject
·
046Vue3的官方推荐的三个组件传值解决方案:props、pinia(状态管理)、provide和inject
- 以下代码基于setup编写
props
<script setup>
const props = defineProps(['foo'])
// 或者 用下面的方式也可以
const props = defineProps({
foo: String
})
// js中使用方法props.foo,注意:前面加上props
console.log(props.foo)
</script>
<template>
<!--在html中直接调用foo即可,如果js中不需要使用props可以直接defineProps(['foo'])-->
<div>{{ foo }}</div>
</template>
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 strings
return ['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',
// 类型将自动推断为 number
nextId: 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缓存的key
locals: ['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
})
- 注入调用方式
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
三种方式的传值,按需使用
- 父子组件使用props
- 有层级关系的多个组件provide
- 跨无关联组件使用pinia
更多推荐
已为社区贡献3条内容
所有评论(0)