Skip to content

Cell - React 组件 ​

Cell 应用的 React 前端:用装饰器声明的页面、React Router 路由、布局和应用壳,都由前端 DI 容器管理。安装后添加 frontend 构建目标(通过 @celljs/widget)。指南:前端与 RPC。

bash
npm install @celljs/react react
npm install -D @types/react

cell 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。

tsx
// 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 将错误记录到控制台。

tsx
// 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。

tsx
function Header() {
    const userService = useInject<UserService>(UserService);
    const title = useValue<string>('app.public.title', 'My app');
    return <header>{title}: {userService.name}</header>;
}

配置(公开:前端接收) ​

键默认
react.router.typebrowserhash 用于基于哈希的 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。

tsx
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> 在每个组件中工作。