hyperappで大きめなアプリをTypeScriptで書くには？


こんな感じにすれば良さそう


```ts
import { h, app, ActionsType, View } from "hyperapp";

namespace Counter {
  export interface State {
    count: number;
  }

  export interface Actions {
    down(): State;
    up(): State;
  }

  export const state: State = {
    count: 0
  };

  export const actions: ActionsType<State, Actions> = {
    down: () => state => {
      if (state.count > 0) {
        return { count: state.count - 1 };
      }
      return state;
    },
    up: () => state => ({
      count: state.count + 1
    })
  };
  export const view: View<State, Actions> = (state, actions) => (
    <div>
      <div>{state.count}</div>
      <button onclick={actions.down}>-</button>
      <button onclick={actions.up}>+</button>
    </div>
  );
}

namespace CounterTwice {
  export interface State extends Counter.State {}
  export interface Actions extends Counter.Actions {}
  export const state: State = Counter.state;
  export const actions: ActionsType<State, Actions> = {
    down: () => state => {
      if (state.count > 0) {
        return { count: state.count - 2 };
      }
      return state;
    },
    up: () => state => ({
      count: state.count + 2
    })
  };
  export const view = Counter.view;
}

namespace MultiCounter {
  export interface State {
    c1: Counter.State;
    c2: CounterTwice.State;
    c3: Counter.State;
  }
  export interface Actions {
    c1: Counter.Actions;
    c2: CounterTwice.Actions;
    c3: Counter.Actions;
    alldown(): void;
    allup(): void;
  }

  export const state: State = {
    c1: Counter.state,
    c2: CounterTwice.state,
    c3: Counter.state
  };

  export const actions: ActionsType<State, Actions> = {
    c1: Counter.actions,
    c2: CounterTwice.actions,
    c3: Counter.actions,
    allup: () => (state, actions) => {
      actions.c1.up();
      actions.c2.up();
      actions.c3.up();
      return;
    },
    alldown: () => (state, actions) => {
      actions.c1.down();
      actions.c2.down();
      actions.c3.down();
      return;
    }
  };
  export const view: View<MultiCounter.State, MultiCounter.Actions> = (
    state,
    actions
  ) => (
    <div>
      {Counter.view(state.c1, actions.c1)}
      {CounterTwice.view(state.c2, actions.c2)}
      {Counter.view(state.c3, actions.c3)}
      <button onclick={actions.alldown}>-</button>
      <button onclick={actions.allup}>+</button>
    </div>
  );
}

const main = app<MultiCounter.State, MultiCounter.Actions>(
  MultiCounter.state,
  MultiCounter.actions,
  MultiCounter.view,
  document.body
);

// hot reload対策
if ((window as any).isSet === undefined) {
  setInterval(main.c3.up, 250, 1);
  setInterval(main.c3.down, 500, 1);
}
(window as any).isSet = true;

```

ハマリポイントは`` allup: () => (state, actions)`` のところくらいです。
引数に``actions``を省略すると補完が効かなくなり悩みました。


# routerもTypeScriptから使いたい

大きめのアプリを書くにはrouterも必要そうですよね。

現状[本家のrouter](https://github.com/hyperapp/router)の型情報がないみたいなので作りました

``mytypes/@hyperapp/router.d.ts``に配置して下さい。


```mytypes/@hyperapp/router.d.ts

import { VNode } from "hyperapp";

/** Link */
interface LinkProps {
  to: string;
  location?: Location;
}
export function Link(props: LinkProps): VNode<LinkProps>;

/** Route */
interface Match<P> {
  url: string;
  path: string;
  isExact: boolean;
  params: P;
}
interface RenderProps<P> {
  location: Location;
  match: Match<P>;
}

interface RouteProps<P> {
  parent?: boolean;
  path: string;
  location?: Location;
  render: (props: RenderProps<P>) => VNode<RenderProps<P>>;
}

export function Route<P>(
  props: RouteProps<P>
): VNode<RenderProps<P>> | undefined;

/**Switch */
export function Switch<P>(
  props: object,
  children: Array<VNode<Match<P>>>
): VNode<object>;

/** Redirect */
type RedirectProps = LinkProps;
export function Redirect(props: RedirectProps): VNode<RedirectProps>;

/** location */
interface LocationState {
  pathname: string;
  previous: string;
}

interface LocationActions {
  go: (pathname: string) => void;
  set: (data: LocationState) => LocationState;
}
interface RouterLocation {
  state: LocationState;
  actions: LocationActions;
  subscribe: (actions: LocationActions) => Function;
}

export declare const location: RouterLocation;


```

tsconfigに自作の型情報を読むための設定を追加します

```
{
  "compilerOptions": {
    "strict": true,
    "jsx": "react",
    "jsxFactory": "h",
    "baseUrl": ".",
    "sourceMap": true,
    "paths": {
      "*": ["mytypes/*", "node_modules/@types/*", "*"]
    }
  }
}

```

parcelから[typescript + hyperapp](https://qiita.com/m0a/items/e0aaa9583375e2191c3f)が使えますが、
``"sourceMap": true,``にすることで、TypeScriptデバッグできるようにもなっているみたいです。

## routerの書き方

react-routerとおんなじ感じです

sample-code

```ts
import { h, app, View, ActionsType } from "hyperapp";
import {
  Link,
  Route,
  RenderProps,
  location,
  LocationActions,
  LocationState
} from "@hyperapp/router";

const Home = () => <h2>Home</h2>;
const About = () => <h2>About</h2>;
const Topic = ({ match }: RenderProps<{ topicId: string }>) => (
  <h3>{match.params.topicId}</h3>
);
const TopicsView = ({ match }: RenderProps<{ topicId: string }>) => (
  <div>
    <h2>Topics</h2>
    <ul>
      <li>
        <Link to={`${match.url}/components`}>Components</Link>
      </li>
      <li>
        <Link to={`${match.url}/single-state-tree`}>Single State Tree</Link>
      </li>
      <li>
        <Link to={`${match.url}/routing`}>Routing</Link>
      </li>
    </ul>

    {match.isExact && <h3>Please select a topic.</h3>}

    <Route parent path={`${match.path}/:topicId`} render={Topic} />
  </div>
);

interface RouteState {
  location: LocationState;
}
const state: RouteState = {
  location: location.state
};

interface RouteActions {
  location: LocationActions;
}

const routeActions: ActionsType<RouteState, RouteActions> = {
  location: location.actions
};
const view: View<RouteState, RouteActions> = (state: RouteState) => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/about">About</Link>
      </li>
      <li>
        <Link to="/topics">Topics</Link>
      </li>
    </ul>

    <hr />

    <Route path="/" render={Home} />
    <Route path="/about" render={About} />
    <Route parent path="/topics" render={TopicsView} />
  </div>
);

const main = app(state, routeActions, view, document.body);

const unsubscribe = location.subscribe(main.location);

```

# 最後に

hyperappはREADMEだけで使いかたが完結していて凄く使いやすそうです。
[READMEを翻訳してみたので参照してみて下さい](https://taiyaq.com/contents/PGg8jzP12zX8JtE7LALAdzvOAJ)