升級指南
升級 Nuxt
最新版本
若要將 Nuxt 升級到最新版本,請使用 nuxi upgrade
命令。
npx nuxi upgrade
每夜發布管道
若要使用最新的 Nuxt 建置版本並在功能發布前進行測試,請閱讀每夜發布管道指南。
latest
標籤目前追蹤 Nuxt v4 分支,表示現在特別容易出現重大變更 - 請務必小心!您可以使用 "nuxt": "npm:nuxt-nightly@3x"
選擇加入 3.x 分支的每夜發布版本。測試 Nuxt 4
Nuxt 4 的發布日期待定。這取決於 Nitro 主要版本發布後是否有足夠的時間在社群中進行充分測試。您可以在此 PR 中追蹤 Nitro 發布的進度。
在發布之前,可以從 Nuxt 3.12+ 版本開始測試 Nuxt 4 的許多重大變更。
選擇加入 Nuxt 4
首先,將 Nuxt 升級到最新版本。
然後,您可以設定您的 compatibilityVersion
以符合 Nuxt 4 的行為
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
},
// To re-enable _all_ Nuxt v3 behavior, set the following options:
// srcDir: '.',
// dir: {
// app: 'app'
// },
// experimental: {
// scanPageMeta: 'after-resolve',
// sharedPrerenderData: false,
// compileTemplate: true,
// resetAsyncDataToUndefined: true,
// templateUtils: true,
// relativeWatchPaths: true,
// normalizeComponentNames: false,
// spaLoadingTemplateLocation: 'within',
// defaults: {
// useAsyncData: {
// deep: true
// }
// }
// },
// features: {
// inlineStyles: true
// },
// unhead: {
// renderSSRHeadOptions: {
// omitLineBreaks: false
// }
// }
})
當您將 compatibilityVersion
設定為 4
時,整個 Nuxt 設定中的預設值將變更為選擇加入 Nuxt v4 行為,但您可以在測試時細緻地重新啟用 Nuxt v3 行為,請依照上面註解掉的行。如果這樣做,請提交問題,以便我們可以在 Nuxt 或生態系統中解決這些問題。
遷移到 Nuxt 4
重大或顯著的變更將在此處註明,並提供向後/向前相容性的遷移步驟。
compatibilityVersion: 4
測試 Nuxt 4,請定期查看此處。使用 Codemods 遷移
為了方便升級過程,我們與 Codemod 團隊合作,透過一些開放原始碼 codemod 自動化許多遷移步驟。
npx codemod feedback
向 Codemod 團隊回報 🙏如需 Nuxt 4 codemod 的完整列表、每個 codemod 的詳細資訊、來源以及各種執行方式,請造訪 Codemod Registry。
您可以使用以下 codemod
配方執行本指南中提及的所有 codemod
npx codemod@latest nuxt/4/migration-recipe
此命令將依序執行所有 codemod,您可以選擇取消選取任何您不想執行的 codemod。每個 codemod 也會在下面與其各自的變更一起列出,並且可以獨立執行。
新目錄結構
🚦 影響程度:顯著
Nuxt 現在預設為新的目錄結構,具有向後相容性(因此,如果 Nuxt 偵測到您正在使用舊結構,例如使用頂層 pages/
目錄,則此新結構將不適用)。
👉 查看完整 RFC
變動內容
- 新的 Nuxt 預設
srcDir
預設為app/
,並且大多數內容都從該處解析。 serverDir
現在預設為<rootDir>/server
而不是<srcDir>/server
layers/
、modules/
和public/
預設會相對於<rootDir>
解析- 如果使用 Nuxt Content v2.13+,
content/
會相對於<rootDir>
解析 - 新增了
dir.app
,這是我們尋找router.options.ts
和spa-loading-template.html
的目錄 - 這預設為<srcDir>/
v4 資料夾結構範例。
.output/
.nuxt/
app/
assets/
components/
composables/
layouts/
middleware/
pages/
plugins/
utils/
app.config.ts
app.vue
router.options.ts
content/
layers/
modules/
node_modules/
public/
server/
api/
middleware/
plugins/
routes/
utils/
nuxt.config.ts
👉 如需更多詳細資訊,請參閱實作此變更的 PR。
變更原因
- 效能 - 將所有程式碼放在存放庫的根目錄中,會導致
.git/
和node_modules/
資料夾被 FS 監看程式掃描/包含的問題,這可能會顯著延遲非 Mac OS 上的啟動。 - IDE 型別安全 -
server/
和應用程式的其餘部分在兩個完全不同的環境中執行,可用的全域匯入不同,確保server/
不是在與應用程式其餘部分相同的資料夾中,是確保您在 IDE 中獲得良好自動完成功能的一大步。
遷移步驟
- 建立一個名為
app/
的新目錄。 - 將您的
assets/
、components/
、composables/
、layouts/
、middleware/
、pages/
、plugins/
和utils/
資料夾以及app.vue
、error.vue
、app.config.ts
移動到該目錄下。如果您有app/router-options.ts
或app/spa-loading-template.html
,則這些路徑保持不變。 - 確保您的
nuxt.config.ts
、content/
、layers/
、modules/
、public/
和server/
資料夾保留在app/
資料夾之外,位於專案的根目錄中。 - 請記住更新任何第三方設定檔,以使用新的目錄結構,例如您的
tailwindcss
或eslint
設定(如果需要 -@nuxtjs/tailwindcss
應自動正確設定tailwindcss
)。
npx codemod@latest nuxt/4/file-structure
自動化此遷移但是,並非必須進行遷移。如果您希望保留目前的資料夾結構,Nuxt 應該會自動偵測到。(如果沒有偵測到,請提出問題。)唯一的例外是如果您已經有自訂的 srcDir
。在這種情況下,您應該注意,您的 modules/
、public/
和 server/
資料夾將從您的 rootDir
而不是從您的自訂 srcDir
解析。如果需要,您可以透過設定 dir.modules
、dir.public
和 serverDir
來覆寫此設定。
您也可以使用以下設定強制使用 v3 資料夾結構
export default defineNuxtConfig({
// This reverts the new srcDir default from `app` back to your root directory
srcDir: '.',
// This specifies the directory prefix for `app/router.options.ts` and `app/spa-loading-template.html`
dir: {
app: 'app'
}
})
路由元數據去重
🚦 影響程度:極小
變動內容
可以使用 definePageMeta
設定一些路由元數據,例如 name
、path
等。先前,這些在路由和路由元數據上都可用(例如,route.name
和 route.meta.name
)。
現在,它們只能在路由物件上存取。
變更原因
這是預設啟用 experimental.scanPageMeta
的結果,並且是一種效能最佳化。
遷移步驟
遷移應該很簡單
const route = useRoute()
- console.log(route.meta.name)
+ console.log(route.name)
正規化元件名稱
🚦 影響程度:中等
Vue 現在將產生符合 Nuxt 元件命名模式的元件名稱。
變動內容
預設情況下,如果您沒有手動設定,Vue 將指派一個符合元件檔案名稱的元件名稱。
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
在這種情況下,就 Vue 而言,元件名稱將為 MyComponent
。如果您想將 <KeepAlive>
與其搭配使用,或在 Vue DevTools 中識別它,則需要使用此名稱。
但是為了自動匯入它,您需要使用 SomeFolderMyComponent
。
透過此變更,這兩個值將會相符,並且 Vue 將產生一個符合 Nuxt 元件命名模式的元件名稱。
遷移步驟
請確保在任何使用 @vue/test-utils
中的 findComponent
的測試以及任何依賴元件名稱的 <KeepAlive>
中使用更新後的名稱。
或者,目前您可以透過以下方式停用此行為
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: false
}
})
Unhead v2
🚦 影響程度:極小
變動內容
用於產生 <head>
標籤的 Unhead 已更新至版本 2。雖然大部分相容,但它包含幾個針對較低層級 API 的重大變更。
- 已移除的屬性:
vmid
、hid
、children
、body
。 - 不再支援 Promise 輸入。
- 標籤現在預設使用 Capo.js 排序。
遷移步驟
上述變更應該對您的應用程式影響極小。
如果您遇到問題,您應該驗證
- 您沒有使用任何已移除的屬性。
useHead({
meta: [{
name: 'description',
// meta tags don't need a vmid, or a key
- vmid: 'description'
- hid: 'description'
}]
})
import { TemplateParamsPlugin, AliasSortingPlugin } from '@unhead/vue/plugins'
export default defineNuxtPlugin({
setup() {
const unhead = injectHead()
unhead.use(TemplateParamsPlugin)
unhead.use(AliasSortingPlugin)
}
})
雖然不是必需的,但建議將任何從 @unhead/vue
的匯入更新為 #imports
或 nuxt/app
。
-import { useHead } from '@unhead/vue'
+import { useHead } from '#imports'
如果您仍然遇到問題,您可以透過啟用 head.legacy
設定還原為 v1 行為。
export default defineNuxtConfig({
unhead: {
legacy: true,
}
})
SPA 載入畫面的新 DOM 位置
🚦 影響程度:極小
變動內容
當渲染僅限用戶端的頁面(使用 ssr: false
)時,我們會選擇性地在 Nuxt 應用程式根目錄中渲染載入畫面(來自 app/spa-loading-template.html
)
<div id="__nuxt">
<!-- spa loading template -->
</div>
現在,我們預設為在 Nuxt 應用程式根目錄旁邊渲染範本
<div id="__nuxt"></div>
<!-- spa loading template -->
變更原因
這允許 SPA 載入範本保留在 DOM 中,直到 Vue 應用程式暫停解析完成,從而防止出現白屏閃爍。
遷移步驟
如果您使用 CSS 或 document.queryElement
定位 SPA 載入範本,您將需要更新您的選取器。為此,您可以使用新的 app.spaLoaderTag
和 app.spaLoaderAttrs
設定選項。
或者,您可以透過以下方式還原為先前的行為
export default defineNuxtConfig({
experimental: {
spaLoadingTemplateLocation: 'within',
}
})
更細緻的內聯樣式
🚦 影響程度:中等
Nuxt 現在只會內聯 Vue 元件的樣式,而不是全域 CSS。
變動內容
先前,Nuxt 會內聯所有 CSS,包括全域樣式,並移除 <link>
元素以分隔 CSS 檔案。現在,Nuxt 只會對 Vue 元件執行此操作(先前會產生個別的 CSS 區塊)。我們認為這更好地平衡了減少個別網路請求(就像以前一樣,初始載入時不會有每個頁面或每個元件的個別 .css
檔案請求),以及允許快取單一全域 CSS 檔案並減少初始請求的文件下載大小。
遷移步驟
此功能是完全可設定的,您可以透過將 inlineStyles: true
設定為內聯全域 CSS 以及每個元件的 CSS 來還原為先前的行為。
export default defineNuxtConfig({
features: {
inlineStyles: true
}
})
解析後掃描頁面元數據
🚦 影響程度:極小
變動內容
我們現在在呼叫 pages:extend
鉤子之後而不是之前掃描頁面元數據(在 definePageMeta
中定義)。
變更原因
這是為了允許掃描使用者想要在 pages:extend
中新增的頁面的元數據。我們仍然提供一個在新的 pages:resolved
鉤子中變更或覆寫頁面元數據的機會。
遷移步驟
如果您想要覆寫頁面元數據,請在 pages:resolved
而不是 pages:extend
中執行此操作。
export default defineNuxtConfig({
hooks: {
- 'pages:extend'(pages) {
+ 'pages:resolved'(pages) {
const myPage = pages.find(page => page.path === '/')
myPage.meta ||= {}
myPage.meta.layout = 'overridden-layout'
}
}
})
或者,您可以透過以下方式還原為先前的行為
export default defineNuxtConfig({
experimental: {
scanPageMeta: true
}
})
共享預先渲染資料
🚦 影響程度:中等
變動內容
我們啟用了一個先前實驗性的功能,可以在不同頁面之間共享來自 useAsyncData
和 useFetch
呼叫的資料。請參閱原始 PR。
變更原因
此功能會自動在預先渲染的頁面之間共享酬載資料。當預先渲染使用 useAsyncData
或 useFetch
且在不同頁面中提取相同資料的網站時,這可以顯著提高效能。
例如,如果您的網站需要每個頁面的 useFetch
呼叫(例如,取得選單的導覽資料,或來自 CMS 的網站設定),則此資料只會在預先渲染第一個使用它的頁面時提取一次,然後快取以供預先渲染其他頁面時使用。
遷移步驟
請確保您的資料的任何唯一索引鍵始終可以解析為相同的資料。例如,如果您使用 useAsyncData
來提取與特定頁面相關的資料,您應該提供一個唯一符合該資料的索引鍵。(useFetch
應該會自動為您執行此操作。)
// This would be unsafe in a dynamic page (e.g. `[slug].vue`) because the route slug makes a difference
// to the data fetched, but Nuxt can't know that because it's not reflected in the key.
const route = useRoute()
const { data } = await useAsyncData(async () => {
return await $fetch(`/api/my-page/${route.params.slug}`)
})
// Instead, you should use a key that uniquely identifies the data fetched.
const { data } = await useAsyncData(route.params.slug, async () => {
return await $fetch(`/api/my-page/${route.params.slug}`)
})
或者,您可以透過以下方式停用此功能
export default defineNuxtConfig({
experimental: {
sharedPrerenderData: false
}
})
`useAsyncData` 和 `useFetch` 中的預設 `data` 和 `error` 值
🚦 影響程度:極小
變動內容
從 useAsyncData
傳回的 data
和 error
物件現在預設為 undefined
。
變更原因
先前,data
初始化為 null
,但在 clearNuxtData
中重設為 undefined
。error
初始化為 null
。此變更是為了提高一致性。
遷移步驟
如果您先前檢查 data.value
或 error.value
是否為 null
,您可以更新這些檢查以檢查 undefined
。
npx codemod@latest nuxt/4/default-data-error-value
自動化此步驟如果您遇到任何問題,您可以透過以下方式還原為先前的行為
export default defineNuxtConfig({
experimental: {
defaults: {
useAsyncData: {
value: 'null',
errorValue: 'null'
}
}
}
})
如果您這樣做,請回報問題,因為我們不打算將其保留為可設定的。
移除在 `useAsyncData` 和 `useFetch` 中呼叫 `refresh` 時,`dedupe` 選項已棄用的 `boolean` 值
🚦 影響程度:極小
變動內容
先前可以將 dedupe: boolean
傳遞給 refresh
。這些是 cancel
(true
) 和 defer
(false
) 的別名。
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
async function refreshData () {
await refresh({ dedupe: true })
}
變更原因
為了更清晰起見,已移除這些別名。
當將 dedupe
新增為 useAsyncData
的選項時,出現了這個問題,並且我們移除了布林值,因為它們最終是相反的。
refresh({ dedupe: false })
表示「不要取消現有的請求以支持這個新的請求」。但是,在 useAsyncData
的選項中傳遞 dedupe: true
表示「如果存在現有的待處理請求,則不要發出任何新的請求。」(請參閱PR。)
遷移步驟
遷移應該很簡單
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
async function refreshData () {
- await refresh({ dedupe: true })
+ await refresh({ dedupe: 'cancel' })
- await refresh({ dedupe: false })
+ await refresh({ dedupe: 'defer' })
}
npx codemod@latest nuxt/4/deprecated-dedupe-value
自動化此步驟在 `useAsyncData` 和 `useFetch` 中清除 `data` 時,尊重預設值
🚦 影響程度:極小
變動內容
如果您為 useAsyncData
提供自訂的 default
值,則在呼叫 clear
或 clearNuxtData
時,現在將使用此值,並且會重設為其預設值,而不是僅取消設定。
變更原因
使用者通常會設定適當的空值,例如空陣列,以避免在迭代時需要檢查 null
/undefined
。在重設/清除資料時,應尊重此設定。
遷移步驟
如果您遇到任何問題,目前可以透過以下方式還原為先前的行為
export default defineNuxtConfig({
experimental: {
resetAsyncDataToUndefined: true,
}
})
如果您這樣做,請回報問題,因為我們不打算將其保留為可設定的。
`useAsyncData` 和 `useFetch` 中的淺層資料響應性
🚦 影響程度:極小
從 useAsyncData
、useFetch
、useLazyAsyncData
和 useLazyFetch
傳回的 data
物件現在是 shallowRef
而不是 ref
。
變動內容
當提取新資料時,任何依賴 data
的內容仍然會是響應式的,因為整個物件都被取代了。但是,如果您的程式碼變更了該資料結構內的屬性,則這將不會觸發應用程式中的任何響應性。
變更原因
這為深度巢狀物件和陣列帶來了顯著的效能提升,因為 Vue 不需要監看每個屬性/陣列的修改。在大多數情況下,data
也應該是不可變的。
遷移步驟
在大多數情況下,不需要遷移步驟,但是如果您依賴資料物件的響應性,則有兩個選項
- 您可以依據每個可組合項選擇加入深度響應性
- const { data } = useFetch('/api/test') + const { data } = useFetch('/api/test', { deep: true })
- 您可以變更專案範圍的預設行為(不建議)nuxt.config.ts
export default
defineNuxtConfig({experimental: {defaults: {useAsyncData: {deep: true } } } })
npx codemod@latest nuxt/4/shallow-function-reactivity
自動化此步驟`builder:watch` 中的絕對監看路徑
🚦 影響程度:極小
變動內容
Nuxt builder:watch
鉤子現在發出一個路徑,該路徑是絕對路徑,而不是相對於您的專案 srcDir
的路徑。
變更原因
這使我們能夠支援監看 srcDir
之外的路徑,並為圖層和其他更複雜的模式提供更好的支援。
遷移步驟
我們已經主動遷移了我們知道使用此鉤子的公共 Nuxt 模組。請參閱問題 #25339。
但是,如果您是使用 builder:watch
鉤子的模組作者,並且希望保持向後/向前相容性,則可以使用以下程式碼來確保您的程式碼在 Nuxt v3 和 Nuxt v4 中都能正常運作
+ import { relative, resolve } from 'node:fs'
// ...
nuxt.hook('builder:watch', async (event, path) => {
+ path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
// ...
})
npx codemod@latest nuxt/4/absolute-watch-path
自動化此步驟移除 window.__NUXT__
物件
變動內容
我們正在應用程式完成 hydration 後移除全域 window.__NUXT__
物件。
變更原因
這為多應用程式模式 (#21635) 開啟了道路,並使我們能夠專注於存取 Nuxt 應用程式資料的單一方式 - useNuxtApp()
。
遷移步驟
資料仍然可用,但可以使用 useNuxtApp().payload
存取
- console.log(window.__NUXT__)
+ console.log(useNuxtApp().payload)
目錄索引掃描
🚦 影響程度:中等
變動內容
也會掃描 middleware/
資料夾中的子資料夾以尋找 index
檔案,並且這些檔案現在也會在您的專案中註冊為 middleware。
變更原因
Nuxt 會自動掃描許多資料夾,包括 middleware/
和 plugins/
。
掃描 plugins/
資料夾中的子資料夾以尋找 index
檔案,我們希望使掃描目錄之間的行為保持一致。
遷移步驟
可能不需要遷移,但如果您希望還原為先前的行為,您可以新增一個鉤子來篩選掉這些 middleware
export default defineNuxtConfig({
hooks: {
'app:resolve'(app) {
app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
}
}
})
範本編譯變更
🚦 影響程度:極小
變動內容
先前,Nuxt 使用 lodash/template
來編譯位於檔案系統上的範本,並使用 .ejs
檔案格式/語法。
此外,我們提供了一些範本公用程式 (serialize
、importName
、importSources
),可用於在這些範本中進行程式碼產生,這些公用程式現在已被移除。
變更原因
在 Nuxt v3 中,我們改用具有 getContents()
函數的「虛擬」語法,這種語法更加靈活且效能更高。
此外,lodash/template
曾發生過一連串的安全問題。這些問題實際上並不適用於 Nuxt 專案,因為它是在建置時而非執行時使用,並且是由受信任的程式碼使用。然而,它們仍然會出現在安全稽核中。此外,lodash
是一個相當龐大的依賴項,且大多數專案都不會使用到它。
最後,直接在 Nuxt 內部提供程式碼序列化函式並非理想的做法。相反地,我們維護像 unjs/knitwork 這樣的專案,它可以作為您專案的依賴項,並且安全問題可以直接回報/解決,而無需升級 Nuxt 本身。
遷移步驟
我們已提交 PR 以更新使用 EJS 語法的模組,但如果您需要自行執行此操作,您有三種向下/向上相容的替代方案
- 將您的字串插值邏輯直接移至
getContents()
中。 - 使用自訂函式來處理替換,例如在 https://github.com/nuxt-modules/color-mode/pull/240 中。
- 使用
es-toolkit/compat
(lodash template 的直接替換),作為您專案的依賴項,而不是 Nuxt 的依賴項
+ import { readFileSync } from 'node:fs'
+ import { template } from 'es-toolkit/compat'
// ...
addTemplate({
fileName: 'appinsights-vue.js'
options: { /* some options */ },
- src: resolver.resolve('./runtime/plugin.ejs'),
+ getContents({ options }) {
+ const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
+ return template(contents)({ options })
+ },
})
最後,如果您正在使用模板工具 (serialize
、importName
、importSources
),您可以按照以下方式使用 knitwork
中的工具來替換它們
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"{(.+)}"(?=,?$)/gm, r => JSON.parse(r).replace(/^{(.*)}$/, '$1'))
const importSources = (sources: string | string[], { lazy = false } = {}) => {
return toArray(sources).map((src) => {
if (lazy) {
return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
}
return genImport(src, genSafeVariableName(src))
}).join('\n')
}
const importName = genSafeVariableName
npx codemod@latest nuxt/4/template-compilation-changes
自動執行此步驟移除實驗性功能
🚦 影響程度:極小
變動內容
在 Nuxt 4 中,有四個實驗性功能不再可配置
experimental.treeshakeClientOnly
將會是true
(自 v3.0 以來的預設值)experimental.configSchema
將會是true
(自 v3.3 以來的預設值)experimental.polyfillVueUseHead
將會是false
(自 v3.4 以來的預設值)experimental.respectNoSSRHeader
將會是false
(自 v3.4 以來的預設值)vite.devBundler
不再可配置 - 預設將使用vite-node
變更原因
這些選項已設定為目前的數值一段時間,我們沒有理由相信它們需要保持可配置。
遷移步驟
Nuxt 2 與 Nuxt 3+
在下表中,快速比較了 3 個版本的 Nuxt
功能 / 版本 | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
---|---|---|---|
Vue | 2 | 2 | 3 |
穩定性 | 😊 穩定 | 😊 穩定 | 😊 穩定 |
效能 | 🏎 快速 | ✈️ 更快 | 🚀 最快 |
Nitro 引擎 | ❌ | ✅ | ✅ |
ESM 支援 | 🌙 部分 | 👍 更好 | ✅ |
TypeScript | ☑️ 選擇性加入 | 🚧 部分 | ✅ |
Composition API | ❌ | 🚧 部分 | ✅ |
Options API | ✅ | ✅ | ✅ |
元件自動匯入 | ✅ | ✅ | ✅ |
<script setup> 語法 | ❌ | 🚧 部分 | ✅ |
自動匯入 | ❌ | ✅ | ✅ |
webpack | 4 | 4 | 5 |
Vite | ⚠️ 部分 | 🚧 部分 | ✅ |
Nuxi CLI | ❌ 舊版 | ✅ nuxi | ✅ nuxi |
靜態網站 | ✅ | ✅ | ✅ |
Nuxt 2 到 Nuxt 3+
遷移指南提供了 Nuxt 2 功能與 Nuxt 3+ 功能的逐步比較,以及調整目前應用程式的指引。
Nuxt 2 到 Nuxt Bridge
如果您偏好逐步將 Nuxt 2 應用程式遷移到 Nuxt 3,可以使用 Nuxt Bridge。Nuxt Bridge 是一個相容性層,讓您可以在 Nuxt 2 中使用 Nuxt 3+ 功能,並具有選擇性加入機制。