mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 07:05:11 +00:00
feat: useState
composable (#719)
This commit is contained in:
parent
d8f40155c1
commit
666b7f1ba8
40
docs/content/3.docs/1.usage/2.state.md
Normal file
40
docs/content/3.docs/1.usage/2.state.md
Normal file
@ -0,0 +1,40 @@
|
||||
# State
|
||||
|
||||
Nuxt provides `useState` to create a globally shared state.
|
||||
|
||||
## `useState`
|
||||
|
||||
Within your pages, components and plugins you can use `useState`. It can be used to create your own store implementation.
|
||||
You can think of it as a ssr-friendly ref that it's value will be hydrated (preserved) after Server-Side rendering and it is shared across all components.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
useState(key: string)
|
||||
```
|
||||
|
||||
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests
|
||||
|
||||
### Example
|
||||
|
||||
In this example, we use a server-only plugin to find about request locale.
|
||||
|
||||
```ts [plugins/locale.server.ts]
|
||||
import { defineNuxtPlugin, useState } from '#app'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const locale = useState('locale')
|
||||
locale.value = nuxt.ssrContext.req.headers['accept-language']?.split(',')[0]
|
||||
})
|
||||
```
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const locale = useState('locale')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
Current locale: {{ locale }}
|
||||
</template>
|
||||
```
|
4
examples/use-state/nuxt.config.ts
Normal file
4
examples/use-state/nuxt.config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { defineNuxtConfig } from 'nuxt3'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
})
|
12
examples/use-state/package.json
Normal file
12
examples/use-state/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "example-use-state",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"nuxt3": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
"build": "nuxt build",
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
13
examples/use-state/pages/index.vue
Normal file
13
examples/use-state/pages/index.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
Locale: {{ locale }}
|
||||
<button @click="updateLocale">
|
||||
Set to Klington
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const locale = useState('locale')
|
||||
const updateLocale = () => { locale.value = 'tlh-klingon' }
|
||||
</script>
|
6
examples/use-state/plugins/locale.server.ts
Normal file
6
examples/use-state/plugins/locale.server.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { useState } from '#app'
|
||||
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
const locale = useState('locale')
|
||||
locale.value = nuxt.ssrContext.req.headers['accept-language']?.split(',')[0]
|
||||
})
|
@ -1,6 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import { createHooks } from 'hookable/dist/index.mjs'
|
||||
import { setNuxtInstance } from '#app'
|
||||
import { setNuxtAppInstance } from '#app'
|
||||
|
||||
export default (ctx, inject) => {
|
||||
const nuxt = {
|
||||
@ -42,7 +42,7 @@ export default (ctx, inject) => {
|
||||
nuxt.legacyApp = this
|
||||
})
|
||||
|
||||
setNuxtInstance(nuxt)
|
||||
setNuxtAppInstance(nuxt)
|
||||
|
||||
inject('_nuxtApp', nuxt)
|
||||
}
|
||||
|
@ -33,26 +33,26 @@ export interface Context {
|
||||
$_nuxtApp: NuxtAppCompat
|
||||
}
|
||||
|
||||
let currentNuxtInstance: NuxtAppCompat | null
|
||||
let currentNuxtAppInstance: NuxtAppCompat | null
|
||||
|
||||
export const setNuxtInstance = (nuxt: NuxtAppCompat | null) => {
|
||||
currentNuxtInstance = nuxt
|
||||
export const setNuxtAppInstance = (nuxt: NuxtAppCompat | null) => {
|
||||
currentNuxtAppInstance = nuxt
|
||||
}
|
||||
|
||||
export const defineNuxtPlugin = plugin => (ctx: Context) => {
|
||||
setNuxtInstance(ctx.$_nuxtApp)
|
||||
setNuxtAppInstance(ctx.$_nuxtApp)
|
||||
plugin(ctx.$_nuxtApp)
|
||||
setNuxtInstance(null)
|
||||
setNuxtAppInstance(null)
|
||||
}
|
||||
|
||||
export const useNuxtApp = () => {
|
||||
const vm = getCurrentInstance()
|
||||
|
||||
if (!vm) {
|
||||
if (!currentNuxtInstance) {
|
||||
throw new Error('nuxt instance unavailable')
|
||||
if (!currentNuxtAppInstance) {
|
||||
throw new Error('nuxt app instance unavailable')
|
||||
}
|
||||
return currentNuxtInstance
|
||||
return currentNuxtAppInstance
|
||||
}
|
||||
|
||||
return vm?.proxy.$_nuxtApp
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { reactive } from '@vue/composition-api'
|
||||
import { reactive, toRef, isReactive } from '@vue/composition-api'
|
||||
import type VueRouter from 'vue-router'
|
||||
import type { Route } from 'vue-router'
|
||||
import { useNuxtApp } from './app'
|
||||
@ -39,3 +39,15 @@ export const useRoute = () => {
|
||||
|
||||
return nuxt._route as Route
|
||||
}
|
||||
|
||||
// payload.state is used for vuex by nuxt 2
|
||||
export const useState = (key: string) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (!nuxtApp.payload.useState) {
|
||||
nuxtApp.payload.useState = {}
|
||||
}
|
||||
if (!isReactive(nuxtApp.payload.useState)) {
|
||||
nuxtApp.payload.useState = reactive(nuxtApp.payload.useState)
|
||||
}
|
||||
return toRef(nuxtApp.payload.useState, key)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export { defineNuxtComponent } from './component'
|
||||
export { useAsyncData } from './asyncData'
|
||||
export { useHydration } from './hydrate'
|
||||
export { useState } from './state'
|
||||
|
12
packages/nuxt3/src/app/composables/state.ts
Normal file
12
packages/nuxt3/src/app/composables/state.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { useNuxtApp } from '#app'
|
||||
|
||||
/**
|
||||
* Create a global reactive ref that will be hydrated but not shared across ssr requests
|
||||
*
|
||||
* @param key a unique key to identify the data in the Nuxt payload
|
||||
*/
|
||||
export const useState = <T> (key: string): Ref<T> => {
|
||||
const nuxt = useNuxtApp()
|
||||
return toRef(nuxt.payload.state, key)
|
||||
}
|
@ -44,6 +44,7 @@ export interface NuxtApp {
|
||||
payload: {
|
||||
serverRendered?: true
|
||||
data?: Record<string, any>
|
||||
state?: Record<string, any>
|
||||
rendered?: Function
|
||||
[key: string]: any
|
||||
}
|
||||
@ -70,7 +71,11 @@ export function createNuxtApp (options: CreateOptions) {
|
||||
const nuxt: NuxtApp = {
|
||||
provide: undefined,
|
||||
globalName: 'nuxt',
|
||||
payload: reactive(process.server ? { serverRendered: true, data: {} } : (window.__NUXT__ || { data: {} })),
|
||||
payload: reactive({
|
||||
data: {},
|
||||
state: {},
|
||||
...(process.client ? window.__NUXT__ : { serverRendered: true })
|
||||
}),
|
||||
isHydrating: process.client,
|
||||
_asyncDataPromises: {},
|
||||
...options
|
||||
@ -150,10 +155,10 @@ export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin {
|
||||
return !plugin[NuxtPluginIndicator]
|
||||
}
|
||||
|
||||
let currentNuxtInstance: NuxtApp | null
|
||||
let currentNuxtAppInstance: NuxtApp | null
|
||||
|
||||
export const setNuxtInstance = (nuxt: NuxtApp | null) => {
|
||||
currentNuxtInstance = nuxt
|
||||
export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
|
||||
currentNuxtAppInstance = nuxt
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,9 +168,9 @@ export const setNuxtInstance = (nuxt: NuxtApp | null) => {
|
||||
* @param setup The function to call
|
||||
*/
|
||||
export async function callWithNuxt (nuxt: NuxtApp, setup: () => any) {
|
||||
setNuxtInstance(nuxt)
|
||||
setNuxtAppInstance(nuxt)
|
||||
const p = setup()
|
||||
setNuxtInstance(null)
|
||||
setNuxtAppInstance(null)
|
||||
await p
|
||||
}
|
||||
|
||||
@ -176,10 +181,10 @@ export function useNuxtApp (): NuxtApp {
|
||||
const vm = getCurrentInstance()
|
||||
|
||||
if (!vm) {
|
||||
if (!currentNuxtInstance) {
|
||||
if (!currentNuxtAppInstance) {
|
||||
throw new Error('nuxt instance unavailable')
|
||||
}
|
||||
return currentNuxtInstance
|
||||
return currentNuxtAppInstance
|
||||
}
|
||||
|
||||
return vm.appContext.app.$nuxt
|
||||
|
@ -4,7 +4,8 @@ const identifiers = {
|
||||
'defineNuxtComponent',
|
||||
'useNuxtApp',
|
||||
'defineNuxtPlugin',
|
||||
'useRuntimeConfig'
|
||||
'useRuntimeConfig',
|
||||
'useState'
|
||||
],
|
||||
'#meta': [
|
||||
'useMeta'
|
||||
|
4
test/fixtures/bridge/layouts/default.vue
vendored
4
test/fixtures/bridge/layouts/default.vue
vendored
@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<Nuxt />
|
||||
{{ route.path }}
|
||||
<hr>
|
||||
Route: {{ route.path }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -9,7 +10,6 @@
|
||||
export default {
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
console.log(route.path)
|
||||
return { route }
|
||||
}
|
||||
}
|
||||
|
3
test/fixtures/bridge/nuxt.config.ts
vendored
3
test/fixtures/bridge/nuxt.config.ts
vendored
@ -17,5 +17,8 @@ export default defineNuxtConfig({
|
||||
plugins: ['~/plugins/setup.js'],
|
||||
nitro: {
|
||||
output: { dir: process.env.NITRO_OUTPUT_DIR }
|
||||
},
|
||||
bridge: {
|
||||
meta: true
|
||||
}
|
||||
})
|
||||
|
12
test/fixtures/bridge/pages/index.vue
vendored
12
test/fixtures/bridge/pages/index.vue
vendored
@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<div>Hello Vue {{ version }}!</div>
|
||||
<div>
|
||||
<div>Hello Vue {{ version }}!</div>
|
||||
<div>
|
||||
State: {{ state }} <button @click="updateState">
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
useMeta({ meta: [{ name: 'description', content: 'This is a page to demo Nuxt Bridge.' }] })
|
||||
const version = ref('2')
|
||||
const state = useState('test-state')
|
||||
state.value = '123'
|
||||
const updateState = () => { state.value = '456' }
|
||||
</script>
|
||||
|
@ -9230,6 +9230,14 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-use-state@workspace:examples/use-state":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-use-state@workspace:examples/use-state"
|
||||
dependencies:
|
||||
nuxt3: latest
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-wasm@workspace:examples/wasm":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-wasm@workspace:examples/wasm"
|
||||
|
Loading…
Reference in New Issue
Block a user