Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
三者三様 宣言的UI
Search
Keita Kagurazaka
October 21, 2025
Programming
520
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
三者三様 宣言的UI
React / Jetpack Compose / Flutterの3つの宣言的フレームワークの共通点・相違点を実践的なトピックに絞って概説します。
Keita Kagurazaka
October 21, 2025
More Decks by Keita Kagurazaka
See All by Keita Kagurazaka
SELECT FOR UPDATEの話
kkagurazaka
0
490
Mobileアプリのアーキテクチャ設計法
kkagurazaka
2
1.5k
原理から完全理解するDagger Hilt Migration
kkagurazaka
1
2k
今後のJetpackでAndroid開発はこう変わる!
kkagurazaka
16
6.4k
外部SDKのViewにマスク処理をする方法と罠
kkagurazaka
0
1.1k
AWAのフルリニューアルを支えたアーキテクチャ
kkagurazaka
1
970
CQRS Architecture on Android
kkagurazaka
7
3.2k
suspending functionの裏側
kkagurazaka
3
480
coroutinesで非同期ページネーション
kkagurazaka
1
710
Other Decks in Programming
See All in Programming
TAKTでAI駆動開発の品質を設計する
j5ik2o
6
1.3k
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.6k
OSもどきOS
arkw
0
560
ローカルLLMを使ってB2Bサービスを作っていての学び
yaotti
0
170
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
540
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
510
Webフレームワークの ベンチマークについて
yusukebe
0
170
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
6.6k
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
250
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
130
Claspは野良GASの夢をみるか
takter00
0
190
Lessons from Spec-Driven Development
simas
PRO
0
190
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
Designing Powerful Visuals for Engaging Learning
tmiket
1
410
The Curious Case for Waylosing
cassininazir
1
390
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
Code Reviewing Like a Champion
maltzj
528
40k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
580
Joys of Absence: A Defence of Solitary Play
codingconduct
1
390
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
2.9k
How to train your dragon (web standard)
notwaldorf
97
6.7k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
140
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.8k
Transcript
三者三様 宣言的UI React / Compose / Flutter を見比べて Keita Kagurazaka
@ ANDPAD Inc.
今日のテーマ 状態管理と再レンダリング 3つの宣言的UIフレームワークの共通点や相違点を概説 React (Web): 2013年初版公開 Jetpack Compose (Android): 2019年preview版公開
Flutter (iOS/Android): 2017年alpha版公開 2
アプリの状態管理
アプリの2つの状態について アプリの状態とは UIを(再)構築する際に必要となる、あらゆるデータ 宣言的UIは アプリの状態 を引数に UI を出力する関数とみなせる 大きく分けて2種類ある Ephemeral
State Ephemeralは 一時的な という意味 1つのUI要素に閉じた状態 他のUIツリーからは依存されず、独立している App State アプリ内の様々な箇所から参照される状態 画面単位だったり、アプリが立ち上がってる間ずっと保持する状態 https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app 4
Ephemeral Stateの扱い方
Ephemeral Stateの扱い方 各フレームワークにおける代表的な方法 React: useState など Compose: remember x mutableStateOf
Flutter: StatefulWidget 6
Reactの場合 - Hooks React Hooksでシンプルな状態をget/setするパターン function Counter() { const [count,
setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); } 7
Composeの例 (1) - remember recompositionを超えて MutableState を維持させるため remember を利用 Kotlinのdelegation記法
by を使って、mutableな変数を書き換える形で書ける @Composable fun Counter() { var count by remember { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") } } 8
Composeの例 (2) - remember Kotlinの分解宣言を用いて書くこともできる こうするとReactのuseStateとそっくりなことがわかる setterを関数としてそのまま引数に渡すみたいなケースで便利 @Composable fun Counter()
{ val (count, setCount) = remember { mutableStateOf(0) } Button(onClick = { setCount(count + 1) }) { Text("Count: $count") } } 9
Flutterの例 (1) - StatefulWidget class Counter extends StatefulWidget { @override
_CounterState createState() => _CounterState(); } class _CounterState extends State<Counter> { int count = 0; @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () => setState(() { count++; }), child: Text('Count: $count'), ); } } 10
Flutterの例 (2) - flutter_hooks React Hooksライクな書き方ができる外部パッケージ 中身のコードは本質的にStatufulWidgetと等価なので、個人的にオススメ class Counter extends
HookWidget { @override Widget build(BuildContext context) { final count = useState(0); return ElevatedButton( onPressed: () => count.value++, child: Text('Count: ${count.value}'), ); } } 11
Ephemeral Stateの扱い方 - まとめ いずれもReact Hooksスタイルで書ける React: useState など Compose:
remember x mutableStateOf Flutter: useState など by 3rd-party lib 12
暗黙的な状態伝達
暗黙的な状態伝達 UIツリーの部分木で状態を共有する仕組み React: Context API Compose: CompositionLocal Flutter: InheritedWidget 14
Reactの例 - Context API Providerで提供した値を、任意のComponent内で useContext を使って取得 Providerをネストすると直近の祖先の値を優先して使う const ThemeContext
= React.createContext('light'); function App() { return ( <ThemeContext.Provider value="dark"> <Screen /> </ThemeContext.Provider> ); } function Screen() { const theme = useContext(ThemeContext); return <div>Theme: {theme}</div>; } 15
Composeの例 - CompositionLocal Providerで提供した値を、任意のComposable内で .current を使って取得 Providerをネストすると直近の祖先の値を優先して使う val LocalTheme =
compositionLocalOf { LightTheme } @Composable fun App() { CompositionLocalProvider(LocalTheme provides DarkTheme) { Screen() } } @Composable fun Screen() { val theme = LocalTheme.current Text("Theme: $theme") } 16
Flutterの例 (1) - InheritedWidget class ThemeData extends InheritedWidget { final
Color primaryColor; // ThemeDataは、この状態を保持するUIなしWidget final Widget child; const ThemeData({ required this.primaryColor, required this.child, }) : super(child: child); @override bool updateShouldNotify(ThemeData old) => primaryColor != old.primaryColor; static ThemeData of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<ThemeData>()!; } } 17
Flutterの例 (2) - InheritedWidget class App extends StatelessWidget { @override
Widget build(BuildContext context) { return ThemeData( // 提供したい値を引数にInheritedWidgetでwrap primaryColor: Colors.blue, child: Screen(), ); } } class Screen extends StatelessWidget { @override Widget build(BuildContext context) { final theme = ThemeData.of(context); // .ofで提供された値を取得 return Text('Theme: ${theme.primaryColor}'); } } 18
暗黙的な状態伝達 - まとめ 書き方にはそれぞれ個性があるが、同じ機能は提供されている ThemeのようにUIツリーのありとあらゆる箇所から参照しつつ、動的に変わりうる ものだけに使うのが一般的 非常にシンプルなアプリの場合、App State管理としても使える UIツリー全体をroot nodeを頂点とする部分木としてみる
19
App Stateの扱い方
App Stateの扱い方 アプリ全体で共有する状態管理 React: Zustand など Compose: ViewModel + StateFlow
Flutter: Riverpod など 21
Reactの例 - Zustand selectorで状態を部分watchできる import create from 'zustand'; const useStore
= create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })) })); function Counter() { const increment = useStore((state) => state.increment); return <button onClick={increment}><CountDisplay /></button>; } function CountDisplay() { const count = useStore((state) => state.count); // 状態を部分的にwatch return <span>Count: {count}</span>; } 22
Composeの例 (1) - ViewModel + StateFlow 状態とその操作を定義 data class CounterUiState(val
count: Int = 0) class CounterViewModel : ViewModel() { private val _uiState = MutableStateFlow(CounterUiState()) val uiState: StateFlow<CounterUiState> = _uiState.asStateFlow() fun increment() { _uiState.update { it.copy(count = it.count + 1) } } } 23
Composeの例 (2) - ViewModel + StateFlow Jetpack Composeにはselectorはなし @Composable fun
Counter(viewModel: CounterViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsState() Button(onClick = { viewModel.increment() }) { CountDisplay(count = uiState.count) } } @Composable fun CountDisplay(count: Int) { Text("Count: $count") } 24
Flutterの例 (1) - Riverpod 状態とその操作を定義 class CounterState { final int
count; CounterState(this.count); } final counterProvider = StateNotifierProvider<CounterNotifier, CounterState>( (ref) => CounterNotifier() ); class CounterNotifier extends StateNotifier<CounterState> { CounterNotifier() : super(CounterState(0)); void increment() => state = CounterState(state.count + 1); } 25
Flutterの例 (2) - Riverpod selectorで状態を部分watchできる class Counter extends ConsumerWidget {
@override Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton( onPressed: () => ref.read(counterProvider.notifier).increment(), child: CountDisplay(), ); } } class CountDisplay extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(counterProvider.select((state) => state.count)); return Text('Count: $count'); } } 26
App Stateの扱い方 - まとめ 採用するライブラリによって千差万別 実現したいこと 状態の定義や操作をUIとは別のクラス・コンポーネントに切り出す パフォーマンスを落とさない 暗黙的な状態伝達も同じライブラリで実現可能なことも ZustandやRiverpodはUIツリーの特定の部分木で状態を上書きできる
大は小を兼ねる そもそも全ての状態をApp Stateとして管理することは理屈としては可能 適宜使い分けると、より保守しやすいコードに 27
再レンダリングの思想
Reactの場合 再レンダリングは制御しよう派 コンポーネント設計を工夫する 状態をなるべくツリー末端に移動 propsでchildren / componentを受け取る useMemo , useCallback
, React.memo などのメモ化を駆使する デフォルトでは、あるコンポーネントを再レンダリングすると、その子孫すべて が再レンダリングされる つい先日、React Compiler v1.0がリリースされたので、今後は不要になりそう 状態管理ライブラリにもパフォーマンスを維持する機能が搭載されがち selectorによる部分watchなど 29
Composeの場合 State Hoistingすればコンパイラがなんとかする派 Composableはなるべくstatelessにし、必要なimmutable stateを引数として受け取る いわゆるバケツリレー推奨の設計 原則として Composableが引数を比較して必要な部分だけ再composition Stable うんぬん話は今回は省略
strong skippingでラムダも自動でメモ化 後発なUIフレームワークなだけあって開発者フレンドリー https://developer.android.com/develop/ui/compose/performance/stability/strongskipping 30
Flutterの場合 再buildが軽量ならば何も考えなくていいよね派 Reactと同様、あるWidgetの再buildは子孫のWidget全てを再buildする const で生成されたWidgetを除く Flutterの開発陣いわく、再buildは十分に高速なので、むやみに避ける必要はない Widgetツリーの再構築とRenderObjectの差分更新が分離しているため 現実問題どうなのかは周りの人に聞いてみよう RiverpodやInheritedWidgetを活用しているチームが多いと思われる 31
まとめ
まとめ 宣言的UIの本質は共通 パラダイムが実現したいことは一緒なので、目的ベースで機能を理解しよう 書き方の差はAIが埋めてくれる時代に感謝 それぞれに独自の思想がある 「なぜこういう書き方になるのか?」を掘っていくと理解が深まる これらを踏まえたうえで、1つ理解すれば他も学びやすい 33
ありがとうございました