mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
feat(nuxt): allow accessing NuxtPage
ref via pageRef
(#19403)
This commit is contained in:
parent
c6a62268c3
commit
319935fc95
@ -46,6 +46,23 @@ definePageMeta({
|
|||||||
|
|
||||||
:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/nuxt/tree/main/examples/routing/pages?file=app.vue" blank}
|
:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/nuxt/tree/main/examples/routing/pages?file=app.vue" blank}
|
||||||
|
|
||||||
|
## Accessing a page's component ref
|
||||||
|
|
||||||
|
To get the ref of a page component, access it through `ref.value.pageRef`
|
||||||
|
|
||||||
|
````html
|
||||||
|
<template>
|
||||||
|
<NuxtPage ref="page" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const page = ref()
|
||||||
|
function logFoo () {
|
||||||
|
page.value.pageRef.foo()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
````
|
||||||
|
|
||||||
## Custom Props
|
## Custom Props
|
||||||
|
|
||||||
In addition, `NuxtPage` also accepts custom props that you may need to pass further down the hierarchy. These custom props are accessible via `attrs` in the Nuxt app.
|
In addition, `NuxtPage` also accepts custom props that you may need to pass further down the hierarchy. These custom props are accessible via `attrs` in the Nuxt app.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Suspense, Transition, computed, defineComponent, h, nextTick, onMounted, provide, reactive } from 'vue'
|
import { Suspense, Transition, computed, defineComponent, h, nextTick, onMounted, provide, reactive, ref } from 'vue'
|
||||||
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
||||||
import { RouterView } from '#vue-router'
|
import { RouterView } from '#vue-router'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
@ -34,8 +34,12 @@ export default defineComponent({
|
|||||||
default: null
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup (props, { attrs }) {
|
setup (props, { attrs, expose }) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
const pageRef = ref()
|
||||||
|
|
||||||
|
expose({ pageRef })
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
||||||
default: (routeProps: RouterViewSlotProps) => {
|
default: (routeProps: RouterViewSlotProps) => {
|
||||||
@ -57,7 +61,7 @@ export default defineComponent({
|
|||||||
suspensible: true,
|
suspensible: true,
|
||||||
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
||||||
onResolve: () => { nextTick(() => nuxtApp.callHook('page:finish', routeProps.Component).finally(done)) }
|
onResolve: () => { nextTick(() => nuxtApp.callHook('page:finish', routeProps.Component).finally(done)) }
|
||||||
}, { default: () => h(RouteProvider, { key, routeProps, pageKey: key, hasTransition } as {}) })
|
}, { default: () => h(RouteProvider, { key, routeProps, pageKey: key, hasTransition, pageRef } as {}) })
|
||||||
)).default()
|
)).default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -81,7 +85,7 @@ const RouteProvider = defineComponent({
|
|||||||
name: 'RouteProvider',
|
name: 'RouteProvider',
|
||||||
// TODO: Type props
|
// TODO: Type props
|
||||||
// eslint-disable-next-line vue/require-prop-types
|
// eslint-disable-next-line vue/require-prop-types
|
||||||
props: ['routeProps', 'pageKey', 'hasTransition'],
|
props: ['routeProps', 'pageKey', 'hasTransition', 'pageRef'],
|
||||||
setup (props) {
|
setup (props) {
|
||||||
// Prevent reactivity when the page will be rerendered in a different suspense fork
|
// Prevent reactivity when the page will be rerendered in a different suspense fork
|
||||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||||
@ -111,11 +115,11 @@ const RouteProvider = defineComponent({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (process.dev && process.client) {
|
if (process.dev && process.client) {
|
||||||
vnode = h(props.routeProps.Component)
|
vnode = h(props.routeProps.Component, { ref: props.pageRef })
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(props.routeProps.Component)
|
return h(props.routeProps.Component, { ref: props.pageRef })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -326,6 +326,27 @@ describe('pages', () => {
|
|||||||
await expectNoClientErrors('/client-only-explicit-import')
|
await expectNoClientErrors('/client-only-explicit-import')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('/wrapper-expose/page', async () => {
|
||||||
|
await expectNoClientErrors('/wrapper-expose/page')
|
||||||
|
let lastLog: string|undefined
|
||||||
|
const page = await createPage('/wrapper-expose/page')
|
||||||
|
page.on('console', (log) => {
|
||||||
|
lastLog = log.text()
|
||||||
|
})
|
||||||
|
page.on('pageerror', (log) => {
|
||||||
|
lastLog = log.message
|
||||||
|
})
|
||||||
|
await page.waitForLoadState('networkidle')
|
||||||
|
await page.locator('#log-foo').click()
|
||||||
|
expect(lastLog === 'bar').toBeTruthy()
|
||||||
|
// change page
|
||||||
|
await page.locator('#to-hello').click()
|
||||||
|
await page.locator('#log-foo').click()
|
||||||
|
expect(lastLog?.includes('.foo is not a function')).toBeTruthy()
|
||||||
|
await page.locator('#log-hello').click()
|
||||||
|
expect(lastLog === 'world').toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('client-fallback', async () => {
|
it('client-fallback', async () => {
|
||||||
const classes = [
|
const classes = [
|
||||||
'clientfallback-non-stateful-setup',
|
'clientfallback-non-stateful-setup',
|
||||||
|
@ -35,7 +35,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
|
|
||||||
it('default server bundle size', async () => {
|
it('default server bundle size', async () => {
|
||||||
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||||
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.9k"')
|
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.8k"')
|
||||||
|
|
||||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||||
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2286k"')
|
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2286k"')
|
||||||
|
24
test/fixtures/basic/pages/wrapper-expose/page.vue
vendored
Normal file
24
test/fixtures/basic/pages/wrapper-expose/page.vue
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtPage ref="page" />
|
||||||
|
|
||||||
|
<button id="log-hello" @click="logHello">
|
||||||
|
hello
|
||||||
|
</button>
|
||||||
|
<button id="log-foo" @click="logFoo">
|
||||||
|
foo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const page = ref()
|
||||||
|
|
||||||
|
function logFoo () {
|
||||||
|
page.value.pageRef.foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
function logHello () {
|
||||||
|
page.value.pageRef.hello()
|
||||||
|
}
|
||||||
|
</script>
|
21
test/fixtures/basic/pages/wrapper-expose/page/another.vue
vendored
Normal file
21
test/fixtures/basic/pages/wrapper-expose/page/another.vue
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
hello
|
||||||
|
</p>
|
||||||
|
<NuxtLink id="to-foo" to="/wrapper-expose/page">
|
||||||
|
to foo
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
function hello () {
|
||||||
|
console.log('world')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
hello
|
||||||
|
})
|
||||||
|
</script>
|
19
test/fixtures/basic/pages/wrapper-expose/page/index.vue
vendored
Normal file
19
test/fixtures/basic/pages/wrapper-expose/page/index.vue
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>foo</p>
|
||||||
|
<NuxtLink id="to-hello" to="/wrapper-expose/page/another">
|
||||||
|
to hello
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
function foo () {
|
||||||
|
console.log('bar')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
foo
|
||||||
|
})
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user