mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +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 Vue from 'vue'
|
||||||
import { createHooks } from 'hookable/dist/index.mjs'
|
import { createHooks } from 'hookable/dist/index.mjs'
|
||||||
import { setNuxtInstance } from '#app'
|
import { setNuxtAppInstance } from '#app'
|
||||||
|
|
||||||
export default (ctx, inject) => {
|
export default (ctx, inject) => {
|
||||||
const nuxt = {
|
const nuxt = {
|
||||||
@ -42,7 +42,7 @@ export default (ctx, inject) => {
|
|||||||
nuxt.legacyApp = this
|
nuxt.legacyApp = this
|
||||||
})
|
})
|
||||||
|
|
||||||
setNuxtInstance(nuxt)
|
setNuxtAppInstance(nuxt)
|
||||||
|
|
||||||
inject('_nuxtApp', nuxt)
|
inject('_nuxtApp', nuxt)
|
||||||
}
|
}
|
||||||
|
@ -33,26 +33,26 @@ export interface Context {
|
|||||||
$_nuxtApp: NuxtAppCompat
|
$_nuxtApp: NuxtAppCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentNuxtInstance: NuxtAppCompat | null
|
let currentNuxtAppInstance: NuxtAppCompat | null
|
||||||
|
|
||||||
export const setNuxtInstance = (nuxt: NuxtAppCompat | null) => {
|
export const setNuxtAppInstance = (nuxt: NuxtAppCompat | null) => {
|
||||||
currentNuxtInstance = nuxt
|
currentNuxtAppInstance = nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defineNuxtPlugin = plugin => (ctx: Context) => {
|
export const defineNuxtPlugin = plugin => (ctx: Context) => {
|
||||||
setNuxtInstance(ctx.$_nuxtApp)
|
setNuxtAppInstance(ctx.$_nuxtApp)
|
||||||
plugin(ctx.$_nuxtApp)
|
plugin(ctx.$_nuxtApp)
|
||||||
setNuxtInstance(null)
|
setNuxtAppInstance(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNuxtApp = () => {
|
export const useNuxtApp = () => {
|
||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
if (!currentNuxtInstance) {
|
if (!currentNuxtAppInstance) {
|
||||||
throw new Error('nuxt instance unavailable')
|
throw new Error('nuxt app instance unavailable')
|
||||||
}
|
}
|
||||||
return currentNuxtInstance
|
return currentNuxtAppInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
return vm?.proxy.$_nuxtApp
|
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 VueRouter from 'vue-router'
|
||||||
import type { Route } from 'vue-router'
|
import type { Route } from 'vue-router'
|
||||||
import { useNuxtApp } from './app'
|
import { useNuxtApp } from './app'
|
||||||
@ -39,3 +39,15 @@ export const useRoute = () => {
|
|||||||
|
|
||||||
return nuxt._route as Route
|
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 { defineNuxtComponent } from './component'
|
||||||
export { useAsyncData } from './asyncData'
|
export { useAsyncData } from './asyncData'
|
||||||
export { useHydration } from './hydrate'
|
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: {
|
payload: {
|
||||||
serverRendered?: true
|
serverRendered?: true
|
||||||
data?: Record<string, any>
|
data?: Record<string, any>
|
||||||
|
state?: Record<string, any>
|
||||||
rendered?: Function
|
rendered?: Function
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
@ -70,7 +71,11 @@ export function createNuxtApp (options: CreateOptions) {
|
|||||||
const nuxt: NuxtApp = {
|
const nuxt: NuxtApp = {
|
||||||
provide: undefined,
|
provide: undefined,
|
||||||
globalName: 'nuxt',
|
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,
|
isHydrating: process.client,
|
||||||
_asyncDataPromises: {},
|
_asyncDataPromises: {},
|
||||||
...options
|
...options
|
||||||
@ -150,10 +155,10 @@ export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin {
|
|||||||
return !plugin[NuxtPluginIndicator]
|
return !plugin[NuxtPluginIndicator]
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentNuxtInstance: NuxtApp | null
|
let currentNuxtAppInstance: NuxtApp | null
|
||||||
|
|
||||||
export const setNuxtInstance = (nuxt: NuxtApp | null) => {
|
export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
|
||||||
currentNuxtInstance = nuxt
|
currentNuxtAppInstance = nuxt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,9 +168,9 @@ export const setNuxtInstance = (nuxt: NuxtApp | null) => {
|
|||||||
* @param setup The function to call
|
* @param setup The function to call
|
||||||
*/
|
*/
|
||||||
export async function callWithNuxt (nuxt: NuxtApp, setup: () => any) {
|
export async function callWithNuxt (nuxt: NuxtApp, setup: () => any) {
|
||||||
setNuxtInstance(nuxt)
|
setNuxtAppInstance(nuxt)
|
||||||
const p = setup()
|
const p = setup()
|
||||||
setNuxtInstance(null)
|
setNuxtAppInstance(null)
|
||||||
await p
|
await p
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,10 +181,10 @@ export function useNuxtApp (): NuxtApp {
|
|||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
if (!currentNuxtInstance) {
|
if (!currentNuxtAppInstance) {
|
||||||
throw new Error('nuxt instance unavailable')
|
throw new Error('nuxt instance unavailable')
|
||||||
}
|
}
|
||||||
return currentNuxtInstance
|
return currentNuxtAppInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
return vm.appContext.app.$nuxt
|
return vm.appContext.app.$nuxt
|
||||||
|
@ -4,7 +4,8 @@ const identifiers = {
|
|||||||
'defineNuxtComponent',
|
'defineNuxtComponent',
|
||||||
'useNuxtApp',
|
'useNuxtApp',
|
||||||
'defineNuxtPlugin',
|
'defineNuxtPlugin',
|
||||||
'useRuntimeConfig'
|
'useRuntimeConfig',
|
||||||
|
'useState'
|
||||||
],
|
],
|
||||||
'#meta': [
|
'#meta': [
|
||||||
'useMeta'
|
'useMeta'
|
||||||
|
4
test/fixtures/bridge/layouts/default.vue
vendored
4
test/fixtures/bridge/layouts/default.vue
vendored
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Nuxt />
|
<Nuxt />
|
||||||
{{ route.path }}
|
<hr>
|
||||||
|
Route: {{ route.path }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -9,7 +10,6 @@
|
|||||||
export default {
|
export default {
|
||||||
setup () {
|
setup () {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
console.log(route.path)
|
|
||||||
return { route }
|
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'],
|
plugins: ['~/plugins/setup.js'],
|
||||||
nitro: {
|
nitro: {
|
||||||
output: { dir: process.env.NITRO_OUTPUT_DIR }
|
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>
|
<template>
|
||||||
<div>Hello Vue {{ version }}!</div>
|
<div>
|
||||||
|
<div>Hello Vue {{ version }}!</div>
|
||||||
|
<div>
|
||||||
|
State: {{ state }} <button @click="updateState">
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
useMeta({ meta: [{ name: 'description', content: 'This is a page to demo Nuxt Bridge.' }] })
|
useMeta({ meta: [{ name: 'description', content: 'This is a page to demo Nuxt Bridge.' }] })
|
||||||
const version = ref('2')
|
const version = ref('2')
|
||||||
|
const state = useState('test-state')
|
||||||
|
state.value = '123'
|
||||||
|
const updateState = () => { state.value = '456' }
|
||||||
</script>
|
</script>
|
||||||
|
@ -9230,6 +9230,14 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
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":
|
"example-wasm@workspace:examples/wasm":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "example-wasm@workspace:examples/wasm"
|
resolution: "example-wasm@workspace:examples/wasm"
|
||||||
|
Loading…
Reference in New Issue
Block a user