如何把 Refine 集成进现有 React 项目并挂载到 /refine 路由?
如何把 Refine 集成进现有 React 项目并挂载到 /refine 路由?
如果你已经有一个运行中的 React 应用(Vite、Next.js 等),想把 Refine 的管理面板能力挂到应用的 /refine 路由下,而不是让 Refine 接管整个应用,Refine 官方提供了完整的接入方式:安装 @refinedev/core 及相关包,用一个 Refine 组件包裹 /refine 路由下的页面,其余现有路由保持原样。本文章节对应文档 Usage with Existing Projects。
集成原理与准备条件
Refine 组件为所有子组件提供必要的 context,Refine 的 hooks 和组件依赖这个 context 才能工作。Refine 组件唯一的必填 prop 是 dataProvider,关于 data provider 的介绍见 Data Provider 文档。
因此集成思路是:
- 如果只在个别组件里用 Refine hooks(比如
useShow),可以直接在App.tsx里用Refine组件包裹这些组件,无需路由配置; - 如果想把 Refine 页面挂载到
/refine路由,则需要为/refine路由单独准备一个Refine上下文组件(含routerProvider),现有路由不动。
本文按第二种目标展开。准备条件只有一个:项目中已有 React 环境和可用的包管理器。
安装依赖
在现有项目根目录安装以下包(以 npm 为例,pnpm add / yarn add 同理):
npm i @refinedev/core @refinedev/react-router @refinedev/simple-rest
@refinedev/core:核心包,Refine 运行的唯一必需依赖;@refinedev/react-router:路由适配包(routerProvider),Next.js 项目改用@refinedev/nextjs-router;@refinedev/simple-rest:文档示例中用作 data provider 的 REST 适配器,你可以换成项目自己的或任意的 data provider。
Vite + React Router 项目:挂载 /refine 路由
文档给出的分步说明如下:先在 refine/refine-context.tsx 创建 RefineContext 组件,用于包裹应用的 /refine 路由;然后在 App.tsx 中新增一个 path="/refine" 的 Route,并用 RefineContext 包裹;最后创建 refine/pages/products/list.tsx,由于它的布局被 Refine 组件包裹,即可使用 Refine 的各项功能。
第 1 步:创建 refine/refine-context.tsx
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/react-router";
import dataProvider from "@refinedev/simple-rest";
export function RefineContext({ children }) {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
list: "/refine/products",
},
]}
options={{ syncWithLocation: true }}
>
{children}
</Refine>
);
}
dataProvider 中的 https://api.fake-rest.refine.dev 是文档示例使用的演示 API 地址,实际使用时替换为你自己的后端地址。resources 中声明了 products 资源,其 list 页面位于 /refine/products。
第 2 步:在 App.tsx 中注册 /refine 路由
import { BrowserRouter, Route, Routes, Outlet } from "react-router";
import { ErrorComponent } from "@refinedev/core";
import { NavigateToResource } from "@refinedev/react-router";
import { Home } from "./pages/home.tsx";
import { About } from "./pages/about.tsx";
import { ProductList } from "./refine/pages/products/list.tsx";
import { RefineContext } from "./refine/refine-context.tsx";
export default function App() {
return (
<BrowserRouter>
<Routes>
{/* Your existing routes */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Refine routes */}
<Route
path="/refine"
element={
<RefineContext>
<Outlet />
</RefineContext>
}
>
<Route index element={<NavigateToResource />} />
<Route path="products" element={<ProductList />} />
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</BrowserRouter>
);
}
注意路由结构:/refine 路由的元素是 <RefineContext> 包裹的 <Outlet />,Refine 的页面作为子路由挂在它下面。index 路由使用 NavigateToResource,访问 /refine 时会自动导向 resources 中声明的列表页。Home / About 代表你现有的页面,它们不在 Refine 上下文内,不受影响。
第 3 步:创建 refine/pages/products/list.tsx
import { useList } from "@refinedev/core";
export const ProductList = () => {
const { result } = useList();
const products = result?.data;
return (
<div>
<h1>Refine Products Page</h1>
<ul>
{products?.map((record) => (
<li key={record.id}>{record.name}</li>
))}
</ul>
</div>
);
};
结果验证
在应用首页放置一个指向 /refine 的链接(文档示例的 Home 页即如此)。验证路径如下:
- 访问
/refine,应被NavigateToResource导向/refine/products; - 该页面使用
useList拉取数据,应渲染出产品列表——文档演示环境返回的是https://api.fake-rest.refine.dev的产品数据,换成自己的后端后应返回对应数据。
如果你暂时只想验证 hooks 可用而不挂路由,可以用文档的 headless 示例:在 App.tsx 中用 <Refine dataProvider={dataProvider(API_URL)}> 直接包裹组件,组件内使用 useShow({ resource: "products", id: 1 }),能拿到查询结果即说明 context 生效。
可选:为 /refine 添加 UI 布局
上一步的 /refine 页面是纯 hooks 渲染。如果希望有现成的管理界面,文档的后续示例是引入 @refinedev/antd 的 Ant Design 布局。额外安装:
npm i @refinedev/antd
对应的 refine/refine-context.tsx 改为:
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/react-router";
import { RefineThemes, ThemedLayout } from "@refinedev/antd";
import { App as AntdApp, ConfigProvider } from "antd";
import "@refinedev/antd/dist/reset.css";
export function RefineContext({ children }) {
return (
<ConfigProvider theme={RefineThemes.Blue}>
<AntdApp>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
list: "/refine/products",
},
]}
options={{ syncWithLocation: true }}
>
<ThemedLayout>{children}</ThemedLayout>
</Refine>
</AntdApp>
</ConfigProvider>
);
}
同时做两处更新:App.tsx 中的 ErrorComponent 改从 @refinedev/antd 导入;列表页改用 @refinedev/antd 的 List 组件与 useTable hook:
import { List, useTable } from "@refinedev/antd";
import { Table } from "antd";
export const ProductList = () => {
const { tableProps } = useTable();
return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="Id" />
<Table.Column dataIndex="name" title="Name" />
<Table.Column dataIndex="price" title="Price" />
</Table>
</List>
);
};
其他 UI 库(Material UI、Chakra UI、Mantine 等)的接入方式见 UI Libraries 指南。
Next.js 项目的接入差异
Next.js 项目用 @refinedev/nextjs-router 替代 @refinedev/react-router:
npm i @refinedev/core @refinedev/nextjs-router @refinedev/simple-rest
- App Router:文档的做法是创建
app/refine/layout.tsx,这个 layout 会被/refine文件夹下所有页面复用,页面文件(如app/refine/products/page.tsx)自然被Refine包裹。layout 需声明"use client":
"use client";
import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router";
import dataProvider from "@refinedev/simple-rest";
export default function RefineLayout({ children }: { children: React.ReactNode }) {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "products",
list: "/refine/products",
},
]}
options={{ syncWithLocation: true }}
>
{children}
</Refine>
);
}
之后在 app/refine/products/page.tsx 中即可直接使用 useList 等 hooks(该页面同样需要 "use client")。
- Pages Router:文档的做法是创建
src/components/refine-layout.tsx(Refine上下文组件,routerProvider从@refinedev/nextjs-router/pages导入),在pages/_app.tsx中根据页面是否定义了getLayout决定性地包裹页面,再给pages/refine/products.tsx挂上Page.getLayout,使其被Refine上下文包裹。需要说明的是,当前仓库中该示例文件(nextjs/pages/router.tsx)存在明显笔误:RefineLayout的返回 JSX 中残留了未导入的<AntdApp>、<ConfigProvider>标签。按文档说明的逻辑,该组件的正确形态应与上面 App Router 的 layout 一致,即直接返回<Refine ...>{children}</Refine>。
复用现有项目的认证
如果现有应用已经有登录态,文档的结论是:为了启用 Refine 的 Authentication 功能,只需要实现 AuthProvider 的 check 方法即可,登录、登出逻辑继续走你现有的方案。提供 check 之后,即可使用 Authenticated 组件和 useIsAuthenticated hook,Refine 会把未认证用户重定向到你指定的登录页:
import { AuthProvider } from "@refinedev/core";
export const authProvider: AuthProvider = {
check: async () => {
const isAuthenticated = myCheckLogic();
if (isAuthenticated) {
return { authenticated: true };
}
return {
authenticated: false,
redirectTo: "/my-login-page",
error: {
name: "Authentication Failed.",
message: "User not found.",
},
};
},
login: async () => {
throw new Error("Method not implemented.");
},
logout: async () => {
throw new Error("Method not implemented.");
},
onError: async () => {
throw new Error("Method not implemented.");
},
};
其中 myCheckLogic() 与 "/my-login-page" 是文档中的占位写法,替换为你自己的登录态判断和登录页路径。getIdentity 可选实现,用于启用 useGetIdentity hook(也用于 UI 布局头部显示当前用户);logout 可选实现,用于启用 useLogout hook(也用于侧边栏的登出按钮)。
参考文档
- Usage with Existing Projects 指南:本文各步骤的完整出处,含 Vite / Next.js App / Next.js Pages 三个 tab 的可运行沙盒示例;
- Routing 指南:路由与
syncWithLocation等选项的进一步说明; - Data Provider 文档:
dataProvider的编写与替换。