feat: support effect scope outside of component, fix #1505

This commit is contained in:
Guillaume Chau
2023-09-12 15:31:46 +02:00
parent 574bd8f2b1
commit f5e371f5e6
10 changed files with 205 additions and 27 deletions
@@ -20,6 +20,7 @@
"@vue/apollo-util": "workspace:*",
"graphql": "^16.7.1",
"graphql-tag": "^2.12.6",
"pinia": "^2.1.6",
"test-server": "workspace:*",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
@@ -1,22 +1,21 @@
<script lang="ts" setup>
import { computed } from 'vue'
import ChannelList from './ChannelList.vue'
import GlobalLoading from './GlobalLoading.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const displayChannels = computed(() => !route.matched.some(r => r.meta?.layout === 'blank'))
</script>
<template>
<GlobalLoading />
<div class="flex h-screen items-stretch bg-gray-100">
<ChannelList class="w-1/4 border-r border-gray-200" />
<ChannelList
v-if="displayChannels"
class="w-1/4 border-r border-gray-200"
/>
<router-view class="flex-1 overflow-auto" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ChannelList from './ChannelList.vue'
import GlobalLoading from './GlobalLoading.vue'
export default defineComponent({
name: 'App',
components: {
ChannelList,
GlobalLoading,
},
})
</script>
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import { useChannels } from '@/stores/channel'
const channelStore = useChannels()
</script>
<template>
<div
v-if="channelStore.loading"
class="p-12 text-gray-500"
>
Loading channels...
</div>
<div
v-else
class="flex flex-col bg-white"
>
<router-link
v-for="channel of channelStore.channels"
:key="channel.id"
v-slot="{ href, navigate, isActive }"
:to="{
name: 'channel',
params: {
id: channel.id,
},
}"
custom
>
<a
:href="href"
class="channel-link px-4 py-2 hover:bg-green-100 text-green-700"
:class="{
'bg-green-200 hover:bg-green-300 text-green-900': isActive,
}"
@click="navigate"
>
# {{ channel.label }}
</a>
</router-link>
</div>
</template>
@@ -0,0 +1,51 @@
<script lang="ts" setup>
import { useChannels } from '@/stores/channel'
import { onBeforeUnmount, ref, watch } from 'vue'
const channels = ref<any[]>([])
let unwatch: (() => void) | undefined
setTimeout(() => {
const channelStore = useChannels()
unwatch = watch(() => channelStore.channels, (newChannels) => {
channels.value = newChannels
}, {
immediate: true,
})
}, 0)
onBeforeUnmount(() => {
unwatch?.()
})
</script>
<template>
<div
v-if="channels"
class="flex flex-col bg-white"
>
<router-link
v-for="channel of channels"
:key="channel.id"
v-slot="{ href, navigate, isActive }"
:to="{
name: 'channel',
params: {
id: channel.id,
},
}"
custom
>
<a
:href="href"
class="channel-link px-4 py-2 hover:bg-green-100 text-green-700"
:class="{
'bg-green-200 hover:bg-green-300 text-green-900': isActive,
}"
@click="navigate"
>
# {{ channel.label }}
</a>
</router-link>
</div>
</template>
@@ -1,15 +1,13 @@
import { createApp, h, provide } from 'vue'
import { createApp } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { createPinia } from 'pinia'
import { apolloClient } from './apollo'
import App from './components/App.vue'
import { router } from './router'
import '@/assets/styles/tailwind.css'
const app = createApp({
setup () {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App),
})
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.provide(DefaultApolloClient, apolloClient)
app.mount('#app')
@@ -50,5 +50,19 @@ export const router = createRouter({
path: '/null-query',
component: () => import('./components/NullQuery.vue'),
},
{
path: '/pinia',
component: () => import('./components/ChannelListPinia.vue'),
meta: {
layout: 'blank',
},
},
{
path: '/pinia2',
component: () => import('./components/ChannelListPinia2.vue'),
meta: {
layout: 'blank',
},
},
],
})
@@ -0,0 +1,33 @@
import gql from 'graphql-tag'
import { useQuery } from '@vue/apollo-composable'
import { defineStore } from 'pinia'
import { computed, watch } from 'vue'
interface Channel {
id: string
label: string
}
export const useChannels = defineStore('channel', () => {
const query = useQuery<{ channels: Channel[] }>(gql`
query channels {
channels {
id
label
}
}
`)
const channels = computed(() => query.result.value?.channels ?? [])
watch(query.loading, value => {
console.log('loading', value)
}, {
immediate: true,
})
return {
loading: query.loading,
channels,
}
})
@@ -0,0 +1,19 @@
describe('Pinia', () => {
beforeEach(() => {
cy.task('db:reset')
})
it('with current instance', () => {
cy.visit('/pinia')
cy.get('.channel-link').should('have.lengthOf', 2)
cy.contains('.channel-link', '# General')
cy.contains('.channel-link', '# Random')
})
it('with effect scope only', () => {
cy.visit('/pinia2')
cy.get('.channel-link').should('have.lengthOf', 2)
cy.contains('.channel-link', '# General')
cy.contains('.channel-link', '# Random')
})
})
@@ -1,4 +1,4 @@
import { getCurrentInstance, inject } from 'vue-demi'
import { getCurrentInstance, getCurrentScope, inject } from 'vue-demi'
import { ApolloClient } from '@apollo/client/core/index.js'
export const DefaultApolloClient = Symbol('default-apollo-client')
@@ -35,7 +35,7 @@ export function useApolloClient<TCacheShape = any> (clientId?: ClientId): UseApo
// Save current client in current closure scope
const savedCurrentClients = currentApolloClients
if (!getCurrentInstance()) {
if (!getCurrentInstance() && !getCurrentScope()) {
resolveImpl = (id?: ClientId) => {
if (id) {
return resolveClientWithId(savedCurrentClients, id)
@@ -68,7 +68,7 @@ export function useApolloClient<TCacheShape = any> (clientId?: ClientId): UseApo
throw new Error(
`Apollo client with id ${
id ?? 'default'
} not found. Use provideApolloClient() if you are outside of a component setup.`,
} not found. Use an app.runWithContext() or provideApolloClient() if you are outside of a component setup.`,
)
}
return client
+21 -1
View File
@@ -189,6 +189,9 @@ importers:
graphql-tag:
specifier: ^2.12.6
version: 2.12.6(graphql@16.7.1)
pinia:
specifier: ^2.1.6
version: 2.1.6(typescript@5.0.2)(vue@3.3.4)
test-server:
specifier: workspace:*
version: link:../test-server
@@ -12834,6 +12837,24 @@ packages:
engines: {node: '>=6'}
dev: true
/pinia@2.1.6(typescript@5.0.2)(vue@3.3.4):
resolution: {integrity: sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==}
peerDependencies:
'@vue/composition-api': ^1.4.0
typescript: '>=4.4.4'
vue: '*'
peerDependenciesMeta:
'@vue/composition-api':
optional: true
typescript:
optional: true
dependencies:
'@vue/devtools-api': 6.5.0
typescript: 5.0.2
vue: 3.3.4
vue-demi: 0.14.6(@vue/composition-api@1.0.0)(vue@3.3.4)
dev: false
/pirates@4.0.5:
resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
engines: {node: '>= 6'}
@@ -15560,7 +15581,6 @@ packages:
resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==}
engines: {node: '>=12.20'}
hasBin: true
dev: true
/uglify-es@3.3.9:
resolution: {integrity: sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==}