useCompExp: Data Sharing Within a Vue Component Tree
useCompExp is built on Vue’s provide / inject to share data and methods within a component tree, simplifying typescript types and eliminating the need to pass props / emit down through every level.
1. Difference from Pinia
- Pinia global state: suitable for sharing data across multiple unrelated component types.
- useCompExp component-tree data: scoped only to the descendants of the master component and collected when the component is destroyed. It is suitable for sharing data and methods tightly coupled with the UI, such as a search box and a
keywordvariable.
2. Basic Usage
Use useCompExp to declare the type.
export const useActivity = ({ isMaster = false } = {}) =>
useCompExp<{
freshList: () => any
keyword: Ref<string>
}>({ isMaster, key: 'activity' })
Root component
const { registerFunc, funcs } = useActivity({ isMaster: true })
const list = ref([])
const freshList = function() {
list.value = totalList.filter(item => item.name === funcs.keyword.value)
}
registerFunc({ freshList })
Child component
const { funcs, registerFunc } = useActivity()
// Register your own capabilities back to the master
registerFunc({
keyword: ref('')
})
// Click the search button to trigger a list refresh
const onSearch = function () {
funcs.freshList()
}
Note: the master must be an ancestor, otherwise you get an empty object without any error; also, child components should not pass isMaster: true by mistake.
3. Implementation Principle
The core is only a dozen or so lines:
export const useCompExp = function <T>({ isMaster = true, key = 'compExp' } = {} as any) {
if (isMaster) {
const funcs = {} as T
const registerFunc = funcList => Object.assign(funcs, funcList)
provide(key, { funcs, registerFunc })
return { funcs, registerFunc }
}
const { funcs, registerFunc } = inject(key, { funcs: {}, registerFunc: () => {} })
return { funcs, registerFunc } as { funcs: T; registerFunc: (f: Partial<T>) => any }
}
Key points:
- The master creates
funcsandprovides it to the subtree; whatinjectreceives is the same object reference. registerFuncusesObject.assignto merge in place, so the reference never changes and it is accessible before and after registration.funcsis a plain object, so reactivity is your responsibility: registerref/computed, not raw values.- Types are only compile-time assertions; fields that are not registered are
undefinedat runtime, so null-value protection is required.
Conclusion
Treat it as a complement to Pinia: leave global state to Pinia, and hand over capability exchange within a component tree to useCompExp.