Skip to content

클라이언트 컴포넌트

hono/jsx는 서버 사이드뿐만 아니라 클라이언트 사이드도 지원한다. 즉, 브라우저에서 실행되는 인터랙티브 UI를 만들 수 있다. 이를 클라이언트 컴포넌트 또는 hono/jsx/dom이라고 부른다.

이 방식은 빠르고 매우 가볍다. hono/jsx/dom에서 구현한 카운터 프로그램은 Brotli 압축 시 단 2.8KB에 불과하다. 반면 React는 47.8KB를 차지한다.

이번 섹션에서는 클라이언트 컴포넌트만의 특징을 소개한다.

카운터 예제

React와 동일하게 동작하는 간단한 카운터 예제를 살펴보자.

tsx
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() 함수를 사용하면 특정 HTML 엘리먼트 안에 JSX 컴포넌트를 삽입할 수 있다.

tsx
render(<Component />, container)

React와 호환 가능한 Hooks

hono/jsx/dom은 React와 호환되거나 부분적으로 호환되는 Hooks를 제공한다. React 공식 문서를 참고해 이 API들에 대해 더 자세히 알아볼 수 있다.

  • 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() 관련 함수는 View Transitions API를 쉽게 다룰 수 있도록 설계된 원본 훅과 함수를 포함한다. 다음은 이들을 사용하는 예제다.

1. 가장 간단한 예제

document.startViewTransitionstartViewTransition()으로 간단히 사용해 트랜지션을 구현할 수 있다.

tsx
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. keyframes()와 함께 viewTransition() 사용하기

viewTransition() 함수를 사용하면 고유한 view-transition-name을 얻을 수 있다. 이 함수를 keyframes()와 함께 사용하면 ::view-transition-old()::view-transition-old(${uniqueName})으로 변환된다.

tsx
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()을 사용할 수 있다. 이 훅은 [boolean, (callback: () => void) => void]를 반환하며, 각각 isUpdating 플래그와 startViewTransition() 함수다.

이 훅을 사용하면 컴포넌트가 다음 두 시점에 평가된다.

tsx
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을 수정한다.

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

Released under the MIT License.