スパイスファクトリー株式会社でエンジニアとして働いている平川です。
現在は主に TypeScript, React, PHP 等を用いてWebアプリやモバイルアプリの開発を担当しています。
弊社の詳しいサービスはシステム開発サービスをご覧ください。

最近 Flutter(新しいタブで開きます) を数カ月間利用してモバイルアプリ開発を行う機会がありました。Flutterを扱うのは初めてでしたが、私のようなReact(新しいタブで開きます) 経験者にとって Flutter は習得しやすいと感じました。

その理由を考察してみたいと思います。

弊社スパイスファクトリーでは様々な開発において豊富な実績があります。

弊社の概要やサービスプラン、過去の導入実績などをまとめた資料をご用意しました。気になる方はこちらからダウンロードしてください。

Flutterとは

Flutter とは、近年注目を集めている Google 製クロスプラットフォームUI開発ツールキットです。
公式サイト(新しいタブで開きます)には下記のような記載があります。

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase.

Google翻訳

Flutter は、単一のコードベースからモバイル、ウェブ、デスクトップ、組み込みデバイス向けにネイティブにコンパイルされた美しいアプリケーションを構築するためのGoogleのUIツールキットです。

コードを1つ書けば、AndroidとiOS、更にwebアプリもFlutterが作ってくれるという事なので、とても簡単そうです。

実際、Webアプリと全く同じ感覚でアプリを作る事が可能です。

$ flutter create myapp
$ cd myapp
$ flutter run

これだけでエミューレーター又はUSBで接続した物理端末でアプリが立ち上がります。(下の画像。2021年7月現在)

アプリイメージ

更にソースコードを編集した際にはホットリロードによって変更が即座に反映できるなど、Web開発に近い、良好な開発体験です。

もちろん実際には事前に Flutter 本体と最低でも「Android Studio」をインストールする必要がありますが、一度セットアップしてしまえば上記のように手軽に開発を始めることができます。未体験の方は公式ドキュメント(新しいタブで開きます)を参照して Android Studio と Flutter をインストールし、ひとまず試してみるのも良いでしょう。

ちなみに、Android StudioやXcodeはビルド時にSDKが必須であるだけで付属のIDE自体は必ずしも使用する必要はありません。
好みに応じて使い慣れたエディタ(但しLSP (Language Server Protocol)対応は必須)とFlutter CLIだけで快適に開発できるのもWebエンジニアとして持っている経験を大きく活用できる点で嬉しいところです。

Flutter習得をしてみて

個人的な感想になりますが、私は Flutter の習得は比較的スムーズに進めることができました。
理由は当時私が React をメインに開発していて、かつ Flutter のUIコードが React と似ていたことだと思っています。

このあとは、 Flutter と React が似ていると思ったポイントを考察していきたいと思います。

具体的には、以下の点について触れていきます。

  • Flutter のUI表現構造が JSX と似ている。そもそも HTML とも似ている
  • Dart と JavaScript (ES6以降) の文法が似ている
  • 「状態管理」や「イベント・ドリブン」など重要な概念が共通している

FlutterとReactの類似点

先述した Flutter と React の類似点についてそれぞれ説明していきます。

1. UIの表現構造

まずは Flutter のコード構造がJSXが似ている点ですが、下記の Flutter コードを御覧ください。

Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
  ),
);

React に慣れている方であれば Flutter を知らなくても、しばらく眺めていれば出力がどうなるか想像できるかもしれません。

上記のFlutterコードをJSXで書いてみると下記のようになります。
(<Center>, <Column> , <Text>等はどこか他の場所で定義してあるとします。)

<Center>
  <Column style="align-items: center">
    <Text>You have pushed the button this many times:</Text>
    <Text class="headline4">{counter}</Text>
  </Column>
</Center>

上記の Flutter とJSXを見比べてみると、記法に少し差があるだけで大枠での構造は同じだと思いませんか?
HTML では UI を階層構造として捉え、それを表現するのに要素を「入れ子」にしていますが、Flutter でもchildchildrenという明示的なパラメータが必要なものの同様に要素の入れ子で表現しています。
スタイルなども HTML の属性で指定するのと同じ様に、Dart関数の引数に指定する形を取っています。

この構造が同じなので、例えば Flutter でWidget(~ ReactにおけるComponent)を作る際も React を記述するのと同じつもりで記述するだけで、概ね Flutter の流儀に沿ったものが書けてしまいます。

2. DartとJavaScript

Flutter はDart(新しいタブで開きます) という言語で書かれますが、この言語の文法は JavaScript (ES6)とよく似ています。
更に Dart には型付や型推論がありますが、記法は異なるものの TypeScript をマスターしていれば戸惑うことは少なくなります。

下記はDart言語の例です。

Stream computePi({int batch = 100000}) async* {
  var total = 0; // Inferred to be of type int
  var count = 0;
  while (true) {
    final points = generateRandom().take(batch);
    final inside = points.where((p) => p.isInsideUnitCircle);

    total += batch;
    count += inside.length;
    final ratio = count / total;

    yield ratio * 4;
  }
}

Allow function, Generator, var, async, await, 制御文, 配列やObject (Dartではmapと呼びます)、変数のスコープなどかなりの部分がJavaScriptと似ているため、細部を無視すれば概ねコードの意味を掴むことが可能です。
更に言うと TypeScript との文法上の差分を押さえていくだけでも、かなり記述力を高める事ができます。

Dart言語は Google が JavaScript の代替としてクライアントサイドのweb言語として開発しているものです。
そのためか、Dart の文法自体は JavaScript と似ており、更に TypeScript と同様に型安全であったり、開発時期が同時期だったこともあり TypeScript と文法上は似たものになっています。

TypeScript と Dart を両方利用していると感じますが、TypeScriptの型定義しやすさと型推論の強さはDartを凌駕しています。
ただし Google のサービスの一部は Angular を Dart で記述する AngularDart を利用しているとの情報(新しいタブで開きます)があるなど、web開発分野でもDartの動向から目が離せません。

3. 両者に共通する概念

React など Web UIの開発で重要な「コンポーネント」、「状態」、「イベント・ドリブン」などの概念はやはり Flutter でも重要です。
これらは慣れていないと結構難しい概念なので、多少なりとも似ているのは React 経験者にとって非常に嬉しいところです。

「状態」についてすこし具体的に考えてみます。React は「状態」をどの様な「見た目」で表現するか?、という考えを非常にはっきりさせる API を有しています。

少し分かりづらいと思うので、具体的なコードで考えます。

例えば下記のコードを見てみます。

const CountButton: React.FC = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(prev => prev + 1)}>{count}</button>;
};

このコードは React 経験者ならすぐに理解できるでしょう。
それは勿論「JSXが読める」とか「hooks APIを知っている」からという理由もあるかと思います。
ただ、個人的にはそれ以上に React 流の状態と見た目の分離の仕方が身についているという点が大きいのではないかと考えています。

これをFlutterで書くと、概ね下記のようになります。

class _CountButtonState extends State {
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    return TextButton(
      child: Text('$_counter'),
      onPressed: () {
        setState(() {
          _counter++;
        });
      },
    );
  }
}

記法の差はあるものの、コードの意味や、setStateが必要な理由などは理解できると思います。
もし React など類似のフレームワークの開発経験が無ければ理解に時間を要することでしょう。

React経験者には「React Native」の方がいいのではないか?

ここまで Flutter の紹介をしてきましたが、React 使いが始める Android / iOSアプリ開発といえば、真っ先に思いつくものに React Native(新しいタブで開きます) があります。当然のことながら Flutter より更に学習コストは低くできます。
また React Native 上に構築されたフレームワーク Expo(新しいタブで開きます) を使えば、驚くべきことに Android Studio や Xcode をインストールせずに実デバイスで動作確認しながらアプリ開発をスタートできます。
Reduxなどの状態管理ライブラリもそのまま利用可能ですし、既存のwebアプリのコードを変更なしで流用したりもできるでしょう。
UIもJSX (TSX)とCSSで記述することが可能です。

以上のように React Native はとても魅力的ではありますが、それでも個人的には React Native に挑戦する前に Flutter も検討すべきだと思っています。
実は過去に私も React Native で開発を行ったことがありますが、今回の Flutter 習得と比較してみるとUI記述の学習コストには大きな差がないと感じました。
React Native はプラットフォームがブラウザからアプリに変わるので、HTMLタグが全てそのまま使えるわけではない点も学習コストの差を縮めています。
また設計思想の違いからか、画面遷移など含めモバイルアプリで「よくある」UIを実現するには Flutter の方が遥かに簡単な印象です。

Web開発者でもFlutterならアプリ開発にスムーズに入れる可能性が高い

React 開発経験者の私が、Flutte rでの開発業務に数ヶ月携わった経験を元に、Flutter と React との比較を書いてみました。
Web開発者でも Flutter ならアプリ開発にスムーズに入れる可能性があると感じていただければ幸いです。

当社では、ネイティブでのアプリ開発を極めたエンジニアと私のような Webエンジニアが共同で Flutter を使い開発を行う機会がありました。近年のアプリはWebバックエンドとの連携が必須です。Webエンジニアがアプリのコードベースに直接手を入れることで、Webネイティブの視点からアプリエンジニアが気づかなかった問題提起や解決を行うなど、相補的に協力しながら仕事を進めることができたと感じています。

当社では現在 React 、Flutter を重要なコア技術とみなして複数の案件の開発に採用しています。

弊社スパイスファクトリーは豊富な実績を持ち、これらのプロセスを効率的に進めるためのサポートを提供いたします。

詳細を知りたい方は、こちらからサービス紹介資料をダウンロードしてください。

DXエージェンシースパイスファクトリー株式会社、エンジニアのワンです。
スパイスファクトリーのシステム開発サービスはこちら。

この記事では、React 初心者の方に向けて、react hooks についての基本説明と、いくつかの hook の紹介をしていきます。

hookとは

そもそも、 hook とは何でしょうか。
React 公式サイト(新しいタブで開きます)では、hooks について以下のように説明してあります。

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。

要は、hook は Reactの state やライフサイクルの機能などを、関数コンポーネント内に使用できるようにするための関数です。

hookを使用する理由

React 公式ドキュメントでは、クラスコンポーネントより hook を用いる関数コンポーネントの方がおすすめだとされています。

なぜクラスコンポーネントではなく関数コンポーネントを使用した方が良いのでしょうか。

実際に両者のソースコードを比較していきましょう。

クラスを使った場合

クラスは this を使用しています。 例えば this.setState などの特殊な状態処理メソッドを指します。
しかし、this は JavaScript の特別なキーワードであり、状況によって異なるものを参照するため、分かり辛い場合があります。

  • メソッドの中では、クラスオブジェクトを指す
  • イベントハンドラーでは、イベントを受け取った要素を指す

それに加えて、コードが多くなると更に複雑になります。
以下はクラスコンポーネントの簡単な例です。


import React from 'react';
class HelloComponent extends React.Component {
  constructor(props) {
      super(props);
      this.state = { name: '' };
      this.handleChange = this.handleChange.bind(this);
  };
  
  handleChange(event) {
      this.setState({ name: event.target.value });
  };
  
  render() {
      const { name } = this.state
      return (
            <div>
                <input value={name} onChange={this.handleChange} />
                <p>Hello {name}</p>
            </div>
      );
  };
};

 

hook を使って関数コンポーネントとして定義した場合

次のコードは、上と同じコンポーネントを関数コンポーネントとして書いたものです。


import React, { useState } from 'react';
const HelloComponent: React.FC = () => {
    const [name, setName] = useState('');
    
    const handleChange = (event) => {
        setName(event.target.value)
    };

    return (
        <div>
            <input value={name} onChange={handleChange} />
            <p>Hello {name}</p>
        </div>
    );
};

 

こちらは this を使用せず、 useState という hook を使って name の値を管理しています。
クラスコンポーネントと比べてコードの量が少なく、読みやすい印象です。

hook でロジックをカプセル化できるので、UIとロジックの分離が容易になります。
また、一度定義した hook は、関数コンポーネント内と他の hook 内でも使いまわせます。

把握しておくべきhookのルール

hook には2つのルールがあります。
hook の使用にあたって、まずこれらを理解しておく必要があります。

  • 関数コンポーネントでのみ使用でき、クラスコンポーネントでは使用できない
  • hookの呼び出しの順番は、毎回のレンダリングで同じである必要がある

尚、呼び出しの順番に関しては、if 条件やループ、ネストした関数の中に hook を入れることもできませんのでその点もご注意ください。

Reactの公式hook紹介

続いて、React が公式で出している hook について紹介します。
ここでは最もよく使われるという理由から useState と useEffect をピックアップしてご説明します。

useState と useEffect 以外にも、useContext、useRef、useCallback などの hook があるので、詳しく知りたい方はこちらの記事(新しいタブで開きます)を参照してください。
https://ja.reactjs.org/docs/react-api.html#hooks

useState


import { useState } from 'react';
const [state, setState] = useState(initialState);
 

 

useState は state の値とその値を更新するためにセッター関数 setState を返します。
ステート管理のため、クラスコンポーネントのthis.stateとthis.setState()の代わりに使えます。

useEffect


import { useEffect } from 'react';
useEffect(() => {
    // 実行処理
}, []));

 

useEffect は、クラスコンポーネントの componentDidMount、componentDidUpdate、componentWillUnmount のメソッドの代わりに使えます。
1つ目の引数として定義した関数の実行が2つ目の引数の値によって変わります。イメージとしては以下です。

  1. 2つ目の引数の配列が指定されてない場合は、何かのステートが変わる度に実行されます。
  2. 2つ目の引数の配列が空だったら、componentDidMount と同じ動きになります。
  3. 2つ目の引数の配列に変数を指定すると、その変数に変化があったときに実行されます。

その他おすすめhook

その他にも便利な hook はたくさんあります。

react hook form

react hook form はフォームを検証するのに役立つライブラリです。
hook の形にしたメソッドで使用されます。
以下は react-hook-form を使った簡単な例です。


import { useForm } from 'react-hook-form';
const HelloFormComponent = () => {
    const { register, handleSubmit, formState: { errors } } = useForm();
    const onSubmit = (data) => console.log('Hello: ' + data.name);
    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input defaultValue="" {...register("name")} placeholder="名前" />
                {errors.exampleRequired && <span>必須項目です</span>}
            <input type="submit" />
        </form>
    );
};
 

 

custom hooks

また、独自の hook を定義することもできます。
こちら(新しいタブで開きます)は custom hook が集まっているレポジトリです。
https://github.com/streamich/react-use

過去作成したhook紹介

これまで私自身が作成した hook についても1つピックアップしてご紹介します。

useIsScrollAtEnd

この hook は isScrollAtTop と isScrollAtBottom を返します。scroll の位置が一番上もしくは一番下にあるかどうかを教えてくれます。


import { useCallback, useEffect, useState } from 'react';
import throttle from 'lodash/throttle';
const fps = 12;
const useIsScrollAtTopBottom = (
    targetDom: HTMLDivElement | (Window & typeof globalThis) = window,
    scrollTopThreshold = 0
) => {
    const [isScrollAtTop, setIsScrollAtTop] = useState(false);
    const [isScrollAtBottom, setIsScrollAtBottom] = useState(false);
    const target = targetDom;

    const handleScroll = useCallback(() => {
        const scrollPosition =
            'pageYOffset' in target ? target.pageYOffset : target.scrollTop;
        const scrollHeight =
            'document' in target
                ? target.document.documentElement.scrollHeight
                : target.scrollHeight;
        const containerHeight =
            'innerHeight' in target ? target.innerHeight : target.clientHeight;
    
        const maxScrollTop = scrollHeight - containerHeight;

        if (scrollPosition = maxScrollTop) {
            setIsScrollAtBottom(true);
        } else {
            setIsScrollAtTop(false);
            setIsScrollAtBottom(false);
        }
    }, [target, scrollTopThreshold]);

    const throttledScroll = throttle(handleScroll, 1000 / fps);

    useEffect(() => {
        targetDom.addEventListener('scroll', throttledScroll);
        return () => targetDom.removeEventListener('scroll', throttledScroll);
    }, [handleScroll]);

    return { isScrollAtTop, isScrollAtBottom };
};

 

以下は実際の使い方です。
isScrollAtBottom の値が true になったら、loadNextPage() で次のページを取得する処理を実行します。
そうすると、無限スクロールを実現することができます。


const { isScrollAtBottom } = useIsScrollAtTopBottom();
useEffect(() => {
    if (isScrollAtBottom) {
        loadNextPage();
    }
}, [isScrollAtBottom]);

 

 

useIsScrollAtEnd のフックを実装することで、無限スクロールを簡単に実現できるようになりました。

hookは、シンプルで利便性の高い機能

関数コンポーネント + hook はクラスコンポーネントの代わりに使用できます。
クラスよりもシンプルで、コードがとても読みやすくなります。
また、hook は関数コンポーネント内のどこでも再利用できるので、そういった観点からもとても便利です。
是非、皆さまも活用してみてください。

また、弊社では開発からサービスの運用まで、幅広くご支援させていただいております。
是非一度お問い合わせください。

また、詳しいサービス内容に関しては、当社ホームページのアジャイル開発サービスをご参照ください。

https://spice-factory.co.jp/contact/

無料相談はこちらから
 

スパイスファクトリー株式会社、エンジニアのリンです。
スパイスファクトリーの詳しいサービスはシステム開発サービスをご覧ください。

近年、React + GraphQL + Apollo Client を組み合わせた開発の人気の高まりと共に、React + GraphQL + Apollo Client の導入について紹介する記事が増えてきています。

React + GraphQL + Apollo Client

2020 state of js data layer 部門GraphQLだけでなく、GraphQL関連のpackage (Apollo Client)も注目されてます

その中で、GraphQL のつなぎ込みや Apollo が提供する hook などの記事は多くありますが、ローカルステートの管理についての紹介はまだ多くありません。

そのため、Apollo Client を使ったときのローカルステートの管理をどのように設計すべきか参考例と共にまとめていきます。
尚、こちらの記事は、React、redux、GraphQL の知識をお持ちの方を対象に説明を進めます。

Apollo Clientが担う役割によって運用方法やパッケージが異なる

まず、Apollo Client がアプリケーション内でどのような役割を担うか考えてみます。
その違いによって、ローカルステートの管理の仕方、または運用するパッケージも異なってくるためです。

基本的には、Apollo Client がアプリケーション内で担う役割については、以下の2つになります。

  • 単なるGraphQLのAPIサーバーへのつなぎ込み及び取得したデータのキャッシュ化
  • アプリ全体のデータのコアとしての役割

Apollo Client を単なる GraphQL のAPIサーバーへのつなぎ込み、そして取得したデータをキャッシュ化してくれるツールと考える場合は、Apollo Client以外での状態管理が必要になります。

Apollo Client をアプリ全体のデータのコアと考える場合は、Apollo Client の API でローカルステートも管理していきます。

以下、Apollo Client 以外で状態管理する場合と、Apollo Client の API でローカルステートも管理する場合の、それぞれの方針について詳しく見てきます。

Apollo Client以外で状態管理する場合

Apollo Client以外で状態管理をするなら、GraphQL や Apollo Client を利用しないアプリケーションと大きな違いは無いと考えます。
そのため一般的によく使われる下記のような状態管理のライブラリが利用できます。

  • redux
  • React Context
  • その他(MobX…など)

考慮すべき点

Apollo Client 以外で状態管理する場合は以下の留意点があります。
まず、GraphQL に関連するデータが ApolloClient 側に、ローカルステートは他のライブラリにそれぞれ保持されるため、管理し辛い状況が出てきます。
また、データが二重管理になる可能性や、パッケージ数が増えるといった点も考慮する必要があるでしょう。

これらの問題を解決するには、予めルールを作りデータが重複しないよう工夫することがまず考えられます。
その他、Apollo Client の代替として URQL を利用するのもひとつの方法でしょう。

https://formidable.com/open-source/urql/(新しいタブで開きます)

Apollo ClientのAPIで状態管理する場合

Apollo Client の API で状態管理する場合について考えていきます。
尚、Apollo Client はローカルステートに関して4種類の API を提供しています。

Apollo Clientのcache にデータを直接書き込む

Apollo Client の cache にデータを直接書き込んで、GraphQL の形で呼び出します。
以下のような記述で実現可能になります。

呼び出す

graphQL の変数名の後ろに `@client`を追加すると、Apollo Client はローカルステートとして認識してくれます。


const IS_LOGGED_IN = gql`
  query IsUserLoggedIn {
    isLoggedIn @client
  }
`;

 

書き込む


cache.writeQuery({
  query: IS_LOGGED_IN,
  data: {
    isLoggedIn: // ログインの判断処理 e.g. !!localStorage.getItem('token'),
  },
});

 

reactive variableを利用する(Apollo 3 系以降)

reactive variable (Apollo 3 系以降)は、どこでも使えるグローバル変数です。
以下ソースコード例です。

グローバル変数作成


import { makeVar } from '@apollo/client';
const isLoggedInVar = makeVar(false);

 

呼び出す


console.log(isLoggedInVar());
// output: false

 

書き込む


isLoggedInVar(true)
console.log(isLoggedInVar());
// output: true

 

type policiesを利用する(Apollo 3 系以降)

type policies (Apollo 3 系以降)は、GraphQL (Query) を通じて、ローカルステートを呼び出し、type policies が対応するデータを返します。

定義する

ローカルステートの中に `isLoggedIn` が使われる時にどういう値を返すかのを定義します。後ほど GraphQL で isLoggedIn も呼び出すことができます。


export const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        isLoggedIn: {
          read() {
            return true;
          }
        }
      }
    }
  }
});

 

呼び出す


const IS_LOGGED_IN = gql`
  query IsUserLoggedIn {
    isLoggedIn @client
  }
`;

 

local resolverを利用する(Apollo Client 3系前で利用可能)

3系以前で主流だった方法のひとつです。
3系以降でも使えるのですが、廃止予定となっておりかつ API の更新が既に停止しているため、ご注意ください。
尚、API の更新が早かったり、コンセプト自体が他の既存のライブラリーとやや異なるため、学習コストが高いことが懸念点です。

プロジェクトの状況によって、適するローカルステートの管理方法は異なる

React + GraphQL + Apollo Client でのローカルステートの管理の方法をまとめてみました。
プロジェクトの状況によって、適するローカルステートの管理方法は異なってくると思います。

この記事では Apollo Client が担う役割に基づいて2つの実装方針をまとめましたが、他にも色々な観点があります。
例えば、既に redux を導入しているプロジェクトだと、無理に Apollo Client APIに変更することはあまりメリットがないかもしれません。
また、Apollo Client の API がまだ定まっていないという観点から、Apollo Client の API を導入しないと考えるのもまた合理的だと思います。
私自身は、パッケージを減らすことができ、構造上 SSOT(single source of truth) のコンセプトに近いという視点から、Apollo Client の API を利用しています。

ケースバイケースで適するローカルステートの管理方法が何かを考え、取り組んでいくことが必要でしょう。

最後に

また、弊社では開発からサービスの運用まで、幅広くご支援させていただいております。
是非一度お問い合わせください。

また、詳しいサービス内容に関しては、当社ホームページのアジャイル開発サービスをご参照ください。

https://spice-factory.co.jp/contact/

無料相談はこちらから