Skip to content

JSX

你可以使用 hono/jsx 和 JSX 语法编写 HTML。

¥You can write HTML with JSX syntax with hono/jsx.

虽然 hono/jsx 在客户端上运行,但你可能最常在服务器端渲染内容时使用它。以下是与 JSX 相关的一些内容,这些内容对服务器和客户端都很常见。

¥Although hono/jsx works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.

设置

¥Settings

要使用 JSX,请修改 tsconfig.json

¥To use JSX, modify the tsconfig.json:

tsconfig.json

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx"
  }
}

或者,使用 pragma 指令:

¥Alternatively, use the pragma directives:

ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */

对于 Deno,你必须修改 deno.json 而不是 tsconfig.json

¥For Deno, you have to modify the deno.json instead of the tsconfig.json:

json
{
  "compilerOptions": {
    "jsx": "precompile",
    "jsxImportSource": "hono/jsx"
  }
}

用法

¥Usage

index.tsx

tsx
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'

const app = new Hono()

const Layout: FC = (props) => {
  return (
    <html>
      <body>{props.children}</body>
    </html>
  )
}

const Top: FC<{ messages: string[] }> = (props: {
  messages: string[]
}) => {
  return (
    <Layout>
      <h1>Hello Hono!</h1>
      <ul>
        {props.messages.map((message) => {
          return <li>{message}!!</li>
        })}
      </ul>
    </Layout>
  )
}

app.get('/', (c) => {
  const messages = ['Good Morning', 'Good Evening', 'Good Night']
  return c.html(<Top messages={messages} />)
})

export default app

片段

¥Fragment

使用 Fragment 对多个元素进行分组,而无需添加额外的节点:

¥Use Fragment to group multiple elements without adding extra nodes:

tsx
import { Fragment } from 'hono/jsx'

const List = () => (
  <Fragment>
    <p>first child</p>
    <p>second child</p>
    <p>third child</p>
  </Fragment>
)

或者,如果设置正确,你可以使用 <></> 编写它。

¥Or you can write it with <></> if it set up properly.

tsx
const List = () => (
  <>
    <p>first child</p>
    <p>second child</p>
    <p>third child</p>
  </>
)

PropsWithChildren

你可以使用 PropsWithChildren 正确地推断函数组件中的子元素。

¥You can use PropsWithChildren to correctly infer a child element in a function component.

tsx
import { PropsWithChildren } from 'hono/jsx'

type Post = {
  id: number
  title: string
}

function Component({ title, children }: PropsWithChildren<Post>) {
  return (
    <div>
      <h1>{title}</h1>
      {children}
    </div>
  )
}

插入原始 HTML

¥Inserting Raw HTML

要直接插入 HTML,请使用 dangerouslySetInnerHTML

¥To directly insert HTML, use dangerouslySetInnerHTML:

tsx
app.get('/foo', (c) => {
  const inner = { __html: 'JSX &middot; SSR' }
  const Div = <div dangerouslySetInnerHTML={inner} />
})

记忆化

¥Memoization

通过使用 memo 记忆计算字符串来优化你的组件:

¥Optimize your components by memoizing computed strings using memo:

tsx
import { memo } from 'hono/jsx'

const Header = memo(() => <header>Welcome to Hono</header>)
const Footer = memo(() => <footer>Powered by Hono</footer>)
const Layout = (
  <div>
    <Header />
    <p>Hono is cool!</p>
    <Footer />
  </div>
)

上下文

¥Context

通过使用 useContext,你可以在组件树的任何级别上全局共享数据,而无需通过 props 传递值。

¥By using useContext, you can share data globally across any level of the Component tree without passing values through props.

tsx
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'

const themes = {
  light: {
    color: '#000000',
    background: '#eeeeee',
  },
  dark: {
    color: '#ffffff',
    background: '#222222',
  },
}

const ThemeContext = createContext(themes.light)

const Button: FC = () => {
  const theme = useContext(ThemeContext)
  return <button style={theme}>Push!</button>
}

const Toolbar: FC = () => {
  return (
    <div>
      <Button />
    </div>
  )
}

// ...

app.get('/', (c) => {
  return c.html(
    <div>
      <ThemeContext.Provider value={themes.dark}>
        <Toolbar />
      </ThemeContext.Provider>
    </div>
  )
})

异步组件

¥Async Component

hono/jsx 支持异步组件,因此你可以在组件中使用 async/await。如果你使用 c.html() 渲染它,它将自动等待。

¥hono/jsx supports an Async Component, so you can use async/await in your component. If you render it with c.html(), it will await automatically.

tsx
const AsyncComponent = async () => {
  await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
  return <div>Done!</div>
}

app.get('/', (c) => {
  return c.html(
    <html>
      <body>
        <AsyncComponent />
      </body>
    </html>
  )
})

Suspense 实验性

¥Suspense Experimental

类似 React 的 Suspense 功能可用。如果你用 Suspense 封装异步组件,则 fallback 中的内容将首先渲染,一旦 Promise 被解析,将显示等待的内容。你可以将它与 renderToReadableStream() 一起使用。

¥The React-like Suspense feature is available. If you wrap the async component with Suspense, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed. You can use it with renderToReadableStream().

tsx
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'

//...

app.get('/', (c) => {
  const stream = renderToReadableStream(
    <html>
      <body>
        <Suspense fallback={<div>loading...</div>}>
          <Component />
        </Suspense>
      </body>
    </html>
  )
  return c.body(stream, {
    headers: {
      'Content-Type': 'text/html; charset=UTF-8',
      'Transfer-Encoding': 'chunked',
    },
  })
})

ErrorBoundary 实验性

¥ErrorBoundary Experimental

你可以使用 ErrorBoundary 捕获子组件中的错误。

¥You can catch errors in child components using ErrorBoundary.

在下面的示例中,如果发生错误,它将显示 fallback 中指定的内容。

¥In the example below, it will show the content specified in fallback if an error occurs.

tsx
function SyncComponent() {
  throw new Error('Error')
  return <div>Hello</div>
}

app.get('/sync', async (c) => {
  return c.html(
    <html>
      <body>
        <ErrorBoundary fallback={<div>Out of Service</div>}>
          <SyncComponent />
        </ErrorBoundary>
      </body>
    </html>
  )
})

ErrorBoundary 还可以与异步组件和 Suspense 一起使用。

¥ErrorBoundary can also be used with async components and Suspense.

tsx
async function AsyncComponent() {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  throw new Error('Error')
  return <div>Hello</div>
}

app.get('/with-suspense', async (c) => {
  return c.html(
    <html>
      <body>
        <ErrorBoundary fallback={<div>Out of Service</div>}>
          <Suspense fallback={<div>Loading...</div>}>
            <AsyncComponent />
          </Suspense>
        </ErrorBoundary>
      </body>
    </html>
  )
})

与 html 集成中间件

¥Integration with html Middleware

结合 JSX 和 html 中间件,实现强大的模板。有关详细信息,请参阅 html 中间件文档

¥Combine the JSX and html middlewares for powerful templating. For in-depth details, consult the html middleware documentation.

tsx
import { Hono } from 'hono'
import { html } from 'hono/html'

const app = new Hono()

interface SiteData {
  title: string
  children?: any
}

const Layout = (props: SiteData) =>
  html`<!doctype html>
    <html>
      <head>
        <title>${props.title}</title>
      </head>
      <body>
        ${props.children}
      </body>
    </html>`

const Content = (props: { siteData: SiteData; name: string }) => (
  <Layout {...props.siteData}>
    <h1>Hello {props.name}</h1>
  </Layout>
)

app.get('/:name', (c) => {
  const { name } = c.req.param()
  const props = {
    name: name,
    siteData: {
      title: 'JSX with html sample',
    },
  }
  return c.html(<Content {...props} />)
})

export default app

使用 JSX 渲染器中间件

¥With JSX Renderer Middleware

JSX 渲染器中间件 允许你更轻松地使用 JSX 创建 HTML 页面。

¥The JSX Renderer Middleware allows you to create HTML pages more easily with the JSX.

覆盖类型定义

¥Override type definitions

你可以覆盖类型定义以添加自定义元素和属性。

¥You can override the type definition to add your custom elements and attributes.

ts
declare module 'hono/jsx' {
  namespace JSX {
    interface IntrinsicElements {
      'my-custom-element': HTMLAttributes & {
        'x-event'?: 'click' | 'scroll'
      }
    }
  }
}

Hono v4.7 中文网 - 粤ICP备13048890号