本篇内容介绍了“Remix怎么集成antd和pro-components”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
核心要注意的问题就是:
问题 | 说明 |
---|---|
模块(包) | 兼容性和 peer 依等问题 |
ssr | Remix 服务端渲染支持问题 |
兼容性主要体现在 React18 和其他的包的兼容性
使用脚手架创建的项目默认使用 React 18,由此带来兼容性问题?
React 18 api 发生了变化,渲染 api 调用是否手动修改为 React18 的方式?
npm 的 peer 依赖安装与否?
其他的依赖的兼容 React 18 的问题?
我们知道 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
remix.config.js
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
future: {
v2_routeConvention: true,
},
ignoredRouteFiles: ["**/.*"],
};
import { createContext } from "react";
const SettingContext = createContext({
theme: {},
setTheme: (theme: any) => {}
});
export default SettingContext;
全局配置放在 SettingContext 上下文中,需要修改和使用都基于此上下文。
// type
import type { MetaFunction } from "@remix-run/node";
// core
import {
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 客户端内容
客户端主要配合: @ant-design/cssinjs
// cores
import { startTransition, useState } from "react";
import { hydrateRoot } from "react-dom/client";
import { RemixBrowser } from "@remix-run/react";
// components and others
import { createCache, StyleProvider } from "@ant-design/cssinjs";
import { ConfigProvider } from "antd";
// context
import 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 来进行水合。
与 客户端一样需要 @ant-design/cssinjs 来配置 antd 的样式。
// types
import type { EntryContext } from "@remix-run/node";
// core
import { useState } from "react";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
// components
import { ConfigProvider } from "antd";
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
// context
import 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__ 在服务端环境中替换
/routes/_layout.tsx
// core
import { useContext } from "react";
import { Outlet } from "@remix-run/react";
// components
import { ClientOnly } from "remix-utils";
import { ProConfigProvider, SettingDrawer } from "@ant-design/pro-components";
// context
import 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 的组件提供颜色等配置
// core
import { json } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
// components
import { 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 布局的 / 页面路由。
// core
import { json } from "@remix-run/node";
import { useFetcher } from "@remix-run/react";
// components
import { 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 的改造版本。需要我们注意的是 表单联动 使用用方式不一样。
“Remix怎么集成antd和pro-components”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://juejin.cn/post/7213923957823995960