Skip to main content

JSX

还没翻译译自英文(hono.dev)本页 42 段都还没译

模型写的

背景主张方法设置结果局限

JSX

通过 hono/jsx 用 JSX 语法编写 HTML

You can write HTML with JSX syntax with hono/jsx.You can write HTML with JSX syntax with hono/jsx.通过 hono/jsx 用 JSX 语法编写 HTML 这是本页服务端与客户端渲染的前提通过 hono/jsx 用 JSX 语法编写 HTMLJSX 语法

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.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.hono/jsx 可用于客户端,但多用于服务端 所述的共享 JSX 特性在两个环境都适用hono/jsx 可用于客户端,但多用于服务端服务端客户端

Settings

为 hono/jsx 配置 tsconfig 或 deno.json

To use JSX, modify the tsconfig.json:To use JSX, modify the tsconfig.json:修改 tsconfig.json 以使用 JSX 它设置 JSX 支持所需的编译器选项修改 tsconfig.json 以使用 JSXtsconfig.json

tsconfig.json:

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

Alternatively, use the pragma directives:Alternatively, use the pragma directives:pragma 指令可替代 tsconfig 设置 它们按文件内联声明 jsx 与 jsxImportSourcepragma 指令可替代 tsconfig 设置pragma 指令

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

For Deno, you have to modify the deno.json instead of the tsconfig.json:For Deno, you have to modify the deno.json instead of the tsconfig.json:Deno 用户改 deno.json 而非 tsconfig.json 它使用 precompile 模式和 @hono/hono/jsxDeno 用户改 deno.json 而非 tsconfig.jsondeno.json

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

Usage

入口改为 .tsx 并用 c.html 渲染组件

Note

If you are coming straight from the Quick Start, the main file has a .ts extension - you need to change it to .tsx - otherwise you will not be able to run the application at all. You should additionally modify the package.json (or deno.json if you are using Deno) to reflect that change (e.g. instead of having bun run --hot src/index.ts in dev script, you should have bun run --hot src/index.tsx).You should additionally modify the package.json (or deno.json if you are using Deno) to reflect that change (e.g. instead of having bun run --hot src/index.ts in dev script, you should have bun run --hot src/index.tsx).主文件须改为 .tsx,否则应用无法运行 需同步更新 package.json 或 deno.json 的 dev 脚本主文件须改为 .tsx,否则应用无法运行主文件

index.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

Metadata hoisting

title、link 和 meta 标签自动提升到 head

You can write document metadata tags such as <title>, <link>, and <meta> directly inside your components. These tags will be automatically hoisted to the <head> section of the document. This is especially useful when the <head> element is rendered far from the component that determines the appropriate metadata.You can write document metadata tags such as <title>, <link>, and <meta> directly inside your components. These tags will be automatically hoisted to the <head> section of the document. This is especially useful when the <head> element is rendered far from the component that determines the appropriate metadata.title、link 和 meta 自动提升到 head 当 head 远离元数据组件时尤其有用title、link 和 meta 自动提升到 headtitle link meta

import { Hono } from 'hono'

const app = new Hono()

app.use('*', async (c, next) => {
  c.setRenderer((content) => {
    return c.html(
      <html>
        <head></head>
        <body>{content}</body>
      </html>
    )
  })
  await next()
})

app.get('/about', (c) => {
  return c.render(
    <>
      <title>About Page</title>
      <meta name='description' content='This is the about page.' />
      about page content
    </>
  )
})

export default app

Note

When hoisting occurs, existing elements are not removed. Elements appearing later are added to the end. For example, if you have <title>Default</title> in your <head> and a component renders <title>Page Title</title>, both titles will appear in the head.When hoisting occurs, existing elements are not removed. Elements appearing later are added to the end. For example, if you have <title>Default</title> in your <head> and a component renders <title>Page Title</title>, both titles will appear in the head.提升不删除已有元素,后出现的追加在后 因此默认标题与页面标题都出现在 head 中提升不删除已有元素,后出现的追加在后提升元素

Fragment

用 Fragment 或 <> 分组元素而不加额外节点

Use Fragment to group multiple elements without adding extra nodes:Use Fragment to group multiple elements without adding extra nodes:Fragment 分组多个元素且不加额外节点 从 hono/jsx 导入它来包裹子元素Fragment 分组多个元素且不加额外节点Fragment

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 sets up properly.Or you can write it with <></> if it sets up properly.配置正确时可用 <> 简写代替 Fragment 它避免显式导入 Fragment配置正确时可用 <> 简写代替 Fragment简写 Fragment

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.You can use PropsWithChildren to correctly infer a child element in a function component.PropsWithChildren 在函数组件中推断子元素 包裹 props 类型即可正确类型化 childrenPropsWithChildren 在函数组件中推断子元素PropsWithChildren

import { PropsWithChildren } from 'hono/jsx'

type Post = {
  id: number
  title: string
}

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

Inserting Raw HTML

dangerouslySetInnerHTML 直接插入原始 HTML

To directly insert HTML, use dangerouslySetInnerHTML:To directly insert HTML, use dangerouslySetInnerHTML:dangerouslySetInnerHTML 直接插入原始 HTML 传入含 __html 标记的对象dangerouslySetInnerHTML 直接插入原始 HTMLdangerouslySetInnerHTML

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:Optimize your components by memoizing computed strings using memo:memo 通过缓存计算字符串优化组件 用 hono/jsx 导入的 memo 包裹组件memo 通过缓存计算字符串优化组件memo

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 在组件树中共享数据

By using useContext, you can share data globally across any level of the Component tree without passing values through props.By using useContext, you can share data globally across any level of the Component tree without passing values through props.useContext 在组件树中共享数据 无需逐层通过 props 传递值useContext 在组件树中共享数据useContext

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

异步组件由 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.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.异步组件用 await,c.html 会自动等待 这使得组件内可进行异步数据获取异步组件用 await,c.html 会自动等待异步组件

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 Experimental

Suspense 先渲染 fallback 再显示等待内容

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().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().Suspense 先渲染 fallback 再显示等待内容 配合 renderToReadableStream 实现流式输出Suspense 先渲染 fallback 再显示等待内容Suspense

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 Experimental

ErrorBoundary 捕获子组件错误并显示 fallback

You can catch errors in child components using ErrorBoundary.You can catch errors in child components using ErrorBoundary.ErrorBoundary 捕获子组件中的错误 包裹组件以拦截抛出的错误ErrorBoundary 捕获子组件中的错误ErrorBoundary

In the example below, it will show the content specified in fallback if an error occurs.In the example below, it will show the content specified in fallback if an error occurs.发生错误时显示 fallback 内容 抛出的 Error 触发边界的 fallback 渲染发生错误时显示 fallback 内容fallback 内容

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 can also be used with async components and Suspense.ErrorBoundary can also be used with async components and Suspense.ErrorBoundary 也可用于异步组件和 Suspense 它包裹 Suspense 以捕获异步失败ErrorBoundary 也可用于异步组件和 SuspenseErrorBoundary

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>
  )
})

StreamingContext Experimental

StreamingContext 为生成的 script 标签添加 scriptNonce

You can use StreamingContext to provide configuration for streaming components like Suspense and ErrorBoundary. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).You can use StreamingContext to provide configuration for streaming components like Suspense and ErrorBoundary. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).StreamingContext 为流式组件配置 scriptNonce 以支持 CSP 它为 Suspense 和 ErrorBoundary 生成的脚本添加 nonceStreamingContext 为流式组件配置 scriptNonce 以支持 CSPStreamingContext

import { Suspense, StreamingContext } from 'hono/jsx/streaming'

// ...

app.get('/', (c) => {
  const stream = renderToReadableStream(
    <html>
      <body>
        <StreamingContext
          value={{ scriptNonce: 'random-nonce-value' }}
        >
          <Suspense fallback={<div>Loading...</div>}>
            <AsyncComponent />
          </Suspense>
        </StreamingContext>
      </body>
    </html>
  )

  return c.body(stream, {
    headers: {
      'Content-Type': 'text/html; charset=UTF-8',
      'Transfer-Encoding': 'chunked',
      'Content-Security-Policy':
        "script-src 'nonce-random-nonce-value'",
    },
  })
})

The scriptNonce value will be automatically added to any <script> tags generated by Suspense and ErrorBoundary components.The scriptNonce value will be automatically added to any <script> tags generated by Suspense and ErrorBoundary components.scriptNonce 自动添加到生成的 script 标签 Suspense 与 ErrorBoundary 的脚本继承配置的 noncescriptNonce 自动添加到生成的 script 标签scriptNonce

Integration with html Middleware

组合 JSX 与 html 中间件实现强大模板

Combine the JSX and HTML middlewares for powerful templating. For in-depth details, consult the HTML middleware documentation.Combine the JSX and HTML middlewares for powerful templating. For in-depth details, consult the HTML middleware documentation.组合 JSX 与 HTML 中间件实现强大模板 深入细节参见 HTML 中间件文档组合 JSX 与 HTML 中间件实现强大模板HTML 中间件

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

With JSX Renderer Middleware

JSX Renderer Middleware 让创建 HTML 页面更简单

The JSX Renderer Middleware allows you to create HTML pages more easily with the JSX.The JSX Renderer Middleware allows you to create HTML pages more easily with the JSX.JSX Renderer Middleware 让创建 HTML 页面更简单 它将 JSX 渲染封装为可复用中间件JSX Renderer Middleware 让创建 HTML 页面更简单JSX Renderer Middleware

Override type definitions

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

You can override the type definition to add your custom elements and attributes.You can override the type definition to add your custom elements and attributes.覆盖类型定义以添加自定义元素和属性 声明 hono/jsx 模块并扩展 JSX IntrinsicElements覆盖类型定义以添加自定义元素和属性类型定义

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