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
Flutter Hooks を使ったアプリ開発 / App Development with ...
Search
Daichi Furiya (Wasabeef)
March 23, 2022
Programming
2
1.4k
Flutter Hooks を使ったアプリ開発 / App Development with the Flutter Hooks
CyberAgent Developer Conference 2022
Daichi Furiya (Wasabeef)
March 23, 2022
Tweet
Share
More Decks by Daichi Furiya (Wasabeef)
See All by Daichi Furiya (Wasabeef)
DevFest Tokyo 2025 - Flutter のアプリアーキテクチャ現在地点
wasabeef
6
2.1k
About Flutter Architecture
wasabeef
1
270
2023 Flutter/Dart Summary
wasabeef
0
84
I/O Extended 2023 - Dart と Flutter の新機能
wasabeef
0
190
I/O Extended 2023 - Flutter 活用事例
wasabeef
10
3k
What it Takes to be a Flutter Developer
wasabeef
0
200
FlutterKaigi 2022 Keynote
wasabeef
1
620
Flutter 2021 の振り返りと今後のアプリ開発に向けて / Looking back on Flutter 2021 and for future app development.
wasabeef
4
2.2k
Flutter Hooks, sometimes Jetpack Compose
wasabeef
3
1.8k
Other Decks in Programming
See All in Programming
dynamic!
moro
9
6.6k
CSC509 Lecture 03
javiergs
PRO
0
330
大規模アプリのDIフレームワーク刷新戦略 ~過去最大規模の並行開発を止めずにアプリ全体に導入するまで~
mot_techtalk
0
380
私はどうやって技術力を上げたのか
yusukebe
43
17k
明日から始めるリファクタリング
ryounasso
0
120
Breaking Up with Big ViewModels — Without Breaking Your Architecture (droidcon Berlin 2025)
steliosf
PRO
1
330
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
340
iOS 17で追加されたSubscriptionStoreView を利用して5分でサブスク実装チャレンジ
natmark
0
610
monorepo の Go テストをはやくした〜い!~最小の依存解決への道のり~ / faster-testing-of-monorepos
convto
2
400
CSC509 Lecture 05
javiergs
PRO
0
300
CSC509 Lecture 06
javiergs
PRO
0
240
GraphQL×Railsアプリのデータベース負荷分散 - 月間3,000万人利用サービスを無停止で
koxya
1
1.2k
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
237
140k
RailsConf 2023
tenderlove
30
1.2k
Rebuilding a faster, lazier Slack
samanthasiow
84
9.2k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.7k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Testing 201, or: Great Expectations
jmmastey
45
7.7k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
252
21k
Rails Girls Zürich Keynote
gr2m
95
14k
Statistics for Hackers
jakevdp
799
220k
We Have a Design System, Now What?
morganepeng
53
7.8k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Transcript
None
߱େ גࣜձࣾαΠόʔΤʔδΣϯτ ϝσΟΞ౷ׅຊ෦ %FWFMPQFS1SPEVDUJWJUZࣨ XBTBCFFG@KQ XBTBCFFG (PPHMF%FWFMPQFST&YQFSU
"HFOEB wαΠόʔΤʔδΣϯτͷ'MVUUFS࠾༻ࣄྫ w'MVUUFS)PPLTΛͬͨΞϓϦ։ൃ w 'MVUUFS)PPLTͱʁ w 4UBUFGVM8JEHFUͷϥΠϑαΠΫϧΛར༻ͨ͠߹ w 'MVUUFS)PPLTΛجຊΛཧղ͢Δ w
'MVUUFS)PPLTͷޮՌతͳར༻๏ wࠓޙͷల
αΠόʔΤʔδΣϯτͷ'MVUUFS࠾༻ࣄྫ
'MVUUFSͷ࠾༻ࣄྫ ग़యɿIUUQTXXXDZCFSBHFOUDPKQUFDIJOGPJOGPEFUBJMJE
'MVUUFS)PPLTΛͬͨΞϓϦ։ൃ
'MVUUFS)PPLTͱʁ
'MVUUFS)PPLTͱʁ ग़యɿIUUQTHJUIVCDPNSSPVTTFM(JU fl VUUFS@IPPLT ग़యɿIUUQTSFBDUKTPSHEPDTIPPLTJOUSPIUNM w 3FBDU)PPLTͷ'MVUUFS൛Ͱจ๏͍ํ΄΅ಉ͡ͷ
4UBUFGVM8JEHFUͷ ϥΠϑαΠΫϧΛ ར༻ͨ͠߹
class CountPage extends StatefulWidget { @override State createState() => _CountState();
} class _CountState extends State<CountPage> { int count = 0; @override void initState() { / ** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } void setCount(int value) => setState(() = > count = value); @override Widget build(BuildContext context) { return TextButton( child: Text("Count is $count"), onPressed: () => setCount(count + 1), ); } } w γεςϜͷঢ়ଶͳͲʹΑͬͯಈతʹ มԽΛ͢Δখ͞ͳϥΠϑαΠΫϧΛ ͬͨΟδΣοτ w *NBHFɺ5FYU'PSN'JFME)FSPͳ Ͳ͕4UBUFGVM8JEHFUͷαϒΫϥε 4UBUFGVM8JEHFUͷઆ໌
class CountPage extends StatefulWidget { @override State createState() => _CountState();
} class _CountState extends State<CountPage> { int count = 0; @override void initState() { / ** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } void setCount(int value) => setState(() = > count = value); @override Widget build(BuildContext context) { return TextButton( child: Text("Count is $count"), onPressed: () => setCount(count + 1), ); } } 4UBUFGVM8JEHFUΛ͏ʹجຊతʹೋͭͷ ΫϥεΛ࡞Δඞཁ͕͋Δ w 4UBUFGVM8JEHFUΛܧঝͨ͠$PVOU1BHF w 4UBUF5Λܧঝͨ͠@$PVOU4UBUF 4UBUFGVM8JEHFUͷઆ໌
class CountPage extends StatefulWidget { @override State createState() => _CountState();
} class _CountState extends State<CountPage> { int count = 0; @override void initState() { / ** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } void setCount(int value) => setState(() = > count = value); @override Widget build(BuildContext context) { return TextButton( child: Text("Count is $count"), onPressed: () => setCount(count + 1), ); } } 4UBUFGVM8JEHFUͷઆ໌ 4UBUFGVM8JEHFUΛ͏ʹجຊతʹೋͭͷ ΫϥεΛ࡞Δඞཁ͕͋Δ w 4UBUFGVM8JEHFUΛܧঝͨ͠$PVOU1BHF w 4UBUF5Λܧঝͨ͠@$PVOU4UBUF
class CountPage extends StatefulWidget { @override State createState() => _CountState();
} class _CountState extends State<CountPage> { int count = 0; @override void initState() { / ** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } void setCount(int value) => setState(() = > count = value); @override Widget build(BuildContext context) { return TextButton( child: Text("Count is $count"), onPressed: () => setCount(count + 1), ); } } 4UBUFGVM8JEHFUͷઆ໌ JOJU4UBUFɿॳظԽͷॲཧ EJTQPTFɿഁغͷॲཧ TFU4UBUFɿݺͿ͜ͱͰঢ়ଶ͕มԽͨ͜͠ͱ Λ௨ͯ͠ϦϏϧυ͕Δ
4UBUFGVM8JEHFUͰ࣮ͨ͠߹ͷ՝ w ঢ়ଶΛอ࣋͢ΔΫϥεΛ৽ͨʹ࡞Βͳ͍ͱ͍͚ͳ͍ w ෳࡶͳϩδοΫΛؚΉঢ়ଶΛอ͍࣋ͨ͠߹ʹϩδοΫ ͱΟδΣοτΛͰ͖͍ͯͳ͍ w ίʔυͷ࠶ར༻͕ѱ͍ w ςετ͕ॻ͖ʹ͍͘
'MVUUFS)PPLTͷ جຊΛཧղ͢Δ
'MVUUFS)PPLTͰԿ͕ղܾͰ͖Δ͔ ग़యɿIUUQTHJUIVCDPNSSPVTTFM(JU fl VUUFS@IPPLT ग़యɿIUUQTSFBDUKTPSHEPDTIPPLTJOUSPIUNM w એݴత6*ͰΫϥΠΞϯτΛ্࣮͍ͯ͘͠ͰɺϩδοΫͱ ΟδΣοτʢίϯϙʔωϯτʣͷΛϝϯςφϯεੑΛอ ͪͭͭҡ࣋͢Δͷ͕͘͠ɺͦΕΛղܾ͢ΔػೳΛఏڙ͢Δ w
ίϯϙʔωϯτΛͪΌΜͱؔͱͯ͠ѻ͏ͨΊ w ΫϥεͷఆٛΛݮΒ͢ʢγϯλοΫεγϡΨʔʣͨΊ w 'MVUUFSͷΟδΣοτසൟʹ࠶ඳը͕࣮ߦ͞ΕΔͨΊॏ͍ ԋࢉෳࡶͳॲཧΛ͚͞ΕΔΑ͏ͳػೳΛఏڙ͢Δ w ಉ͡ೖྗσʔλͷ߹ʹ݁ՌΛΩϟογϡ͢Δ
VTF4UBUFΛཧղ͢Δ VTF4UBUF'MVUUFS)PPLTΛར༻͢ΔͳΒඞͣར༻͢Δ Ͱ͋Ζ͏جຊతͳϑοΫͰ͢ʢ෦࣮7BMVF/PUJGJFSʣ ར༻͢Δʹ֮͋ͨͬͯ͑Δ͜ͱ؆୯ͰVTF4UBUF 5 Ͱॳظ Λ༩͑ɺDPVOUWBMVFͷΛมߋ͢Δ͜ͱͰ࠶ඳը // ॳظԽ final
count = useState(0); // ͷߋ৽Λ࠶ඳը count.value = 3;
VTF4UBUFΛཧղ͢Δ ঢ়ଶͷཧΛ͢ΔͨΊʹϞόΠϧΞϓϦ։ൃͷΞʔΩςΫ νϟͰΑ͘ฉ͘Α͏ͳ7JFX.PEFM4UPSFΛ༻ҙ͢Δ΄ ͲͰͳ͍߹ʹར༻͢Δ͜ͱͰ͖Δ ྫ͑ɺϘλϯͷ&OBCMFE%JTBCMFEͷঢ়ଶͷอ࣋Θ͟ Θ͟ΫϥεΛ࡞Δ·Ͱͳ͘ɺͦͷΟδΣοτͰอ࣋͢ ΔͳͲ
VTF4UBUFΛཧղ͢Δ class CountPage extends HookWidget { @override Widget build(BuildContext context)
{ final count = useState(0); return TextButton( onPressed: () { count.value ++ ; }, child: Text("Count is ${count.value}"), ); } } class CountPage extends StatefulWidget { @override State createState() => _CountState(); } class _CountState extends State<CountPage> { int count = 0; void setCount(int value) => setState(() = > count = value); @override Widget build(BuildContext context) { return TextButton( child: Text("Count is $count"), onPressed: () => setCount(count + 1), ); } } 4UBUFGVM8JEHFU VTF4UBUF େ͖ͳҧ͍4UBUFΛܧঝͨ͠Ϋϥε͕ඞ ཁͩͬͨͷ͕ͳ͘ͳΓVTF4UBUF 5 ʹॳ ظΛ༩͑Δ͚ͩʹͳ͍ͬͯΔ
VTF4UBUFΛཧղ͢Δ class CountPage extends HookWidget { @override Widget build(BuildContext context)
{ final count = useState(0); return TextButton( onPressed: () { count.value + + ; }, child: Text("Count is ${count.value}"), ); } }
VTF& ff FDUΛཧղ͢Δ ؆୯ͳΟδΣοτͷϥΠϑαΠΫϧͰॲཧ͕ඞཁͩͬͨ ߹ʹVTF&GGFDUΛ͏ ʢΟδΣοτੜ࣌ʹॳظԽഁغΛ͍ͨ͠߹ͳͲʣ class _CountState extends
State<CountPage> { ɹ // . .. @override void initState() { / ** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } } ɹ // . .. } 4UBUFGVM8JEHFUͷJOJU4UBUFEJQPTF૬
VTF& ff FDUΛཧղ͢Δ class CountPage extends HookWidget { @override Widget
build(BuildContext context) { useEffect(() => { / / ॳظԽ final subscription = source.subscribe(id); return () => { // ഁغ subscription.unsubscribe(id); }; }, [id]); return // Widget . .. } } 8JEHFUͷCVJMEͰVTF&GGFDUͷୈҰҾ ʹΫϩʔδϟΛ͠ɺͦͷΫϩʔδϟͷ SFUVSO࣌ʹഁغ࣌ͷॲཧΛॻ͘ VTF&GGFDUͷୈೋҾʹॳظԽΛϏϧυ࣌ ʹຖճ࣮ߦ͞Εͳ͍Α͏ʹΩʔͱͳΔΦϒ δΣΫτΛ͢ඞཁ͕͋Δ
VTF& ff FDUͷୈೋҾॏཁ class CountPage extends HookWidget { @override Widget
build(BuildContext context) { useEffect(() => { / / ᶃ ID ͕มߋ͞ΕΔͨͼʹݺͼग़͠ }, [id]); useEffect(() => { / / ᶄ ຖճݺͼग़͠ }); useEffect(() => { / / ᶅ ࠷ॳͷҰճ͚ͩݺͼग़͠ }, const []); return // Widget . .. } }
VTF"OJNBUJPO$POUSPMMFSΛར༻ͯ͠ΈΔ class Example extends StatefulWidget { final Duration duration; const
Example({Key? key, required this.duration}) : super(key: key); @override _ExampleState createState() => _ExampleState(); } class _ExampleState extends State<Example> with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration); } @override void didUpdateWidget(Example oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller!.duration = widget.duration; } } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } } 4UBUFGVM8JEHFU 'MVUUFSͰΞχϝʔγϣϯΛ4UBUFGVM8JEHFU Ͱ࣮͠Α͏ͱ͢Δͱঢ়ଶཧͷίʔυΛΫ ϥεʹ͚ͩͰ͜Ε͚ͩඞཁʹͳΓͦͷଟ͕͘ ͍ճ͠Ͱ͖ͳ͍ίʔυʹͳΔ ˞ͳίʔυΛݮΒ͢ͱ͍͏త͚ͩͰ͋ Ε%BSUʹ.JYJO͕͋ΔͷͰͦΕͰڞ௨ Խ͢Δ͜ͱͰ͖Δ
VTF"OJNBUJPO$POUSPMMFSΛར༻ͯ͠ΈΔ class Example extends HookWidget { const Example({required this.duration}); final
Duration duration; @override Widget build(BuildContext context) { final controller = useAnimationController(duration: duration); return Container(); } } class Example extends StatefulWidget { final Duration duration; const Example({Key? key, required this.duration}) : super(key: key); @override _ExampleState createState() => _ExampleState(); } class _ExampleState extends State<Example> with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration); } @override void didUpdateWidget(Example oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller!.duration = widget.duration; } } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } } 4UBUFGVM8JEHFU VTF"OJNBUJPO$POUSPMMFS VTF"OJNBUJPO$POUSPMMFSΛ͏͜ͱͰ ͳίʔυྔݮΒ͢͜ͱ͕Ͱ͖ɺར༻ଆͰͷ ίετΛԼ͛Δ͜ͱ͕Ͱ͖Δ
'MVUUFS)PPLTͷ ޮՌతͳར༻๏
ΧελϜϑοΫΛ࡞͢Δ w ໊ؔʹVTF999ͱ͍͏໊લΛ͚ͭΔ w ࠷ॳVTF4UBUFVTF& ff FDUΛΈ߹Θ ࣮ͤͯͯ͠ΈΔ VoidCallback useUpdate()
{ final attempt = useState(0); return () => attempt.value ++ ; } class Sample extends HookWidget { @override Widget build(BuildContext context) { final update = useUpdate(); return Column( children: [ ElevatedButton( onPressed: () => update(), child: const Text("Update"), ), ] ); } } VTF6QEBUF 4BNQMF
fl VUUFS@VTFϥΠϒϥϦΛར༻ͯ͠ΈΔ w SFBDU@VTFͷ'MVUUFS൛ w Λ͑Δ৭ʑͳϢʔεέʔεʹରԠͨ͠ΧελϜϑοΫू w ࣗ࡞ΧελϜϑοΫΛ࡞Δͱ͖ʹࢀߟʹͰ͖Δʢͱࢥ͏ʣ
fl VUUFS@VTFϥΠϒϥϦΛར༻ͯ͠ΈΔ
VTF& ff FDU0ODFΛར༻ͯ͠ΈΔ w fl VUUFS@VTFʹ͓͍ͯ࠷୯७ͳ࣮ w VTF& ff FDUͷୈೋҾΛলུ͢ΔͨΊͷΧελϜϑοΫ
useEffect(() => { // ࠷ॳͷҰճ͚ͩݺͼग़͠ }, const []); void useEffectOnce(Dispose? Function() effect) { return useEffect(effect, const []); } VTF&GGFDU VTF&GGFDU0ODFͷ࣮
VTF& ff FDU0ODFΛར༻ͯ͠ΈΔ w fl VUUFS@VTFʹ͓͍ͯ࠷୯७ͳ࣮ w VTF& ff FDUͷୈೋҾΛলུ͢ΔͨΊͷΧελϜϑοΫ
useEffect(() => { // ࠷ॳͷҰճ͚ͩݺͼग़͠ }, const []); void useEffectOnce(Dispose? Function() effect) { return useEffect(effect, const []); } VTF&GGFDU VTF&GGFDU0ODFͷ࣮
w ΞϓϦཁ݅ͰԿඵޙʹॲཧΛ࣮ߦ͍ͤͨ͞߹ w VTF5JNFPVUVTF5JNFPVU'OΛ͏͜ͱͰࢦఆͷ࣌ؒܦա ޙʹϦϏϧυΫϩʔδϟΛ࣮ߦ͢Δ͜ͱ͕Ͱ͖Δ class Sample extends HookWidget {
@override Widget build(BuildContext context) { final timeout = useTimeout(const Duration(seconds: 3)); return Column( children: [ Text("isReady ?: ${timeout.isReady}"), ElevatedButton( onPressed: () => timeout.reset(), child: const Text("Reset"), ), ], ); } } VTF5JNFPVUΛར༻ͯ͠ΈΔ
w ෦ͰDPOOFDUJWJUZ@QMVTͱ͍͏ϥΠϒϥϦΛͬͯ ͷωοτϫʔΫঢ়گΛऔಘ͍ͯ͠Δ w ࣗͰ࣮͢Δͱඇಉظॲཧͷ੍ޚͳͲ͕ඞཁ͕ͩɺ͜ ͷΧελϜϑοΫͰར༻ଆγϯϓϧʹ͍ͯ͠Δ class Sample extends HookWidget
{ @override Widget build(BuildContext context) { final networkState = useNetworkState(); return Column( children: [ Text( "Network: ${networkState.connectivityResult}"), ], ); } } VTF/FUXPSL4UBUFΛར༻ͯ͠ΈΔ
fl VUUFS@VTF IUUQTHJUIVCDPNXBTBCFFG fl VUUFS@VTF
ΧελϜϑοΫͷςετ w 'MVUUFS)PPLT5FTUJOH-JCSBSZΛར༻ͯ͠ΈΔ w SFBDUIPPLTUFTUJOHMJCSBSZͷ'MVUUFS൛
ϥΠϒϥϦΛར༻͠ͳ͍߹ testWidgets('usePrevious', (tester) async { await tester.pumpWidget( HookBuilder(builder: (context) {
usePrevious(42); return const SizedBox(); }), ); await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(21); return const SizedBox(); }), ); final element = tester.element(find.byType(HookBuilder)); expect( element .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) .toStringDeep(), equalsIgnoringHashCodes( 'HookBuilder\n' ' │ usePrevious: 42\n' ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', ), ); }); VTF1SFWJPVT'MVUUFS)PPLTʹඪ ४Ͱؚ·Ε͍ͯΔ༩͑ͨͷҰͭલͷ ঢ়ଶΛอ࣋͢ΔΧελϜϑοΫ ͜ͷϑοΫΛςετॻ͘߹ʹ࠷ Ͱ͜Ε͚ͩͷίʔυྔͰ͢
testWidgets('usePrevious', (tester) async { / / ... await tester.pumpWidget( HookBuilder(builder:
(context) { usePrevious(42); return const SizedBox(); }), ); / / ... }); ϥΠϒϥϦΛར༻͠ͳ͍߹ গ͠ࡉ͔͘આ໌͍ͯ͘͠ͱɺ QVNQ8JEHFUͱ͍͏ςετ༻Ο δΣοτͷঢ়ଶΛཧ͍ͯ͠Δؔʹ )PPL#VJMEFSΛͯ͠ɺͦͷதͰΧε λϜϑοΫΛ͏ඞཁ͕͋Δ
testWidgets('usePrevious', (tester) async { / / ... await tester.pumpWidget( HookBuilder(builder:
(context) { usePrevious(21); return const SizedBox(); }), ); / / ... }); ϥΠϒϥϦΛར༻͠ͳ͍߹ ͏Ұߋ৽͠ͳ͍ͱ͍͚ͳ͍ͷͰɺ ઌ΄Ͳ͕ͩͬͨࠓճΛ ͍ͯ͠Δ
testWidgets('usePrevious', (tester) async { // .. . final element =
tester.element(find.byType(HookBuilder)); expect( element .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) .toStringDeep(), equalsIgnoringHashCodes( 'HookBuilder\n' ' │ usePrevious: 42\n' ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', ), ); // .. . }); VTF1SFWJPVTͱ͍͏ҰͭલͷΛอ࣋ ͢ΔͷͰ࠷ॳʹ͕ͨ͠ݱࡏอ࣋ ͍ͯ͠ΔͱͳΔ ͜ΕΛνΣοΫ͢ΔͨΊʹςετΟ δΣοτͷϊʔυΛจࣈྻ͔Βϋο γϡίʔυͰൺֱ͍ͯ͠Δ ϥΠϒϥϦΛར༻͠ͳ͍߹
testWidgets('usePrevious', (tester) async { await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(42);
return const SizedBox(); }), ); await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(21); return const SizedBox(); }), ); final element = tester.element(find.byType(HookBuilder)); expect( element .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) .toStringDeep(), equalsIgnoringHashCodes( 'HookBuilder\n' ' │ usePrevious: 42\n' ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', ), ); }); ϥΠϒϥϦΛར༻͠ͳ͍߹
testWidgets('usePrevious', (tester) async { final result = await buildHook( (value)
= > usePrevious(value), initialProps: 42, ); await result.rebuild(21); expect(result.current, 42); }); testWidgets('usePrevious', (tester) async { await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(42); return const SizedBox(); }), ); await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(21); return const SizedBox(); }), ); final element = tester.element(find.byType(HookBuilder)); expect( element .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) .toStringDeep(), equalsIgnoringHashCodes( 'HookBuilder\n' ' │ usePrevious: 42\n' ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', ), ); }); ར༻͠ͳ͍ ར༻͢Δ ΧελϜϑοΫͷঢ়ଶΛߋ৽͢ΔͨΊʹ UFTUFSQVNQ8JEHFUʹ)PPL#VJMEFSΛຖ ճ͍ͯͨ͠ͷΛলུͰ͖ɺFYQFDU࣌ͷ ঢ়ଶऔಘγϯϓϧʹॻ͚Δ ϥΠϒϥϦΛར༻͢Δ߹
testWidgets('usePrevious', (tester) async { final result = await buildHook( (value)
= > usePrevious(value), initialProps: 42, ); await result.rebuild(21); expect(result.current, 42); }); 'MVUUFS)PPLT5FTUJOH-JCSBSZΛར༻ͨ͠ ߹ଟ͘ͷίʔυΛݮ͢Δ͜ͱ͕Ͱ͖Δ CVJME)PPLɿͷΫϩʔδϟʹΧελϜϑοΫͷ ݺͼग़͠Λॻ͖ɺୈೋҾʹॳظΛ༩͑Δ͜ ͱ͕Ͱ͖Δ SFCVJMEɿCVJME)PPLͰੜ͞ΕͨΫϥεͰ Λߋ৽͠ϦϏϧυͰ͖Δ DVSSFOUɿอ͍࣋ͯ͠ΔΛऔಘͰ͖Δ ϥΠϒϥϦΛར༻͢Δ߹
'MVUUFS)PPLT5FTUJOH-JCSBSZ IUUQTHJUIVCDPNXBTBCFFG fl VUUFS@IPPLT@UFTU
'MVUUFS)PPLTͷ-JOU w fl VUUFS@IPPLT@MJOU@QMVHJOΛར༻ͯ͠ΈΔ w FTMJOUQMVHJOSFBDUIPPLTͷ'MVUUFS൛ IUUQTHJUIVCDPNNKIE fl VUUFS@IPPLT@MJOU@QMVHJO
final variable1 = callSomething(); final variable2 = callSomething(); // ෆඞཁ·ͨෆ͍ͯ͠ΔΩʔ
useEffect(() { print(variable1); }, [variable2]); // ϑοΫΛ݅ذͷதʹೖΕͳ͍ if (flag) { final variable = useState('hello'); } ϑοΫʹ͍͔ͭ͘ͷϧʔϧ͕͋Γ·͢ ྫ w ΧελϜϑοΫͷ໊ؔʹVTF999 ͱ͍͏໊લΛ͚ͭΔ ɾϑοΫΛ݅ذʹೖΕͳ͍ ͜ͷ1MVHJOΛ͏͜ͱͰόάΛ͙ͱڞ ʹϨϏϡʔͷίετͳͲͷԼ͛Δ fl VUUFS@IPPLT@MJOU@QMVHJOΛར༻ͯ͠ΈΔ
ࠓޙͷల
ࠓޙͷల w )PPLTʹΑΔੈք؍Λ'MVUUFSք۾ʹਁಁ͍͖͍ͤͯͨ͞ w ੵۃతʹ'MVUUFSΑΓྺ࢙ͷ͋Δ3FBDU͔ΒऔΓೖΕ͍ͯ͘ w ྺ࢙3FBDUʼ'MVUUFSʼ+FUQBDL$PNQPTF4XJGU6*ͳͷͰ 'MVUUFSͷݟΛ+FUQBDL$PNQPTFͳͲʹస༻͍ͯ͘͠ w 3FBDU2VFSZʹΑΔγϯϓϧͳઃܭΛ'MVUUFSʹऔΓೖΕ͍ͨ
w ΞϓϦΞʔΩςΫνϟʹ࣌ؒΛ͔͚ͳ͍ੈք؍ͷ044࡞Δ w 3FDPJMɺ.77.ͳͲͷֶशʹ࣌ؒΛΘͣ؆୯ͳΞϓϦΛ؆୯ʹ࡞Δ ग़యɿIUUQTXXXJSBTVUPZBDPN