99 lines
2.9 KiB
Plaintext
99 lines
2.9 KiB
Plaintext
---
|
|
title: 响应式与交互 Hooks
|
|
description: 在行为必须随视口改变时同步断点,并为自定义交互元素添加波纹。
|
|
order: 20
|
|
toc:
|
|
- id: breakpoint-contract
|
|
title: 断点约定
|
|
- id: reading-breakpoints
|
|
title: 读取断点
|
|
- id: root-classes
|
|
title: 根节点 class
|
|
- id: server-rendering
|
|
title: 服务端渲染
|
|
- id: ripple
|
|
title: 波纹交互
|
|
---
|
|
|
|
## 断点约定 {#breakpoint-contract}
|
|
|
|
共享断点把视口分成三段:
|
|
|
|
| 返回值 | 范围 |
|
|
| --------- | --------------- |
|
|
| `mobile` | 小于 768px |
|
|
| `tablet` | 768px 至 1023px |
|
|
| `desktop` | 大于等于 1024px |
|
|
|
|
仅视觉变化应优先使用 CSS。只有挂载内容、交互方式或数据量必须变化时,才在渲染逻辑中读取断点。
|
|
|
|
## 读取断点 {#reading-breakpoints}
|
|
|
|
```tsx
|
|
import {
|
|
useBreakpoint,
|
|
useIsDesktop,
|
|
useIsMobile,
|
|
useIsTablet,
|
|
useOnBreakpoint,
|
|
} from "@workspace/ui/hooks/use-breakpoint"
|
|
|
|
const breakpoint = useBreakpoint()
|
|
const isMobile = useIsMobile()
|
|
```
|
|
|
|
每个 hook 通过 `useSyncExternalStore` 订阅同一组 `matchMedia` 查询。`useOnBreakpoint(listener)` 只在断点发生变化后调用 listener,不会把初始值作为一次变化上报。
|
|
|
|
## 根节点 class {#root-classes}
|
|
|
|
挂载 `BreakpointReactor` 后,文档根节点会且只会拥有 `.ui:mobile`、`.ui:tablet`、`.ui:desktop` 中的一个:
|
|
|
|
```tsx
|
|
import { BreakpointReactor } from "@workspace/ui/hooks/use-breakpoint"
|
|
|
|
;<>
|
|
<BreakpointReactor />
|
|
<App />
|
|
</>
|
|
```
|
|
|
|
`globals.css` 把它们注册为 `mobile:`、`tablet:`、`desktop:` 自定义变体。这样组件可以让 CSS 与行为层使用同一份三段式定义。
|
|
|
|
## 服务端渲染 {#server-rendering}
|
|
|
|
服务端快照固定为 `desktop`,保证 SSR 可重复。为了在水合前写入真实 class,可把 `getBreakpointInitializationScript()` 的结果作为内联脚本放到文档头部:
|
|
|
|
```tsx
|
|
import { getBreakpointInitializationScript } from "@workspace/ui/hooks/use-breakpoint"
|
|
|
|
;<script
|
|
dangerouslySetInnerHTML={{ __html: getBreakpointInitializationScript() }}
|
|
/>
|
|
```
|
|
|
|
在严格 CSP 下为脚本传入应用生成的 nonce。脚本只同步 class,不替代 `BreakpointReactor` 后续监听变化。
|
|
|
|
## 波纹交互 {#ripple}
|
|
|
|
`Button` 已自动使用 `useRippleRef`,普通按钮无需重复配置。自定义交互表面可直接使用 hook:
|
|
|
|
```tsx
|
|
import { useRipple } from "@workspace/ui/hooks/use-ripple"
|
|
|
|
export function Tile() {
|
|
const ref = useRipple<HTMLButtonElement>({
|
|
duration: 500,
|
|
opacity: 0.2,
|
|
rippleClassName: "bg-primary",
|
|
})
|
|
|
|
return (
|
|
<button ref={ref} className="relative">
|
|
打开项目
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
元素必须建立定位上下文,通常使用 `relative`。hook 会忽略 disabled、`aria-disabled="true"` 和非主鼠标键,并在 ref 变化或卸载时移除监听器和动画节点。需要合并外部 ref 时使用 `useRippleRef(ref, options)`。
|