Hello React Hooks

React からだいぶ離れている間に、16.8 で Hooks が出て、React を使った Web アプリ開発パラダイムがかなり変わったようだ。Hooks 以前と以後に分かれるのでは、と思うくらいに。久しぶりに仕事で React を触ることになりそうなので、Hooks は試しておかなければ。簡単な Todo リストのサンプルを書いてみることにした。

プロジェクトは create-react-app で新規作成。create-react-app は TypeScript をサポートしているので、ちゃんとした Web アプリを React で開発するなら、もはや言語は TypeScript 一択だろう。

npx create-react-app react-todo --template typescript

Hooks は useState だけを使ってみた。App.tsx は次の通り。Todo リストを保持するグローバルなステートを App に持たせ、TodoForm にローカルなステートとして編集内容を持たせている。

import React, { useState } from 'react';

interface Todo {
  id?: number;
  name: string;
  completed: boolean;
  editing: boolean;
}

interface TodoFormProps {
  todo: Todo;
  onSave: (todo: Todo) => void;
}

const TodoForm: React.FC<TodoFormProps> = ({
  todo,
  onSave
}) => {
  const [name, setName] = useState(todo.name);
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      onSave({
        ...todo,
        name,
        editing: false,
      });
      setName("");
    }}>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}/>
      <button type="submit">
        Save
      </button>
      {
        todo.id &&
        <button onClick={(e) => {
          e.preventDefault();
          onSave({
            ...todo,
            editing: false,
          });
        }}>
          Cancel
        </button>
      }
    </form>
  );
};

interface TodoItemProps {
  todo: Todo;
  onEdit: (todo: Todo) => void;
  onDelete: (todo: Todo) => void;
  onComplete: (todo: Todo) => void;
}

const TodoItem: React.FC<TodoItemProps> = ({
  todo,
  onComplete,
  onEdit,
  onDelete
}) => (
  <div>
    <input
      type="checkbox"
      checked={todo.completed}
      onChange={() => onComplete(todo)}
      />
    {
      todo.completed ?
      <del>{todo.name}</del> :
      <>{todo.name}</>
    }
    <button onClick={() => onEdit(todo)}>
      Edit
    </button>
    <button onClick={() => onDelete(todo)}>
      Del
    </button>
  </div>
);

interface TodoListProps {
  todoList: Todo[];
  onEdit: (todo: Todo) => void;
  onDelete: (todo: Todo) => void;
  onUpdate: (todo: Todo) => void;
  onComplete: (todo: Todo) => void;
}

const TodoList: React.FC<TodoListProps> = ({
  todoList,
  onEdit,
  onComplete,
  onUpdate,
  onDelete,
}) => (
  <div>
    {todoList.map(todo => (
      todo.editing ?
        <TodoForm
          todo={todo}
          onSave={onUpdate}/> :
        <TodoItem
          todo={todo}
          onEdit={onEdit}
          onComplete={onComplete}
          onDelete={onDelete}
        /> 
    ))}
  </div>
);

const App: React.FC = () => {
  const [todoList, setTodoList] = useState<Todo[]>([
    { id: 1, name: "foo", completed: false, editing: false },
    { id: 2, name: "bar", completed: false, editing: false },
  ]);
  const [nextId, setNextId] = useState<number>(3);
  return (
    <div className="App">
      <h1>React Todo</h1>
      <TodoForm
        todo={{name:"", completed: false, editing: false}}
        onSave={(todo) => {
          setTodoList([...todoList, { ...todo, id: nextId }]);
          setNextId(nextId + 1);
        }}
        />
      <TodoList 
        todoList={todoList}
        onDelete={(todo) => {
          setTodoList(todoList.filter(x => x.id !== todo.id));
        }}
        onEdit={(todo) => {
          setTodoList(todoList.map(x => {
            if (x.id === todo.id) {
              return {
                ...x,
                editing: true,
              };
            } else {
              return {
                ...x,
                editing: false,
              };
            }
          }));
        }}
        onUpdate={(todo) => {
          setTodoList(todoList.map(x => {
            if (x.id === todo.id) {
              return todo;
            } else {
              return {
                ...x,
                editing: false,
              };
            }
          }));
        }}
        onComplete={(todo) => {
          setTodoList(todoList.map(x => {
            if (x === todo) {
              return {
                ...x,
                completed: !x.completed
              };
            } else {
              return x;
            }
          }));
        }}
        />
    </div>
  );
}

export default App;

yarn start で実行して動作確認。その場編集モードもうまく動いてくれた。 余談だけど、スクリーンを録画したアニメーション GIF を作るのに ScreenToGif というツールを使ってみたら、かなり簡単に作れてすこぶる良かった。アニメーション GIF があると分かりやすさが段違いなので、積極的に活用していきたい。

f:id:griefworker:20191211120254g:plain

今回は useState だけ試したけど、他にも useEffect や useContext や useReducer など、色んなフックがある。さらにはカスタムフックまで。「React Hooks 完全に理解した」って言えるまで、まだまだ遠いな。

まんかい 天神親不孝通り店

天神親不孝通りにオープンしていた、大阪に本店がある豚骨ラーメン店『まんかい』に行ってみた。大阪に本店があるのに博多純系豚骨とは、逆輸入みたいだな。ラーメンウォーカー 2019 にも掲載されていて、新進気鋭といったところかな。

ラーメンは純とんを注文。豚骨特有の臭みがなく、豚骨に慣れていない人でも食べやすいオーソドックスな一杯だった。一風堂の系譜に近い。それもそのはずで、創業者は一風堂で修行したらしい。チャーシューはかなりのボリュームで食べごたえあったけど、脂身の主張が気になった。もっとトロッとするまで煮込んで欲しい。

プラス 300 円でチャーシューごはんのセットにできたので、こちらも注文。しっかり味付けされたチャーシューとご飯の相性は言うまでもない。上にたっぷり載せてあるネギが口の中をさっぱりさせてくれるので、チャーシューの味の濃さとバランスが取れていて良かった。ただ、ここでも脂身の主張が気になったかな。

チャーシューの脂身に関しては完全に好みの問題。自分は脂身苦手なので…。気にならない人は全く問題ないと思う。

関連ランキング:ラーメン | 天神駅赤坂駅西鉄福岡駅(天神)

月光軒

ラーメンウォーカー 2019 を読んでいたら、川端商店街に新しい中華そばの店がオープンしていたことを知った。その名も『月光軒』。「ムンライケン」と読むみたいだ。早速行ってみたけど、この場所、金菜亭があった場所じゃないか。いつの間に変わったのか。

中華そばは醤油と塩、あと煮干そばもあった。どれにするか迷うな。つけそばも捨てがたい。今回は最初なので、看板メニューにしておこう。というわけで中華そばの醤油を注文した。

麺は多加水麺でツルツルかつプリっとしていた。初めての食感かも。豚と鶏のチャーシューはどちらも低温調理してあるのか、柔らかくてジューシー。そしてスープは鶏の旨味が溢れていて、ずっと飲んでいたいくらいだった。あと、うずらの玉子とトマトが入っているのは斬新で面白い。

期待以上のクオリティで、自分の中の醤油ラーメンランキングでトップ 5 に入った。はや川や KOMUGI、寿限無に勝るとも劣らない。はや川は味噌ラーメンにシフトしたしね…。会社の昼休みに行ける距離にあるので、醤油ラーメンが食べたくなったときの候補の筆頭になりそうだ。ひとまず、中華そばの塩と煮干そばとつけそばは食べなければ。

関連ランキング:ラーメン | 中洲川端駅呉服町駅祇園駅

Xamarin で秘密情報を管理するいくつかあるうちひとつの冴えたやりかた

この記事は、Xamarin Advent Calendar 2019 の三日目の記事です。

qiita.com

OAuth で使う ClientId と ClientSecret の管理で悩み中。 ハードコードしてはダメだし、Git リポジトリにコミットするのもダメ。

Xamarin.Android なら AndroidManifest.xml placeholders をサポートしているようなので、 この機能を使えば良いだろう。

github.com

ただ、Xamarin.iOS というか Xamarin. Forms ではどうしたものか。 2019 年も終わり間近だというのに、ベストプラクティスが見当たらない。

これが React + create-react-app での開発なら、 .env ファイルに REACT_APP_KEY=VALUE 形式で秘密情報を記述しておけば、 コードから process.env.REACT_APP_KEY で参照できるんだけどな。 同じようなことが Xamarin でもやりたい。 例えば、秘密情報を JSON ファイルに記述したら、 obj 下に自動的にコードが生成されたらいいんじゃないだろうか。

.NET Core 3.0 で gRPC が正式サポートされ、 Visual Studio で gRPC サービスプロジェクトの .proto ファイルを編集したら、 obj 下にコードが生成されるようになった。 それと同じような仕組みで実現できるかも、 と思って方法を調べていたら、 やろうとしていたことズバリそのものな NuGet パッケージを見つけてしまった。

github.com

NuGet で Mobile.BuildTools をインストールし、 プロジェクトのルートに secrets.json を作成。 この secrets.json に、例えば次のような秘密情報を記述したとする。

{
    "ClientId": "{Your ClientId HERE}",
    "ClientSecret": "{Your ClientSecret HERE}"
}

すると、{プロジェクトのルート}.Helpers 名前空間に次のような Secrets クラスが生成される。

// ------------------------------------------------------------------------------
//  <autogenerated>
//      This code was generated by Mobile.BuildTools. For more information or to
//      file an issue please see https://github.com/dansiegel/Mobile.BuildTools
//
//      Changes to this file may cause incorrect behavior and will be lost when 
//      the code is regenerated. 
//
//      NOTE: This file should be excluded from source control.
//  </autogenerated>
// ------------------------------------------------------------------------------

namespace SecretsSample.Helpers
{
    internal static class Secrets
    {
        internal const string ClientId = "{Your ClientId HERE}";

        internal const string ClientSecret = "{Your ClientSecret HERE}";
    }
}

あとは秘密情報が必要な箇所で、この Secrets クラスを使えばいい。 secrets.json と Secrets.cs は誤ってリポジトリにコミットしないよう、.gitignore に追加しておく。 なんだ、これでいいじゃん。

ちなみに、Mobile.BuildTools の Wikiデモアプリによると App Center にも対応しているみたいで、秘密情報を App Center の環境変数に設定しておいて、ビルド時に組み込むことができそうだ。ただ、App Center は使ってないので試していないけど。

コードファースト ASP.NET Core gRPC

この記事は、C# その2 Advent Calendar 2019 の二日目の記事です

qiita.com

はじめに

ASP.NET Core 3.0 で gRPC がサポートされた。 一方で WCF .NET Core に正式には移植されず、コミュニティによる開発にシフト。 業務では WCF をバリバリ使っているけど、gRPC への移行を検討しなければいけない時期になってきた。 2020 年には .NET 5 も来るし。

コードファーストで開発したい

Visual Studio で proto ファイルを編集したら保存時に C# のコードが生成されるようになったとはいえ、できれば proto ファイル書きたくない。

WCF では C# でデータコントラクトとサービスコントラクトのインタフェースを定義したら、 それをクライアントとサーバーの両方で利用できていた。 gRPC でも同じような体験が理想だ。

protobuf-net.Grpc

WCF から gRPC への移行方法を調べていたら、protobuf-net.Grpc というパッケージに出会った。

github.com

README には次のように書いてある。

protobuf-net.Grpc adds code-first support for services over gRPC using either the native Grpc.Core API, or the fully-managed Grpc.Net.Client / Grpc.AspNetCore.Server API.

WCF のときみたいに、コードファーストで gRPC サービスを開発できそうなシロモノに見える。

コードファースト ASP.NET Core gRPC をやってみる

.NET Core ライブラリ作成

クライアントとサーバーで共通のインタフェースを使うために、 クライアントとサーバーの両方で参照する .NET Core ライブラリプロジェクトを作成する。 名前は『HelloGrpc.Shared』。

NuGet パッケージ追加

プロジェクトには次のパッケージを追加しておく。

データコントラクトとサービスコントラクトを定義

この辺とても WCF っぽい。 引数が 1 つだけなのは protobuf-net.Grpc の制約。 gRPC でも 1 つなのは同じだし。 あと、DataMemberAttribute で Order を指定するのも必須。 .proto ファイルでもフィールドにタグ値を指定するけど、その代わりになる。

using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading.Tasks;

namespace HelloGrpc.Shared
{
    [ServiceContract]
    public interface IGreetingService
    {
        [OperationContract]
        Task<HelloReply> HelloAsync(HelloRequest request);
    }

    [DataContract]
    public class HelloRequest
    {
        [DataMember(Order = 1)]
        public string Name { get; set; }
    }

    [DataContract]
    public class HelloReply
    {
        [DataMember(Order = 1)]
        public string Message { get; set; }
    }
}
gRPC サービス作成

Visual Studio から gRPC サービスのテンプレートを選択してプロジェクトを作成。 名前は『HelloGrpc.Server』。

NuGet パッケージ追加

ASP.NET Core で gRPC サービスを実装するのに必要なパッケージはあらかた参照してあるけど、 下記のパッケージも参照に追加する。

gRPC サービス実装

services.AddCodeFirstGrpc() でコードファーストを有効にし、 endpoints.MapGrpcService<T>() で実際にコードで定義したサービスを登録するだけ。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ProtoBuf.Grpc.Server;

namespace HelloGrpc.Server
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCodeFirstGrpc();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<GreetingService>();
            });
        }
    }

    public class GreetingService : Shared.IGreetingService
    {
        public Task<Shared.HelloReply> HelloAsync(Shared.HelloRequest request)
        {
            return Task.FromResult(new Shared.HelloReply
            {
                Message = $"Hello {request.Name}"
            });
        }
    }
}
.NET Core コンソールアプリケーション作成

gRPC サービスを呼び出すクライアントは、.NET Core コンソールアプリケーションで作成する。 プロジェクト名は『HelloGrpc.Client』で。

NuGet パッケージ追加

HelloGrpc.Client プロジェクトに次のパッケージを追加。

  • Grpc.Net.Client
    • .NET 用 gRPC クライアント
  • protobut-net.Grpc
    • C# コードで定義した gRPC サービスのインタフェースをもとに動的にクライアントを生成してくれるパッケージ
gRPC クライアントを実装

protobut-net.Grpc が提供する GrpcClientFactory クラスが、インタフェースから動的に gRPC クライアントを生成する拡張メソッドを提供しているので、それを使う。

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using HelloGrpc.Shared;
using ProtoBuf.Grpc.Client;

namespace HelloGrpc.Client
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var client = channel.CreateGrpcService<IGreetingService>(); // インタフェースから動的に gRPC クライアントを生成
            var reply = await client.HelloAsync(new HelloRequest
            {
                Name = "Kubo"
            });
            Console.WriteLine(reply.Message);
            Console.ReadLine();
        }
    }
}
実行

コードファーストで作成した gRPC サービスをホストし、クライアントから呼び出すことに成功。

f:id:griefworker:20191016164249p:plain

まとめ

WCF から gRPC に移行する手段として、proto ファイルを書くのではなく、protobuf-net.Grpc を使いコードファーストで開発するのは良い線いってそうだ。さらに検証が必要だけど、希望が見えてきた。

なお、コードファーストで開発する別の選択肢としては MagicOnion もある。

github.com

既に大量の WCF 資産があり、MagicOnion だとそれらに結構な修正が必要そうだったので、 今回は選ばなかった。 ベンチマークを取ったわけではないけど、シリアライザに MessagePack を使っているので、 protobuf-net.Grpc を使った場合よりも速くなりそうな期待がある。 新規にコードファーストで開発するなら、MagicOnion という選択肢は有力だと思う。

五等分の花嫁(12)

五つ子がそれぞれ自分の道を歩み始めたなか、学園祭に突入。風太郎は学園祭で五つ子との関係に答えを出すと宣言したが、果たして誰を選ぶのか。それともまだ誰も選ばないのか。

一花は修学旅行でやらかしてしまったから、やはり分が悪そうに思える。それでも風太郎に対して「私だった?」「嬉しかった?」と尋ねたときの表情は小悪魔で、もう一花でいいんじゃね?って思わせるくらいの魅力があった。

ニ乃はツンデレだったころの面影はもはや無いけど、五つ子の中で一番家族思いなところは変わってない。そんな彼女が父親に対し振り絞った勇気が報われたのは、本当よかったなぁってホロリとなった。

五等分の花嫁(12) (講談社コミックス)

五等分の花嫁(12) (講談社コミックス)

海の中道海浜公園

秋も深まって絶好のレジャー日和だったので、海の中道海浜公園まで足を延ばしてきた。前回マリンワールドに行った際に、ついでに寄るつもりだったけど思いのほか暑くて断念したので、今回はいわばリベンジだ。公園とあるけど、国営公園で有料。

uminaka-park.jp

今回はマリンワールド側の駐車場から入れる、子供向けの遊具がある『子供の広場』を主戦場にした。ゲート入ってすぐにある『くじらぐも"ふわんポリン"』に次々と子供たちが吸い寄せられていく。遊べるのは小学生までで、大人は指をくわえて見ていることしかできない。残念。飛び跳ねてみたい。子供の付き添いという名目ではダメ?ダメかぁ。

ゲート入ってすぐにフードコートがあるので、弁当を持ってくるのを忘れても飢えずに済む。ただ、レトルト食品を食べてるみたいで、お世辞にも美味しいとは言えないな。その中でも佐世保バーガーの屋台は比較的マシだったけど。弁当は持参したほうが良いと思う。次からは外で買ってこよう。

ふわんぽりんは小さいヤツもあって、こっちは大人が付き添いで一緒に跳んでいた。あいにく娘はふわんぽりんの気分ではなかったので、またしても一緒に跳ぶことは叶わず。

『ローラーすべり台と海の生き物遊具』では、結構角度が急なウツボのトンネルに娘がチャレンジしていた。降りるとき多少の手助けは必要だったけど、なんとか出口に到達できるようになっていて、成長を実感。あとは、ローラー滑り台の上のほうにある『子供のとりで』が凄く気に入ったみたいで、主にかくれんぼをしながら長時間遊んでいた。この場所だけでどれくらいいただろうか。1時間どころじゃなかったと思う。

『スカイドルフィン』という大型の遊具にも連れて行きたかったけど、設置されている大芝生広場は子供の広場から結構距離があって、子供を連れて歩いて行くのは難しそうだった。とにかく広大で、園内の移動に有料のバスが運行しているくらい。自転車のレンタルができるので、娘が自転車に乗れるようになったら、家族でサイクリングすると気持ち良いかもな。幼稚園児だと遊園地に行っても乗れるものがあまり無いので、それよりは海の中道海浜公園のほうが楽しめそうだ。