Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Native経験者が Flutterを30⽇間触って驚いたこと

Avatar for ryota_saito ryota_saito
November 03, 2026

React Native経験者が Flutterを30⽇間触って驚いたこと

FlutterKaigi 2026 Day1 の登壇資料です。

React Nativeでアプリ・Webのリリースを経験した私が、新プロジェクトでFlutter(Dart)での開発を始めました。私自身、Flutterは初学者の状態からのスタートでしたが、React Native(TS/JS)の知見がある立場で感じた、Flutter開発における学びを共有します。

本スライドでは、両フレームワークのUI構築と状態管理の思想における共通点と相違点に焦点を当てます。JSXによるコンポーネント定義やReact Hooksによる状態操作の経験が、FlutterのWidgetツリーやRiverpodの世界にどう応用できるのか。学習時の驚きや技術的な気づき、そしてFlutterならではの利点をご紹介します。

Avatar for ryota_saito

ryota_saito

November 03, 2026

Other Decks in Programming

Transcript

  1. 齋藤 稜太 Ryota Saito 株式会社アサイン デジタル戦略室 / エンジニア • ⼤学卒業後、Web制作会社に⼊社し、主に⼤⼿教育企業や⾦融機関向けの

    コンテンツ制作にかかわるプロジェクトを経験 • 昨年11⽉にアサインに参画し、今年3⽉にReact Nativeアプリをリリース。 現在はFlutterアプリを開発中 ©ASSIGN Inc. All Rights Reserved 2
  2. 余⽩をつけたいだけなのに、なぜ包む? React Nativeではstyleの1⾏。FlutterではWidgetで包む。 React Native Flutter <View style={{ padding: 16

    }}> <Text>テキスト</Text> </View> Padding( padding: EdgeInsets.all(16), child: Text('テキスト'), ) ©ASSIGN Inc. All Rights Reserved 4
  3. Widget は、⾃分の位置を⾃分で決められない 制約は下へ、サイズは上へ、位置は親が決める Flutter 親 Widget 使える範囲を渡す 制約は下へ Padding Textの位置を決める

    サイズは上へ Padding( padding: EdgeInsets.all(16), child: Text('テキスト'), ) Text 自分のサイズを決める ©ASSIGN Inc. All Rights Reserved 5
  4. この⾒⽅は、ほかのWidgetにも通⽤するのか 試してみると、どの Widget もひとつの仕事しかしていなかった DecoratedBox( // 装飾をつける decoration: …, child:

    Padding( // 余白をとる padding: …, child: Column( // 縦に並べる children: [ const Text('テキスト'), // 文字を出す ElevatedButton(...), // 操作を受け付ける ]))) ©ASSIGN Inc. All Rights Reserved 6
  5. ネストの正体は、役割の階層だった 仕事は⼤きく3種類に分けることができる Flutter React Native 構造 Text, Image, ElevatedButton レイアウト

    Padding, Column, Center Text, Image, Pressable Viewのstyle(padding, flex, backgroundColor…) スタイル DecoratedBox, Opacity, Theme ※ 理解のための対⽐で、1対1の対応ではない ©ASSIGN Inc. All Rights Reserved 7
  6. 同じことをするのに、クラスを2つ書くの!? React NativeのuseStateは、 1⾏だった Flutter class CounterPage extends StatefulWidget {

    const CounterPage({super.key}); @override React Native State<CounterPage> createState() => _CounterPageState(); } const [count, setCount] = useState(0); class _CounterPageState extends State<CounterPage> { int _count = 0; @override <Button onPress={() => setCount(count + 1)} Widget build(BuildContext context) { /> return ElevatedButton( onPressed: () => setState(() => _count++), child: Text('$_count'), ); } } ©ASSIGN Inc. All Rights Reserved 8
  7. React NativeのuseStateも値は関数の外にあった 関数も Widget も描画のたびに作り直されるので、 値は外に置くしかない React Native Flutter 関数

    描画のたびに実行し直す Widget 再ビルドのたびに作り直す 値 React の内部の オブジェクトに残る 値 State のクラスに残る hooks が値の所在を透過的にしていた ©ASSIGN Inc. All Rights Reserved 9
  8. Flutterにもhooksがあった useStateという名前まで同じで、React Nativeの感覚のまま書けた React Native const [count, setCount] = useState(0);

    <Button onPress={() => setCount(count + 1)} /> ©ASSIGN Inc. All Rights Reserved flutter_hooks final count = useState(0); onPressed: () => count.value++, 10
  9. 状態と通信を、別の道具で持っていた クライアント側で持つデータか、サーバーから来るデータかで 分けていた React Native // 入力途中のデータ(クライアント側の状態) 2つのライブラリを使⽤ const schoolHistory

    = useCreateDocumentStore((s) => s.schoolHistory); 1. Zustand ⼊⼒途中のデータを保持 // 保存済みデータの取得(サーバー由来の状態) const { data, isLoading, error } = useQuery({ queryKey: queryKeys.userDocument(userId), queryFn: () => getUserDocument(userId), 2. TanStack Query ⼀覧の取得やAPIへの保存 }); ©ASSIGN Inc. All Rights Reserved 11
  10. 取得も⼊⼒中の値も、同じref.watchで読める Widgetの外に値を置く仕組みが、ひとつにまとまっていた Flutter / riverpod // クライアント側の状態 final isNewGraduate =

    ref.watch(isNewGraduateControllerProvider); // サーバー由来の状態 final profileAsync = ref.watch(userProfileControllerProvider); ©ASSIGN Inc. All Rights Reserved 12
  11. 読み込み中も失敗も、⼀つの値として届く ⾮同期の結果は、AsyncValueという型でUIに届く @riverpod Future<UserProfile> userProfile(Ref ref) async { ... }

    // 画面:3つの状態がひとつの型で届く final profileAsync = ref.watch(userProfileProvider); return switch (profileAsync) { AsyncValue(hasError: true) => _ErrorArea(), // 失敗 AsyncValue(:final value?, hasValue: true) => _ProfileBody(profile: value), // 値 _ => const CircularProgressIndicator(), // 読み込み中 }; ©ASSIGN Inc. All Rights Reserved 13
  12. まとめ 別のフレームワークを学ぶと、今までの当たり前の理由が⾒えてくる 1. UI構築 React Nativeではstyleの1か所にまとめていたものが、 Flutterでは役割ごとの Widgetに分かれていた 2. 状態管理

    React Nativeでは2つのライブラリで扱っていたものが、 Riverpodでは同じ ref.watchで読め、⾮同期はAsyncValueという型で届いた ©ASSIGN Inc. All Rights Reserved 14