跳转到内容

App 与页面

VPT 项目中有两类入口:

  • App 是整个应用的外层组件,只创建一次,页面跳转时会保留状态;
  • 页面对应一个路由,例如首页、详情页和设置页。

不同于原版 Taro,VPT 的 App 是标准 React 组件,可以渲染任意内容。

在 App 中自由组合视图,并在需要的位置渲染 children

src/app.tsx
import type { PropsWithChildren } from 'react'
import { Text, View } from 'virtual:taro/components'
import './app.css'
function App({ children }: PropsWithChildren) {
return (
<View className="app-shell">
<Text className="app-title">我的应用</Text>
{children}
<Text className="app-footer">示例页脚</Text>
</View>
)
}
export default App

App 可以只返回 children,也可以在它外面添加背景、标题、页脚、共享状态或其他公共组件。无论采用哪种结构,都必须恰好渲染一次 children

此功能没有性能损失,请按照标准 React 组件的方式编写即可。具体实现原理参见 App 视图原理

app.tsx 应只负责组合公共布局和共享状态。把较大的功能提取成独立组件,再在 App 中调用:

src/app.tsx
import type { PropsWithChildren } from 'react'
import { AppShell } from './components/app-shell.tsx'
function App({ children }: PropsWithChildren) {
return <AppShell>{children}</AppShell>
}
export default App

仅拆分文件可以让代码更清晰,但静态导入的组件及其依赖仍需随 App 启动。对于大型且不必立即显示的功能, 可以在提取出的组件中继续使用 React.lazy()

src/components/app-shell.tsx
import { lazy, Suspense, useState } from 'react'
import type { PropsWithChildren } from 'react'
import { Button, View } from 'virtual:taro/components'
const AppTools = lazy(() => import('./app-tools.tsx'))
export function AppShell({ children }: PropsWithChildren) {
const [toolsOpen, setToolsOpen] = useState(false)
return (
<View>
{children}
<Button onClick={() => setToolsOpen(true)}>打开工具</Button>
{toolsOpen && (
<Suspense fallback={<View>加载中…</View>}>
<AppTools />
</Suspense>
)}
</View>
)
}

工具组件及其依赖会等到首次打开时再加载。VPT 会自动规划这些动态模块的分包位置。这样可以减少启动时必须加载和执行的代码。完整规则参见全自动分包

App 中可以使用任意组件,包括原生组件

登录信息、主题和跨页面操作可以放在 App 的 Context 中,也可以使用 Jotai、Zustand 等状态管理库。可以把 Context 和 Provider 一起放在独立文件中,让 App 只负责组合它们。

src/app.tsx
import type { PropsWithChildren } from 'react'
import { AppCountProvider } from './app-count-context.tsx'
function App({ children }: PropsWithChildren) {
return <AppCountProvider>{children}</AppCountProvider>
}
export default App
src/app-count-context.tsx
import { createContext, useMemo, useState } from 'react'
import type { PropsWithChildren } from 'react'
export const AppCountContext = createContext({
count: 0,
increment: () => undefined
})
export function AppCountProvider({ children }: PropsWithChildren) {
const [count, setCount] = useState(0)
const contextValue = useMemo(
() => ({
count: count,
increment: () => setCount((currentCount) => currentCount + 1)
}),
[count]
)
return (
<AppCountContext.Provider value={contextValue}>
{children}
</AppCountContext.Provider>
)
}

页面可以读取和更新同一个值:

src/pages/profile/index.tsx
import { useContext } from 'react'
import { Button, Text, View } from 'virtual:taro/components'
import { AppCountContext } from '../../app-count-context.tsx'
function ProfilePage() {
const appCount = useContext(AppCountContext)
return (
<View>
<Text>{`全局计数:${appCount.count}`}</Text>
<Button onClick={appCount.increment}>增加全局计数</Button>
</View>
)
}
export default ProfilePage

在 App 中使用应用生命周期:

src/app.tsx
import type { PropsWithChildren } from 'react'
import { useDidHide, useDidShow, useLaunch } from 'virtual:taro/api'
function App({ children }: PropsWithChildren) {
useLaunch(() => {
console.log('应用启动')
})
useDidShow(() => {
console.log('应用进入前台')
})
useDidHide(() => {
console.log('应用进入后台')
})
return children
}

页面跳转不会再次调用 useLaunch()。应用进入后台再回到前台时,会调用对应的 useDidHide()useDidShow()

在页面组件中使用页面生命周期:

src/pages/detail/index.tsx
import { useDidHide, useDidShow, useLoad, useUnload } from 'virtual:taro/api'
import { View } from 'virtual:taro/components'
function DetailPage() {
useLoad((options) => {
console.log('页面加载', options)
})
useDidShow(() => {
console.log('页面显示')
})
useDidHide(() => {
console.log('页面隐藏')
})
useUnload(() => {
console.log('页面卸载')
})
return <View>详情页</View>
}

常见跳转的行为如下:

操作原页面新页面
navigateTo()隐藏并保留状态加载并显示
navigateBack()下层页面恢复显示顶层页面卸载
redirectTo()当前页面卸载新页面加载并显示
reLaunch()原页面栈卸载新页面加载并显示

页面隐藏不等于 React 组件卸载。通过 navigateTo() 打开新页面后,旧页面的 React 状态仍然存在, useEffect() 的清理函数也不会仅因为页面隐藏而执行。

需要在隐藏时暂停的定时器、订阅或动画,应使用 useDidHide();页面再次显示时,再通过 useDidShow() 恢复。

使用 Taro 路由 API 打开页面:

import Taro from 'virtual:taro/api'
Taro.navigateTo({
url: '/pages/order/detail?id=42'
})

在目标页面中读取参数:

src/pages/order/detail.tsx
import { useLoad } from 'virtual:taro/api'
import { View } from 'virtual:taro/components'
function OrderDetailPage() {
useLoad((options) => {
console.log('订单 ID', options.id)
})
return <View>订单详情</View>
}

目标页面必须已经在 vpt({ pages }) 中声明。参见配置选项