客户端组件
hono/jsx 不仅支持服务端渲染,也支持在客户端运行。这意味着你可以在浏览器里构建交互式界面,我们把它称作客户端组件,或 hono/jsx/dom。
它既快又小。hono/jsx/dom 的计数器示例在 Brotli 压缩后只有 2.8KB,而 React 的同类示例则有 47.8KB。
本章介绍客户端组件特有的功能。
计数器示例
下面是一个简单的计数器示例,代码与 React 中的写法相同。
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
function App() {
return (
<html>
<body>
<Counter />
</body>
</html>
)
}
const root = document.getElementById('root')
render(<App />, root)render()
你可以使用 render() 将 JSX 组件插入到指定的 HTML 元素中。
render(<Component />, container)与 React 兼容的 Hook
hono/jsx/dom 提供了与 React 兼容或部分兼容的 Hook。具体 API 可以参考 React 文档。
useState()useEffect()useRef()useCallback()use()startTransition()useTransition()useDeferredValue()useMemo()useLayoutEffect()useReducer()useDebugValue()createElement()memo()isValidElement()useId()createRef()forwardRef()useImperativeHandle()useSyncExternalStore()useInsertionEffect()useFormStatus()useActionState()useOptimistic()
startViewTransition() 系列
startViewTransition() 系列提供了原生的 Hook 与函数,帮助你更轻松地使用 View Transitions API。下面展示几种典型写法。
1. 最简单的示例
借助 startViewTransition(),可以非常简洁地调用 document.startViewTransition 来实现过渡效果。
import { useState, startViewTransition } from 'hono/jsx'
import { css, Style } from 'hono/css'
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
return (
<>
<Style />
<button
onClick={() =>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
</button>
<div>
{!showLargeImage ? (
<img src='https://hono.dev/images/logo.png' />
) : (
<div
class={css`
background: url('https://hono.dev/images/logo-large.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 600px;
height: 600px;
`}
></div>
)}
</div>
</>
)
}2. 结合 viewTransition() 与 keyframes()
viewTransition() 函数可以帮你获取唯一的 view-transition-name。
它可以和 keyframes() 搭配使用。::view-transition-old() 会被转换为 ::view-transition-old(${uniqueName})。
import { useState, startViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
<Style />
<button
onClick={() =>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
</button>
<div>
{!showLargeImage ? (
<img src='https://hono.dev/images/logo.png' />
) : (
<div
class={css`
${transitionNameClass}
background: url('https://hono.dev/images/logo-large.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 600px;
height: 600px;
`}
></div>
)}
</div>
</>
)
}3. 使用 useViewTransition
如果只想在动画期间改变样式,可以使用 useViewTransition()。该 Hook 会返回 [boolean, (callback: () => void) => void],其中包含 isUpdating 标志以及 startViewTransition() 函数。
当使用该 Hook 时,组件会在以下两个时机重新计算:
- 调用
startViewTransition()时,回调内部。 - 当
finishPromise 变为已完成状态时。
import { useState, useViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [isUpdating, startViewTransition] = useViewTransition()
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
<Style />
<button
onClick={() =>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
</button>
<div>
{!showLargeImage ? (
<img src='https://hono.dev/images/logo.png' />
) : (
<div
class={css`
${transitionNameClass}
background: url('https://hono.dev/images/logo-large.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 600px;
height: 600px;
position: relative;
${isUpdating &&
css`
&:before {
content: 'Loading...';
position: absolute;
top: 50%;
left: 50%;
}
`}
`}
></div>
)}
</div>
</>
)
}hono/jsx/dom 运行时
客户端组件有一个体积非常小的 JSX 运行时。使用它会比直接使用 hono/jsx 生成更小的打包结果。在 tsconfig.json 中指定 hono/jsx/dom 即可;如果使用 Deno,请修改 deno.json。
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}或者,也可以在 vite.config.ts 的 esbuild 转换配置中指定 hono/jsx/dom。
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxImportSource: 'hono/jsx/dom',
},
})