Component TypesとEvent Handlers

Aug 12, 2022

7 mins read

背景

SolidJSのコンポーネントとイベントハンドラの型定義について悩んでいたが、公式で解説が乗っていたので、
個人的にまとめることにする。


概要

コンポーネントとイベントハンドラの型定義の方法をまとめていく。
公式ではコンポーネントの型をComponent Types、エベントハンドラの型をEvent Handlersとして記載しているので、 同一の単語を使用していく。


本題

Component Types

Component

import type { JSX, Component } from 'solid-js';
type Component<P = {}> = (props: P) => JSX.Element;

基本的にはReactのFCと同じような使い型ができ、propsの型をジェネリクスで定義することができる。
戻り値はJSX.Elementになる。

サンプルコード ※公式のコードを参照

import { render } from "solid-js/web";
import { createSignal, Component } from "solid-js";

const Counter: Component = () => {
    const [count, setCount] = createSignal(0);
    return (
        <button onClick={() => setCount((c) => c+1)}>
            {count()}
        </button>
    );
};

const InitCounter: Component<{initial: number}> = (props) => {
    const [count, setCount] = createSignal(props.initial);
    return (
        <button onClick={() => setCount((c) => c+1)}>
            {count()}
        </button>
    );
};

function App() {
    return (
        <>
            <InitCounter initial={5}/>  // good
            <Counter/>;                 // good
            <Counter initial={5}/>;     // type error: no initial prop
            <Counter>hi</Counter>       // type error: no children prop
        </>

    );
}

render(() => <App />, document.getElementById("app")!);

ParentComponent

コンポーネントにchildren(子要素)を追加したい場合は、Propsに明示的に追加するか、
自動的にchildrenを追加されるParentComponentを使用する。 ParentPropschildrenにオプショナルがついているため、省略することもできる。 また、ジェネリクスを使用してpropsの型定義も使用できる。

import { JSX, ParentComponent, ParentProps } from 'solid-js';
type ParentProps<P = {}> = P & { children?: JSX.Element };
type ParentComponent<P = {}> = Component<ParentProps<P>>;

サンプルコード

import { render } from "solid-js/web";
import { createSignal, ParentComponent } from "solid-js";

const CustomInitCounter: ParentComponent<{initial: number}> = (props) => {
    const [count, setCount] = createSignal(props.initial);
    return (
        <button onClick={() => setCount((c) => c+1)}>
            {count()}
            {props.children}
        </button>
    );
};

function App() {
    return (
        <>
            <CustomInitCounter initial={5}>
                <p>childrenだよ</p>
            </CustomInitCounter>
        </>

    );
}

render(() => <App />, document.getElementById("app")!);

VoidComponent

childrenを使用しない時に使用する型。
Componentchildrenを使用しない型と同等の意味。

import {JSX, VoidComponent, VoidProps} from 'solid-js';
type VoidProps<P = {}> = P & { children?: never };
type VoidComponent<P = {}> = Component<VoidProps<P>>;

サンプルコード

import { render } from "solid-js/web";
import { createSignal, VoidComponent } from "solid-js";

const CustomInitCounter: VoidComponent<{initial: number}> = (props) => {
  const [count, setCount] = createSignal(props.initial);
  return (
    <button onClick={() => setCount((c) => c+1)}>
      {count()}
    </button>
  );
};

function App() {
  return (
    <>
        <CustomInitCounter initial={5} />
    </>

  );
}

render(() => <App />, document.getElementById("app")!);

FlowComponent

import {JSX, FlowComponent, FlowProps} from 'solid-js';
type FlowProps<P = {}, C = JSX.Element> = P & { children: C };
type FlowComponent<P = {}, C = JSX.Element> = Component<FlowProps<P, C>>;

<Show> <For>の様な制御フローを使用するコンポーネントを使用している。
childrenは必須となり、childrenが関数である場合、関数の型など定義することができる。

サンプルコード

import { render } from "solid-js/web";
import { FlowComponent, createEffect } from "solid-js";

const CallMeMaybe: FlowComponent<{when: boolean}, () => void> = (props) => {
  createEffect(() => {
    if (props.when)
      props.children();
  });
  return <>{props.when ? 'Calling' : 'Not Calling'}</>;
};


function App() {
  return (
    <>
      // <CallMeMaybe when={true}/>;  // type error: missing children
      // <CallMeMaybe when={true}>hi</CallMeMaybe>;  // type error: children
      <CallMeMaybe when={true}>                   // good
        {() => console.log("Here's my number")}
      </CallMeMaybe>;  
    </>
  );
}

render(() => <App />, document.getElementById("app")!);


Event Handlers

イベントハンドラの型を定義できる。JSX.EventHandler<T, E>で定義し、TはDOMの要素型でEイベント型として使用する。

サンプルコード

import type { JSX } from 'solid-js';
const onInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (event) => {
  console.log('input changed to', event.currentTarget.value);
};

<input onInput={onInput}/>

また、関数に切り出さないで直接要素に記載した場合は、適切に型を判断してくれる。

<input onInput={(event) => {
    console.log('input changed to', event.currentTarget.value);
    }}
/>;

エディターによる型推論

まとめ

コンポーネントの型の種類が、Reactよりは多くなっているが、リアクティブなコンポーネントを作るために、必要なものなのかなと感じた。
適切に使用しつつ、コンポーネントの見通しもよくしていきたい。


参考文献

Component Types
Event Handlers