@winner-fed/renderer-react WinJS 框架的 React 渲染器,提供客户端渲染能力和 React Router 集成。
✅React 19.x 支持 - 使用 React 19 的最新特性和并发渲染能力 🚦React Router v7 集成 - 最新的路由管理和导航能力 🎯多种路由模式 - 支持 Browser、Hash 和 Memory 路由 🔄插件系统集成 - 与 WinJS 插件系统无缝集成 📦TypeScript 支持 - 完整的类型定义 🎨应用上下文 - 通过 Context 访问路由和应用数据 ⚡懒加载 - 内置代码分割和懒加载支持 🔗路由预加载 - 支持程序化路由预加载 🌊流式渲染 - 支持 React Suspense 流式渲染 npm install @winner-fed/renderer-react renderClient 是渲染 React 应用的主入口:
import { renderClient } from '@winner-fed/renderer-react' ; renderClient ( { rootElement :document . getElementById ( 'root' ) , routes :routesById , routeComponents :routeComponents , pluginManager :pluginManager , basename :'/app' , history :history , } ) ; 路由定义采用 WinJS 路由结构,会自动转换为 React Router 格式:
const routesById = { 'home' :{ id :'home' , path :'/' , parentId :undefined , } , 'about' :{ id :'about' , path :'/about' , parentId :undefined , clientLoader :async ( ) => { return { data :'About 页面数据' } ; } , } , 'user' :{ id :'user' , path :'/user/:id' , parentId :'about' , } , } ; const routeComponents = { 'home' :HomePage , 'about' :AboutPage , 'user' :UserPage , } ; 通过useAppData Hook 访问应用数据:
import { useAppData } from '@winner-fed/renderer-react' ; function MyComponent ( ) { const { routes, clientRoutes, pluginManager, basename, history} = useAppData ( ) ; return < div > 当前 basename:{ basename } </ div > ; } 将 React 应用渲染到 DOM。
参数:
interface RenderClientOpts { // 公共路径配置 publicPath ?:string ; // 是否运行时配置 publicPath runtimePublicPath ?:boolean ; // 挂载元素 ID(微前端场景可能变化) mountElementId ?:string ; // 挂载的 DOM 元素 rootElement ?:HTMLElement ; // 路由配置(按 ID 索引) routes :IRoutesById ; // 路由组件映射 routeComponents :IRouteComponents ; // 插件管理器实例 pluginManager :any ; // 路由 base 路径 basename ?:string ; // 加载中显示的组件 loadingComponent ?:React . ReactNode ; // History 实例(browserHistory/hashHistory/memoryHistory) history :History ; // 是否启用流式渲染(默认 true) useStream ?:boolean ; // 是否仅返回组件(用于测试) components ?:boolean ; // 渲染完成回调 callback ?:( ) => void ; } 返回值:
如果components: true,返回 React 组件 否则直接渲染到 DOM,无返回值 createClientRoutes({ routesById, routeComponents })将 WinJS 路由格式转换为 React Router 格式。
参数:
{ routesById:IRoutesById ; routeComponents:Record < string , any > ; parentId?:string ; loadingComponent?:React . ReactNode ; useStream?:boolean ; } 获取当前的 React Root 实例(用于卸载等场景,如微前端)。
获取应用上下文数据:
const { routes, // 路由配置 routeComponents, // 路由组件映射 clientRoutes, // 客户端路由树 pluginManager, // 插件管理器 rootElement, // 根元素 basename, // base 路径 clientLoaderData, // 客户端加载的数据 preloadRoute, // 预加载路由函数 history// History 实例 } = useAppData ( ) ; 获取当前路由的 clientLoader 加载的数据:
function UserPage ( ) { const { data} = useLoaderData ( ) ; return < div > 用户:{ data . name } </ div > ; } 已废弃,请使用useLoaderData()
获取当前路由的上下文数据:
import { useRouteData } from '@winner-fed/renderer-react' ; function MyComponent ( ) { const { route} = useRouteData ( ) ; return < div > 当前路由 ID:{ route . id } </ div > ; } 获取当前路由的属性(不包括 element):
function MyComponent ( ) { const routeProps = useRouteProps ( ) ; return < div > { routeProps . someCustomProp } </ div > ; } 获取当前匹配的路由链(从根到当前路由):
function Breadcrumb ( ) { const routes = useSelectedRoutes ( ) ; return ( < div > { routes . map ( r => < span key = { r . route . id } > { r . route . path } </ span > ) } </ div > ) ; } 路由链接组件(从 react-router 导出):
import { Link } from '@winner-fed/renderer-react' ; < Link to = "/about" > 关于我们</ Link > < Link to = "/user/123" > 用户页面 < / L i n k > < Link to = "/settings" state = { { from :'home' } } > 设置</ Link > 为类组件注入路由相关的 props:
import { withRouter , RouteComponentProps } from '@winner-fed/renderer-react' ; class MyComponent extends React . Component < RouteComponentProps > { handleClick = ( ) => { this . props . history . push ( '/home' ) ; } ; render ( ) { const { location, params, navigate} = this . props ; return < div > 当前路径:{ location . pathname } </ div > ; } } export default withRouter ( MyComponent ) ; 注入的 props:
history - 包含back(),goBack(),push(),replace() 等方法location - 当前位置对象match - 包含路由参数params - 路由参数对象navigate - 导航函数直接从react-router 重新导出的常用 API:
import { // Hooks useLocation , useNavigate , useParams , useSearchParams , useMatch , useOutlet , useOutletContext , useResolvedPath , useRoutes , // 组件 Link , Navigate , NavLink , Outlet , // 工具函数 createSearchParams , generatePath , matchPath , matchRoutes , resolvePath , } from '@winner-fed/renderer-react' ; 从history 包重新导出:
import { createBrowserHistory , createHashHistory , createMemoryHistory , type History , } from '@winner-fed/renderer-react' ; 路由定义接口:
interface IRoute { id :string ; // 路由唯一标识 path ?:string ; // 路由路径 index ?:boolean ; // 是否为索引路由 parentId ?:string ; // 父路由 ID redirect ?:string ; // 重定向路径 clientLoader ?:ClientLoader ; // 客户端数据加载函数 routeProps ?:Record < string , any > ; // 自定义路由属性 } 客户端路由接口(扩展自 IRoute):
interface IClientRoute extends IRoute { element ?:React . ReactNode ; // 路由元素 Component ?:React . ComponentType ; // 路由组件 children ?:IClientRoute [ ] ; // 子路由 routes ?:IClientRoute [ ] ; // 子路由(遗留) } 路由映射表:
interface IRoutesById { [ id :string ] :IRoute ; } 路由组件映射表:
interface IRouteComponents { [ id :string ] :any ; // React 组件 } 客户端数据加载器:
type ClientLoader = ( ( ) => Promise < any > ) & { hydrate ?:boolean ; // 是否需要水合 } ; 渲染器与 WinJS 插件系统深度集成,提供多个插件钩子:
// 1. innerProvider - 最内层 Provider pluginManager . applyPlugins ( { type :'modify' , key :'innerProvider' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 2. i18nProvider - 国际化 Provider pluginManager . applyPlugins ( { type :'modify' , key :'i18nProvider' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 3. accessProvider - 权限控制 Provider pluginManager . applyPlugins ( { type :'modify' , key :'accessProvider' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 4. dataflowProvider - 数据流 Provider pluginManager . applyPlugins ( { type :'modify' , key :'dataflowProvider' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 5. outerProvider - 最外层 Provider pluginManager . applyPlugins ( { type :'modify' , key :'outerProvider' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 6. rootContainer - 根容器 pluginManager . applyPlugins ( { type :'modify' , key :'rootContainer' , initialValue :App , args :{ routes, history, plugin} , } ) ; // 修改客户端路由 pluginManager . applyPlugins ( { type :'event' , key :'patchClientRoutes' , args :{ routes :clientRoutes } , } ) ; // 路由变化事件 pluginManager . applyPlugins ( { type :'event' , key :'onRouteChange' , args :{ routes, clientRoutes, location, action, basename, isFirst :boolean , } , } ) ; import { renderClient } from '@winner-fed/renderer-react' ; import { createBrowserHistory } from '@winner-fed/renderer-react' ; const history = createBrowserHistory ( ) ; renderClient ( { rootElement :document . getElementById ( 'root' ) , routes :{ 'home' :{ id :'home' , path :'/' } , 'about' :{ id :'about' , path :'/about' } , } , routeComponents :{ 'home' :( ) => < div > 首页</ div > , 'about' :( ) => < div > 关于</ div > , } , pluginManager :pluginManager , history :history , basename :'/' , } ) ; const routes = { 'user' :{ id :'user' , path :'/user/:id' , clientLoader :async ( ) => { const user = await fetchUser ( params . id ) ; return { user} ; } , } , } ; function UserPage ( ) { const { data} = useLoaderData ( ) ; return < div > 用户:{ data . user . name } </ div > ; } const routes = { 'old-path' :{ id :'old-path' , path :'/old' , redirect :'/new' , } , 'new-path' :{ id :'new-path' , path :'/new' , } , } ; const routes = { 'layout' :{ id :'layout' , path :'/' , } , 'home' :{ id :'home' , path :'/' , index :true , parentId :'layout' , } , 'about' :{ id :'about' , path :'about' , parentId :'layout' , } , } ; const routeComponents = { 'layout' :( ) => ( < div > < nav > 导航</ nav > < Outlet /> </ div > ) , 'home' :( ) => < div > 首页内容</ div > , 'about' :( ) => < div > 关于内容</ div > , } ; const routes = { 'protected' :{ id :'protected' , path :'/protected' , routeProps :{ requireAuth :true , keepQuery :true , } , } , } ; function ProtectedPage ( ) { const routeProps = useRouteProps ( ) ; if ( routeProps . requireAuth && ! isLoggedIn ( ) ) { return < Navigate to = "/login" /> ; } return < div > 受保护的内容</ div > ; } import { Link , useAppData } from '@winner-fed/renderer-react' ; function Navigation ( ) { const { preloadRoute} = useAppData ( ) ; // 程序化预加载 const handleHover = ( ) => { if ( preloadRoute ) { preloadRoute ( '/dashboard' ) ; } } ; return ( < nav > < Link to = "/home" > 首页</ Link > < Link to = "/products" > 产品</ Link > { /* 程序化预加载 */ } < button onMouseEnter = { handleHover } > 控制台</ button > </ nav > ) ; } 路由转换 :将 WinJS 的路由格式(IRoutesById)转换为 React Router 格式(IClientRoute[])插件集成 :应用插件系统的各种钩子(patchClientRoutes、onRouteChange 等)数据加载 :在路由变化时自动执行 clientLoader 并缓存数据Provider 包装 :应用多层 Provider(innerProvider → i18nProvider → accessProvider → dataflowProvider → outerProvider → rootContainer)React 渲染 :使用 React 18+ 的 createRoot API 渲染应用根据parentId 构建嵌套路由树 index: true 的路由转换为索引路由redirect 字段转换为Navigate 组件clientLoader 保留在路由定义中,由渲染器管理数据加载使用useCallback 避免不必要的函数重建 clientLoader 数据全局缓存,避免重复加载 支持流式渲染(useStream),提升首屏加载速度 通过 preloadRoute 支持程序化路由预加载 react /react-dom :需要 React 19.0.0 或更高版本react-router :使用 React Router v7 进行路由管理history :使用 History v5,支持多种路由模式(Browser/Hash/Memory)@winner-fed/winjs :与 WinJS 插件系统集成1. 如何在 clientLoader 中访问路由参数? 目前 clientLoader 不直接接收参数,但可以通过window.location 或组件内部的 hooks 获取:
const routes = { 'user' :{ id :'user' , path :'/user/:id' , clientLoader :async ( ) => { // 方案 1:从 URL 解析参数 const id = window . location . pathname . split ( '/' ) . pop ( ) ; return { user :await fetchUser ( id ) } ; } , } , } ; 使用__getRoot() 获取 React Root 实例,在卸载时调用root.unmount():
import { renderClient , __getRoot } from '@winner-fed/renderer-react' ; // 挂载 renderClient ( { /* ... */ } ) ; // 卸载 const root = __getRoot ( ) ; if ( root ) root . unmount ( ) ; 通过插件系统的onRouteChange 钩子实现:
export default { onRouteChange ( { location, routes} ) { // 路由守卫逻辑 if ( needAuth && ! isLoggedIn ( ) ) { history . push ( '/login' ) ; } } , } ; 通过loadingComponent 参数传入:
renderClient ( { // ... loadingComponent :< CustomSpinner /> , } ) ; 使用useAppData 的preloadRoute 方法:
const { preloadRoute} = useAppData ( ) ; // 在鼠标悬停时预加载 const handleHover = ( ) => { if ( preloadRoute ) { preloadRoute ( '/target-path' ) ; } } ; MIT