The trinity pattern is Vuetify Zero’s signature approach to context management. It provides three ways to access the same functionality: injection, provision, and direct access.
// ❌ Problem: No default, fails outside component treeconst theme = inject('theme')// undefined if not provided
Trinity solution:
// ✅ Solution: Third element provides defaultconst [useTheme, provideTheme, theme] = createThemeContext()theme.cycle() // Works anywhere, even outside components
// ❌ Problem: Must mount component with providerimport { mount } from '@vue/test-utils'const wrapper = mount(Component, { global: { provide: { theme: mockTheme } }})
Trinity solution:
// ✅ Solution: Use third element directlyconst [, , theme] = createThemeContext()theme.cycle()expect(theme.current.value).toBe('dark')
// ❌ Problem: Need plugin for cross-tree sharingapp.use(ThemePlugin)
Trinity solution:
// ✅ Solution: Third element is shared by defaultconst [, , theme] = createThemeContext()// Access in any component without injectiontheme.current.value // 'light'
// Need all threeconst [useTheme, provideTheme, theme] = createThemeContext()// Only need use functionconst [useTheme] = createThemeContext()// Only need provide functionconst [, provideTheme] = createThemeContext()// Only need direct accessconst [, , theme] = createThemeContext()// Named exports for clarityexport { useTheme, provideTheme, theme as defaultTheme}
// ❌ Problems:// - No tree-scoped state// - Hard to test with different instances// - No dependency injection benefits// store.tsexport const store = createStore()// ComponentA.vueimport { store } from './store'store.count++// ComponentB.vue import { store } from './store'store.count++ // Same instance always
// ✅ Benefits:// - Tree-scoped state via provide/inject// - Easy to test with custom instances// - Fallback to default instance// store.tsexport const [useStore, provideStore, store] = createStoreContext()// ComponentA.vueprovideStore() // Provide scoped instance// ComponentB.vueconst store = useStore() // Inject scoped instancestore.count++// utils.tsimport { store } from './store'store.count++ // Direct access when needed
// ✅ Good - consumers choose what they needexport const [useTheme, provideTheme, theme] = createThemeContext()// ❌ Bad - limits flexibilityconst [useTheme] = createThemeContext()export { useTheme }
Use third element for non-component code
// ✅ Good - third element works anywhereimport { theme } from './composables/theme'export function apiCall() { const mode = theme.current.value return fetch(`/api?theme=${mode}`)}// ❌ Bad - can't inject outside componentsimport { useTheme } from './composables/theme'export function apiCall() { const theme = useTheme() // Error: not in component return fetch(`/api?theme=${theme.current.value}`)}
Provide at the right level
<!-- ✅ Good - provide at appropriate level --><App> <ThemeProvider> <!-- App-wide theme --> <TabsProvider> <!-- Component-specific tabs --> <TabItem /> </TabsProvider> </ThemeProvider></App><!-- ❌ Bad - everything at root --><App provide="theme,tabs,selection,form,..."> <!-- Hard to reason about scope --></App>