Hook'lar hakkında
Bu sayfa hook'ların ne olduğunu ve Riverpod ile nasıl ilişkili olduklarını açıklar.
"Hook'lar", Riverpod'dan bağımsız ayrı bir paketten gelen yaygın yardımcılardır:
flutter_hooks.
flutter_hooks tamamen ayrı bir paket olmasına ve (en azından doğrudan) Riverpod ile
bir ilgisi bulunmamasına rağmen, Riverpod ve flutter_hooks'u birlikte kullanmak
yaygın bir yaklaşımdır.
Hook'ları kullanmalı mısınız?
Hook'lar güçlü bir araçtır, ancak herkes için uygun değildir.
Riverpod'a yeni başladıysanız, hook'ları kullanmaktan kaçının.
Hook'lar faydalı olsa da Riverpod için gerekli değildir.
Riverpod yüzünden hook'ları kullanmaya başlamamalısınız. Aksine, hook'ları kullanmak
istediğiniz için kullanmaya başlamalısınız.
Hook'ları kullanmak bir ödünleşmedir. Sağlam ve yeniden kullanılabilir kod üretmek için harika olabilirler, ancak aynı zamanda öğrenilmesi gereken yeni bir kavramdır ve başlangıçta kafa karıştırıcı olabilirler. Hook'lar Flutter'ın temel kavramlarından biri değildir. Bu nedenle Flutter/Dart içinde yabancı bir öğe gibi hissettirirler.
Hook'lar nedir?
Hook'lar, widget'ların içinde kullanılan fonksiyonlardır. Mantığı daha yeniden kullanılabilir ve birleştirilebilir kılmak için StatefulWidget'lara bir alternatif olarak tasarlanmışlardır.
Hook'lar React kaynaklı bir kavramdır ve flutter_hooks,
React uygulamasının Flutter'a bir uyarlamasından ibarettir.
Bu nedenle evet, hook'lar Flutter'da biraz yersiz hissettirebilir. İdeal olarak
gelecekte, hook'ların çözdüğü soruna özellikle Flutter için tasarlanmış bir
çözümümüz olacaktır.
Riverpod'un provider'ları "global" uygulama durumu içinse, hook'lar yerel widget durumu
içindir. Hook'lar tipik olarak
TextEditingController,
AnimationController
gibi durum tutan kullanıcı arayüzü nesneleriyle çalışmak için kullanılır.
Ayrıca "builder" kalıbının yerini de alabilirler;
FutureBuilder/TweenAnimatedBuilder
gibi widget'ları "iç içe geçme" gerektirmeyen bir alternatifle değiştirerek okunabilirliği
çarpıcı biçimde artırırlar.
Genel olarak hook'lar şunlar için faydalıdır:
- Formlar
- Animasyonlar
- Kullanıcı olaylarına tepki vermek
- vb.
Örnek olarak, bir widget'ın görünmez başlayıp yavaşça belirdiği bir fade-in animasyonunu elle uygulamak için hook'ları kullanabiliriz.
StatefulWidget kullansaydık, kod şöyle görünürdü:
class FadeIn extends StatefulWidget {
const FadeIn({Key? key, required this.child}) : super(key: key);
final Widget child;
State<FadeIn> createState() => _FadeInState();
}
class _FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
late final AnimationController animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
void initState() {
super.initState();
animationController.forward();
}
void dispose() {
animationController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return Opacity(
opacity: animationController.value,
child: widget.child,
);
},
);
}
}
Hook'lar kullanıldığında bunun eşdeğeri şöyle olurdu:
class FadeIn extends HookWidget {
const FadeIn({Key? key, required this.child}) : super(key: key);
final Widget child;
Widget build(BuildContext context) {
// Bir AnimationController oluşturun. Controller, widget ağaçtan
// çıkarıldığında otomatik olarak yok edilecektir.
final animationController = useAnimationController(
duration: const Duration(seconds: 2),
);
// useEffect; initState + didUpdateWidget + dispose'un eşdeğeridir.
// useEffect'e aktarılan geri çağırma, hook ilk kez çağrıldığında ve
// ardından ikinci parametre olarak aktarılan liste her değiştiğinde çalıştırılır.
// Burada boş bir const liste aktardığımız için, bu tam olarak `initState`e eşdeğerdir.
useEffect(() {
// widget ilk kez oluşturulduğunda animasyonu başlat.
animationController.forward();
// İstersek burada bir miktar "dispose" mantığı döndürebilirdik
return null;
}, const []);
// Animasyon güncellendiğinde bu widget'ı yeniden oluşturmasını Flutter'a söyle.
// Bu, AnimatedBuilder'ın eşdeğeridir
useAnimation(animationController);
return Opacity(
opacity: animationController.value,
child: child,
);
}
}
Bu kodda dikkat çeken birkaç ilginç nokta var:
Bellek sızıntısı yok. Bu kod, widget her yeniden oluşturulduğunda yeni bir
AnimationControlleroluşturmaz ve widget ağaçtan çıkarıldığında controller doğru şekilde serbest bırakılır.Aynı widget içinde hook'ları istediğimiz kadar çok kez kullanabiliriz. Örneğin istersek birden fazla
AnimationControlleroluşturabiliriz:
Widget build(BuildContext context) {
final animationController = useAnimationController(
duration: const Duration(seconds: 2),
);
final anotherController = useAnimationController(
duration: const Duration(seconds: 2),
);
...
}Bu, hiçbir olumsuz sonucu olmadan iki controller oluşturur.
İstersek bu mantığı ayrı ve yeniden kullanılabilir bir fonksiyona ayıklayabiliriz:
double useFadeIn() {
final animationController = useAnimationController(
duration: const Duration(seconds: 2),
);
useEffect(() {
animationController.forward();
return null;
}, const []);
useAnimation(animationController);
return animationController.value;
}Ardından bu fonksiyonu, widget bir HookWidget olduğu sürece widget'larımızın içinde kullanabiliriz:
class FadeIn extends HookWidget {
const FadeIn({Key? key, required this.child}) : super(key: key);
final Widget child;
Widget build(BuildContext context) {
final fade = useFadeIn();
return Opacity(opacity: fade, child: child);
}
}useFadeInfonksiyonumuzunFadeInwidget'ımızdan tamamen bağımsız olduğuna dikkat edin.
İstersek ouseFadeInfonksiyonunu bambaşka bir widget'ta kullanabilirdik ve yine çalışırdı!
Hook'ların kuralları
Hook'ların kendine özgü kısıtlamaları vardır:
Yalnızca HookWidget'tan türeyen bir widget'ın
buildmetodu içinde kullanılabilirler:İyi:
class Example extends HookWidget {
Widget build(BuildContext context) {
final controller = useAnimationController();
...
}
}Kötü:
// HookWidget değil
class Example extends StatelessWidget {
Widget build(BuildContext context) {
final controller = useAnimationController();
...
}
}Kötü:
class Example extends HookWidget {
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// _Gerçekten_ "build" metodunun içinde değil, bunun yerine
// bir kullanıcı etkileşimi yaşam döngüsünün içinde (burada "on pressed").
final controller = useAnimationController();
},
child: Text('click me'),
);
}
}Koşullu olarak veya bir döngü içinde kullanılamazlar.
Kötü:
class Example extends HookWidget {
const Example({required this.condition, super.key});
final bool condition;
Widget build(BuildContext context) {
if (condition) {
// Hook'lar "if"lerin/"for"ların içinde kullanılmamalıdır, ...
final controller = useAnimationController();
}
...
}
}
Hook'lar hakkında daha fazla bilgi için flutter_hooks'a bakın.
Hook'lar ve Riverpod
Kurulum
Hook'lar Riverpod'dan bağımsız olduğu için ayrıca kurulmaları gerekir. Onları kullanmak isterseniz, hooks_riverpod paketini kurmanız yeterli değildir. Ayrıca bağımlılıklarınıza flutter_hooks'u da eklemeniz gerekir. Daha fazla bilgi için Başlarken) sayfasına bakın.
Kullanım
Bazı durumlarda hem hook'ları hem de Riverpod'u kullanan bir Widget yazmak isteyebilirsiniz.
Ancak muhtemelen fark ettiğiniz gibi, hem hook'lar hem de Riverpod kendi özel widget
temel türlerini sunar: HookWidget ve ConsumerWidget.
Fakat sınıflar aynı anda yalnızca tek bir üst sınıftan türeyebilir.
Bu sorunu çözmek için hooks_riverpod paketini kullanabilirsiniz.
Bu paket, hem HookWidget'ı hem de ConsumerWidget'ı tek bir türde birleştiren
bir HookConsumerWidget sınıfı sunar.
Dolayısıyla HookWidget yerine HookConsumerWidget'tan türetebilirsiniz:
// We extend HookConsumerWidget instead of HookWidget
class Example extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// We can use both hooks and providers here
final counter = useState(0);
final value = ref.watch(myProvider);
return Text('Hello $counter $value');
}
}
Alternatif olarak, her iki paketin sunduğu "builder"ları kullanabilirsiniz.
Örneğin StatelessWidget kullanmayı sürdürüp hem HookBuilder hem de Consumer
kullanabiliriz.
class Example extends StatelessWidget {
Widget build(BuildContext context) {
// We can use the builders provided by both packages
return Consumer(
builder: (context, ref, child) {
return HookBuilder(
builder: (context) {
final counter = useState(0);
final value = ref.watch(myProvider);
return Text('Hello $counter $value');
},
);
},
);
}
}
Bu yaklaşım hooks_riverpod kullanmadan da işe yarar. Yalnızca flutter_riverpod yeterlidir.
Bu yaklaşımı beğendiyseniz, hooks_riverpod her iki builder'ı tek bir yerde birleştiren HookConsumer'ı sunarak bunu kolaylaştırır:
class Example extends StatelessWidget {
Widget build(BuildContext context) {
// Equivalent to using both Consumer and HookBuilder.
return HookConsumer(
builder: (context, ref, child) {
final counter = useState(0);
final value = ref.watch(myProvider);
return Text('Hello $counter $value');
},
);
}
}