共计 9084 个字符,预计需要花费 23 分钟才能阅读完成。
读者如果尝试过 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 文件批改
// 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 客户端内容
减少客户端渲染入口文件:entry.client.tsx
客户端次要配合:@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 来进行 水合。
减少服务端渲染入口文件:entry.server.tsx
与 客户端一样须要 @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__
在服务端环境中替换
创立一个布局用于承载 pro-components 组件
/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
的组件提供色彩等配置
应用 antd 创立一个简略的基于 _layout._index.tsx
页面
// 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
布局的 /
页面路由。
应用 pro-component 创立一个简略的基于 _layout._procomponents.tsx
页面
// 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 的革新版本。须要咱们留神的是 表单联动
应用用形式不一样。
我的项目地址
到目前为止基于 antd 的我的项目 remix 曾经探究出一部分,对应 remix antd 感兴趣可拜访 create-remix-antd-pro-app 该我的项目托管在 Github。
小结
到这里就在 Remix 中就集成了 antd/pro-components
组件库就根本完结了
- 外围还是应用
ClientOnly
在客户端渲染组件。 - 提供了
SettingDrawer
更换以后主题的性能。 - 外围难点: 库之间的兼容性问题的解决方案或者代替计划