スパイスファクトリー株式会社でエンジニアとして働いている平川です。
現在は主に TypeScript, React, PHP 等を用いてWebアプリやモバイルアプリの開発を担当しています。
弊社の詳しいサービスはシステム開発サービスをご覧ください。
最近 Flutter(新しいタブで開きます) を数カ月間利用してモバイルアプリ開発を行う機会がありました。Flutterを扱うのは初めてでしたが、私のようなReact(新しいタブで開きます) 経験者にとって 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 の習得は比較的スムーズに進めることができました。
理由は当時私が React をメインに開発していて、かつ Flutter のUIコードが React と似ていたことだと思っています。
このあとは、 Flutter と React が似ていると思ったポイントを考察していきたいと思います。
具体的には、以下の点について触れていきます。
先述した Flutter と React の類似点についてそれぞれ説明していきます。
まずは 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 でもchildやchildrenという明示的なパラメータが必要なものの同様に要素の入れ子で表現しています。
スタイルなども HTML の属性で指定するのと同じ様に、Dart関数の引数に指定する形を取っています。
この構造が同じなので、例えば Flutter でWidget(~ ReactにおけるComponent)を作る際も React を記述するのと同じつもりで記述するだけで、概ね Flutter の流儀に沿ったものが書けてしまいます。
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の動向から目が離せません。
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 など類似のフレームワークの開発経験が無ければ理解に時間を要することでしょう。
ここまで 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 の方が遥かに簡単な印象です。
React 開発経験者の私が、Flutte rでの開発業務に数ヶ月携わった経験を元に、Flutter と React との比較を書いてみました。
Web開発者でも Flutter ならアプリ開発にスムーズに入れる可能性があると感じていただければ幸いです。
当社では、ネイティブでのアプリ開発を極めたエンジニアと私のような Webエンジニアが共同で Flutter を使い開発を行う機会がありました。近年のアプリはWebバックエンドとの連携が必須です。Webエンジニアがアプリのコードベースに直接手を入れることで、Webネイティブの視点からアプリエンジニアが気づかなかった問題提起や解決を行うなど、相補的に協力しながら仕事を進めることができたと感じています。
当社では現在 React 、Flutter を重要なコア技術とみなして複数の案件の開発に採用しています。
弊社スパイスファクトリーは豊富な実績を持ち、これらのプロセスを効率的に進めるためのサポートを提供いたします。
詳細を知りたい方は、こちらからサービス紹介資料をダウンロードしてください。
DXエージェンシースパイスファクトリー株式会社、エンジニアのワンです。
スパイスファクトリーのシステム開発サービスはこちら。
この記事では、React 初心者の方に向けて、react hooks についての基本説明と、いくつかの hook の紹介をしていきます。
そもそも、 hook とは何でしょうか。
React 公式サイト(新しいタブで開きます)では、hooks について以下のように説明してあります。
フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
要は、hook は Reactの state やライフサイクルの機能などを、関数コンポーネント内に使用できるようにするための関数です。
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>
);
};
};
次のコードは、上と同じコンポーネントを関数コンポーネントとして書いたものです。
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 には2つのルールがあります。
hook の使用にあたって、まずこれらを理解しておく必要があります。
尚、呼び出しの順番に関しては、if 条件やループ、ネストした関数の中に hook を入れることもできませんのでその点もご注意ください。
続いて、React が公式で出している hook について紹介します。
ここでは最もよく使われるという理由から useState と useEffect をピックアップしてご説明します。
useState と useEffect 以外にも、useContext、useRef、useCallback などの hook があるので、詳しく知りたい方はこちらの記事(新しいタブで開きます)を参照してください。
https://ja.reactjs.org/docs/react-api.html#hooks
import { useState } from 'react';
const [state, setState] = useState(initialState);
useState は state の値とその値を更新するためにセッター関数 setState を返します。
ステート管理のため、クラスコンポーネントのthis.stateとthis.setState()の代わりに使えます。
import { useEffect } from 'react';
useEffect(() => {
// 実行処理
}, []));
useEffect は、クラスコンポーネントの componentDidMount、componentDidUpdate、componentWillUnmount のメソッドの代わりに使えます。
1つ目の引数として定義した関数の実行が2つ目の引数の値によって変わります。イメージとしては以下です。
その他にも便利な hook はたくさんあります。
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>
);
};
また、独自の hook を定義することもできます。
こちら(新しいタブで開きます)は custom hook が集まっているレポジトリです。
https://github.com/streamich/react-use
これまで私自身が作成した hook についても1つピックアップしてご紹介します。
この 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 は関数コンポーネント内のどこでも再利用できるので、そういった観点からもとても便利です。
是非、皆さまも活用してみてください。
また、弊社では開発からサービスの運用まで、幅広くご支援させていただいております。
是非一度お問い合わせください。
また、詳しいサービス内容に関しては、当社ホームページのアジャイル開発サービスをご参照ください。
https://spice-factory.co.jp/contact/
スパイスファクトリー株式会社、エンジニアのリンです。
スパイスファクトリーの詳しいサービスはシステム開発サービスをご覧ください。
近年、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 がアプリケーション内で担う役割については、以下の2つになります。
Apollo Client を単なる GraphQL のAPIサーバーへのつなぎ込み、そして取得したデータをキャッシュ化してくれるツールと考える場合は、Apollo Client以外での状態管理が必要になります。
Apollo Client をアプリ全体のデータのコアと考える場合は、Apollo Client の API でローカルステートも管理していきます。
以下、Apollo Client 以外で状態管理する場合と、Apollo Client の API でローカルステートも管理する場合の、それぞれの方針について詳しく見てきます。
Apollo Client以外で状態管理をするなら、GraphQL や Apollo Client を利用しないアプリケーションと大きな違いは無いと考えます。
そのため一般的によく使われる下記のような状態管理のライブラリが利用できます。
Apollo Client 以外で状態管理する場合は以下の留意点があります。
まず、GraphQL に関連するデータが ApolloClient 側に、ローカルステートは他のライブラリにそれぞれ保持されるため、管理し辛い状況が出てきます。
また、データが二重管理になる可能性や、パッケージ数が増えるといった点も考慮する必要があるでしょう。
これらの問題を解決するには、予めルールを作りデータが重複しないよう工夫することがまず考えられます。
その他、Apollo Client の代替として URQL を利用するのもひとつの方法でしょう。
https://formidable.com/open-source/urql/(新しいタブで開きます)
Apollo Client の API で状態管理する場合について考えていきます。
尚、Apollo Client はローカルステートに関して4種類の API を提供しています。
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 系以降)は、どこでも使えるグローバル変数です。
以下ソースコード例です。
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 系以降)は、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
}
`;
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/
Spice Factory AIチャット
公開情報からお答えします