读者如果尝试过 Remix 那么感觉 Remix 页面与路由用的真的很难受(简略易用构造清晰),然而有一个问题,目前 Remix 我的项目集成了 antd/pro-components 等 国内UI
组件库好模板示例很少,于是想创立一个集成 Remix 与 antd 生态的组件库模板,能更快的体验并创立具备 antd 生态的 remix 我的项目。
浏览本文须要 React/Remix 基础知识和服务端渲染的相干常识
要留神的问题
外围要留神的问题就是:
问题 | 阐明 |
---|---|
模块(包) | 兼容性和 peer 依等问题 |
ssr | Remix 服务端渲染反对问题 |
兼容性
兼容性次要体现在 React18 和其余的包的兼容性
- 应用脚手架创立的我的项目默认应用 React 18,由此带来兼容性问题?
- React 18 api 产生了变动,渲染 api 调用是否手动批改为 React18 的形式?
- npm 的 peer 依赖装置与否?
- 其余的依赖的兼容 React 18 的问题?
Remix 服务端渲染的反对状况
咱们晓得 Remix 其实基于 esbuild 很多代码都跑在服务端,所以服务端的渲染的留神点是咱们要提前晓得:
- antd 反对服务端渲染
- pro-components 不反对服务端渲染,个别用于客户渲染,因为间接应用了 window/document 等客户端才有的全局对象
- remix-utils 工具包反对
<ClientOnly>{() => <>You Content</>}</ClientOnly>
应用组件仅仅在客户端进行渲染。
初始化我的项目装置必要的包
pnpm dlx create-umi@latest [your_package_name]# remix 抉择默认的选项即可pnpm install remix-utils antd @ant-design/pro-components @ant-design/cssinjs @ant-design/icons
应用新个性 v2 版本的文件路由模式
- remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */module.exports = { future: { v2_routeConvention: true, }, ignoredRouteFiles: ["**/.*"],};
增加 pro-components SettingDrawer
组件上下文
import { createContext } from "react";const SettingContext = createContext({ theme: {}, setTheme: (theme: any) => {}});export default SettingContext;
全局配置放在 SettingContext 上下文中,须要批改和应用都基于此上下文。
root 文件批改
// typeimport type { MetaFunction } from "@remix-run/node";// coreimport { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration,} from "@remix-run/react";export const meta: MetaFunction = () => ({ charset: "utf-8", title: "New Remix App", viewport: "width=device-width,initial-scale=1",});function Document({ children, title = "App title",}: { children: React.ReactNode; title?: string;}) { return ( <html lang="en"> <head> <Meta /> <title>{title}</title> <Links /> {typeof document === "undefined" ? "__ANTD__" : ""} </head> <body> {children} <ScrollRestoration /> <Scripts /> <LiveReload /> </body> </html> );}export default function App() { return ( <Document> <Outlet /> </Document> );}
- 将 html 独自的抽离一个 Document 组件,不便日后批改
- 在 Document 组建中减少
__ANTD__
不便前期替换 antd 客户端内容
减少客户端渲染入口文件:entry.client.tsx
客户端次要配合: @ant-design/cssinjs
// coresimport { startTransition, useState } from "react";import { hydrateRoot } from "react-dom/client";import { RemixBrowser } from "@remix-run/react";// components and othersimport { createCache, StyleProvider } from "@ant-design/cssinjs";import { ConfigProvider } from "antd";// contextimport SettingContext from "./settingContext";const hydrate = () => { startTransition(() => { const cache = createCache(); function MainApp() { const [theme, setTheme] = useState({ colorPrimary: "#00b96b" }); return ( <SettingContext.Provider value={{ theme, setTheme }}> <StyleProvider cache={cache}> <ConfigProvider theme={{ token: { colorPrimary: theme.colorPrimary, }, }} > <RemixBrowser /> </ConfigProvider> </StyleProvider> </SettingContext.Provider> ); } hydrateRoot(document, <MainApp />); });};if (typeof requestIdleCallback === "function") { requestIdleCallback(hydrate);} else { // Safari doesn't support requestIdleCallback // https://caniuse.com/requestidlecallback setTimeout(hydrate, 1);}
定义 theme, setTheme 给 SettingContext 应用管制 antd 配置变动,要阐明的点 StyleProvider 是用于 antd 服务端渲染 配置, 而 ConfigProvider 是 antd 主题配置的提供者。
- 留神:React18 中不能应用
hydrateRoot
api 来进行水合。
减少服务端渲染入口文件:entry.server.tsx
与 客户端一样须要 @ant-design/cssinjs
来配置 antd
的款式。
// typesimport type { EntryContext } from "@remix-run/node";// coreimport { useState } from "react";import { RemixServer } from "@remix-run/react";import { renderToString } from "react-dom/server";// componentsimport { ConfigProvider } from "antd";import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";// contextimport SettingContext from "./settingContext";export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext) { const cache = createCache(); function MainApp() { const [theme, setTheme] = useState({ colorPrimary: "#00b96b" }); return ( <SettingContext.Provider value={{ theme, setTheme }}> <StyleProvider cache={cache}> <ConfigProvider theme={{ token: { colorPrimary: theme.colorPrimary, }, }} > <RemixServer context={remixContext} url={request.url} /> </ConfigProvider> </StyleProvider> </SettingContext.Provider> ); } let markup = renderToString(<MainApp />); const styleText = extractStyle(cache); markup = markup.replace("__ANTD__", styleText); responseHeaders.set("Content-Type", "text/html"); return new Response("<!DOCTYPE html>" + markup, { status: responseStatusCode, headers: responseHeaders, });}
客户端和服务端的革新中蕴含了:
markup = markup.replace("__ANTD__", styleText);{typeof document === "undefined" ? "__ANTD__" : ""}
__ANTD__
在服务端环境中替换
创立一个布局用于承载 pro-components 组件
/routes/_layout.tsx
// coreimport { useContext } from "react";import { Outlet } from "@remix-run/react";// componentsimport { ClientOnly } from "remix-utils";import { ProConfigProvider, SettingDrawer } from "@ant-design/pro-components";// contextimport SettingContext from "~/settingContext";export default function Layout() { const value = useContext(SettingContext); return ( <ClientOnly fallback={<div>Loading...</div>}> {() => ( <ProConfigProvider> <Outlet /> <SettingDrawer getContainer={() => document.body} enableDarkTheme onSettingChange={(settings: any) => { value?.setTheme(settings); }} settings={{ ...value.theme }} themeOnly /> </ProConfigProvider> )} </ClientOnly> );}
留神:布局组件中应用有以下几个点须要留神:
- useContext 获取以后的上下文
- ClientOnly 组件用于仅仅在客户端渲染 Remix 组件
- ProConfigProvider 组件为 SettingDrawer/Outlet 组件提供上下文
- SettingDrawer 给应用以后布局
_layout
的组件提供色彩等配置
应用 antd 创立一个简略的基于 _layout._index.tsx
页面
// coreimport { json } from "@remix-run/node";import { useFetcher } from "@remix-run/react";// componentsimport { Button, Form, Input, Select } from "antd";export async function action() { return json({ title: 1, });}const { Option } = Select;const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 },};const tailLayout = { wrapperCol: { offset: 8, span: 16 },};export default function Index() { const fetcher = useFetcher(); const [form] = Form.useForm(); const onGenderChange = (value: string) => { switch (value) { case "male": form.setFieldsValue({ note: "Hi, man!" }); break; case "female": form.setFieldsValue({ note: "Hi, lady!" }); break; case "other": form.setFieldsValue({ note: "Hi there!" }); break; default: } }; const onFinish = (value: any) => { const formData = new FormData(); formData.append("username", value.username); formData.append("password", value.password); fetcher.submit(formData, { method: "post" }); }; const onReset = () => { form.resetFields(); }; const onFill = () => { form.setFieldsValue({ note: "Hello world!", gender: "male" }); }; return ( <div> <Form {...layout} form={form} name="control-hooks" onFinish={onFinish} style={{ maxWidth: 600 }} > <Form.Item name="note" label="Note" rules={[{ required: true }]}> <Input /> </Form.Item> <Form.Item name="gender" label="Gender" rules={[{ required: true }]}> <Select placeholder="Select a option and change input text above" onChange={onGenderChange} allowClear > <Option value="male">male</Option> <Option value="female">female</Option> <Option value="other">other</Option> </Select> </Form.Item> <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender } > {({ getFieldValue }) => getFieldValue("gender") === "other" ? ( <Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]} > <Input /> </Form.Item> ) : null } </Form.Item> <Form.Item {...tailLayout}> <Button type="primary" htmlType="submit"> Submit </Button> <Button htmlType="button" onClick={onReset}> Reset </Button> <Button type="link" htmlType="button" onClick={onFill}> Fill form </Button> </Form.Item> </Form> </div> );}
_layout._index.tsx
示意应用:_layout
布局的 /
页面路由。
应用 pro-component 创立一个简略的基于 _layout._procomponents.tsx
页面
// coreimport { json } from "@remix-run/node";import { useFetcher } from "@remix-run/react";// componentsimport { Button, Form, Space } from "antd";import { ProForm, ProFormDependency, ProFormSelect, ProFormText,} from "@ant-design/pro-components";export async function action() { return json({ title: 1, });}const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 },};const tailLayout = { wrapperCol: { offset: 8, span: 16 },};export default function Index() { const fetcher = useFetcher(); const [form] = Form.useForm(); const onGenderChange = (value: string) => { switch (value) { case "male": form.setFieldsValue({ note: "Hi, man!" }); break; case "female": form.setFieldsValue({ note: "Hi, lady!" }); break; case "other": form.setFieldsValue({ note: "Hi there!" }); break; default: } }; const onFinish = (value: any) => { const formData = new FormData(); formData.append("username", value.username); formData.append("password", value.password); fetcher.submit(formData, { method: "post" }); }; const onReset = () => { form.resetFields(); }; const onFill = () => { form.setFieldsValue({ note: "Hello world!", gender: "male" }); }; return ( <div> <Form {...layout} form={form} name="control-hooks" onFinish={onFinish} style={{ maxWidth: 600 }} > <ProFormText name="note" label="Note" rules={[{ required: true }]} /> <ProFormSelect name="gender" label="Gender" rules={[{ required: true }]} fieldProps={{ onChange: onGenderChange }} options={[ { label: "male", value: "male", }, { label: "female", value: "female", }, { label: "other", value: "other", }, ]} /> <ProFormDependency name={["gender"]}> {({ gender }) => { return gender === "other" ? ( <ProFormText noStyle name="customizeGender" label="Customize Gender" rules={[{ required: true }]} /> ) : null; }} </ProFormDependency> <ProForm.Item {...tailLayout}> <Space> <Button type="primary" htmlType="submit"> Submit </Button> <Button htmlType="button" onClick={onReset}> Reset </Button> <Button type="link" htmlType="button" onClick={onFill}> Fill form </Button> </Space> </ProForm.Item> </Form> </div> );}
/procomponents
页面根本是 /
页面应用 pro-components 的革新版本。须要咱们留神的是 表单联动
应用用形式不一样。
我的项目地址
到目前为止基于 antd 的我的项目 remix 曾经探究出一部分,对应 remix antd 感兴趣可拜访 create-remix-antd-pro-app 该我的项目托管在 Github。
小结
到这里就在 Remix 中就集成了 antd/pro-components
组件库就根本完结了
- 外围还是应用
ClientOnly
在客户端渲染组件。 - 提供了
SettingDrawer
更换以后主题的性能。 - 外围难点: 库之间的兼容性问题的解决方案或者代替计划