LoginSignup
13
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-11

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

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の型情報がないみたいなので作りました

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が使えますが、
"sourceMap": true,にすることで、TypeScriptデバッグできるようにもなっているみたいです。

routerの書き方

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

sample-code

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を翻訳してみたので参照してみて下さい

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10