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