Cell - React 组件
Cell 应用的 React 前端:用装饰器声明的页面、React Router 路由、布局和应用壳,都由前端 DI 容器管理。安装后添加 frontend 构建目标(通过 @celljs/widget)。指南:前端与 RPC。
npm install @celljs/react react
npm install -D @types/reactcell init 的 fullstack 模板已配置它。cell dev 用 HMR 在后端端口上为前端提供服务;cell build 将其写到 .cell/output/public/。
页面
页面是 src/browser/pages/ 中的文件,每个都以页面组件为默认导出:index.tsx 是 /、about.tsx 是 /about、users/[id].tsx 是 /users/:id、[...rest].tsx 匹配路径的剩余部分、_layout.tsx 包裹其目录及以下的页面,_error.tsx 在其中之一失败时显示。以 _ 开头的名称和测试不是页面。工具链(cell dev、cell build)生成路由(.cell/generated/frontend/routes.js,由 cell info --json 列出);每个页面是访问时加载的块。指南:前端与 RPC。
// src/browser/pages/users/[id].tsx
import { useParams } from '@celljs/react';
export default function User() {
return <h1>User {useParams().id}</h1>;
}路由使用 React Router 7;Link、NavLink、Navigate、Outlet、useParams、useNavigate、useLocation、useSearchParams 和 useMatch 从这里重新导出。
没有页面目录的包通过装饰器在其 DI 模块导入的类上添加页面和替换前端部分:
| 装饰器 | |
|---|---|
@View(pathOrMetadata) / @Route(...) | 一个路由。字符串是路径;元数据有 path、index、component(默认:装饰的类)、layout、caseSensitive、priority |
@DefaultLayout(component?) | 每个没有自己 layout 的路由周围的布局(默认:无) |
@App(component?) | 替换根组件 |
@Router(component?) | 替换路由 |
@Context(component?) | 在应用周围添加 React 上下文提供者 |
路径相对于 react.path(默认 server.path,/)解析。
错误
渲染时抛出的页面,或代码未加载的页面,不会让应用空白:其布局内的 ErrorBoundary 显示备选方案,当用户导航到另一路径时重试页面。备选方案是最近 _error.tsx 的默认导出(在页面所在目录或以上),它得到 { error, reset }(ErrorFallbackProps);没有时,DefaultErrorFallback 显示消息和重试按钮。壳在整个应用周围放另一边界,用于布局和备选方案本身的错误。React 将错误记录到控制台。
// src/browser/pages/_error.tsx
import type { ErrorFallbackProps } from '@celljs/react';
export default function PageError({ error, reset }: ErrorFallbackProps) {
return <p>无法显示此页面({String(error)})。<button onClick={reset}>重试</button></p>;
}ErrorBoundary(fallback、resetKey)为页面的部分导出。
组件中的服务
| Hook | |
|---|---|
useInject<T>(id) | 容器的一个组件,每个组件实例解析一次 |
useValue<T>(key, default?) | 一个配置值,如 @Value(key)(前端仅有公开键) |
useContainer() | 容器本身 |
useRpc<T>(id) | 后端服务的代理(需要 @celljs/rpc),如 @Autorpc(id) |
useLocale() | [locale, setLocale]:用户选择的 widget.locales 的区域设置,以及如何改变它 |
它们使用 ContainerContext 的容器,壳提供;没有全局容器,所以在应用外渲染的组件失败,消息说如此。在测试中,在 <ContainerContext.Provider value={container}> 内渲染组件;reactTestModule(@celljs/react/lib/browser/test/test-module)有这个包的组件,不需要壳和 DOM。
function Header() {
const userService = useInject<UserService>(UserService);
const title = useValue<string>('app.public.title', 'My app');
return <header>{title}: {userService.name}</header>;
}配置(公开:前端接收)
| 键 | 默认 | |
|---|---|---|
react.router.type | browser | hash 用于基于哈希的 URL |
react.path | ${server.path ?? '/'} | 路由的基路径 |
react.router.routes | 按路径的路由元数据,在装饰器上合并:{ '/about': { caseSensitive: true, priority: 2000 } } |
前端配置在构建时评估,对加载页面的任何人可见:不要在其中放机密。
部件和插槽
<Slot area="header" /> 按 priority 顺序渲染为一个区域注册的部件;用 @Widget({ area: 'header', component: Logo }) 从 @celljs/widget 注册一个。库用这个来让应用无需改它们就能添加到它们的布局。
调用后端
useRpc<T>(Id) 返回 @celljs/rpc 的后端服务的类型化代理;它的方法返回 promise,所以在效果或事件处理器中调用它们。对于其他 HTTP API,使用 fetch 或 @celljs/http 的 RestOperations。
function Greeting() {
const greetingServer = useRpc<GreetingServer>(GreetingServer);
const [text, setText] = React.useState('');
React.useEffect(() => { greetingServer.greet('web').then(setText); }, [greetingServer]);
return <p>{text}</p>;
}用 react-intl 翻译
react-intl 是可选的。使用时,安装它,并在 src/browser/module.ts 中导入 @celljs/react/lib/browser/intl 一次:它的 IntlContext 将 IntlProvider 与选择的区域设置的消息放在应用周围,所以 useIntl() 和 <FormattedMessage> 在每个组件中工作。