개발을 시작하는 이야기

Flutter의 Hooks 사용하기 01 본문

개발 이야기/Flutter

Flutter의 Hooks 사용하기 01

Teiresias 2024. 6. 17. 19:11

 

Flutter_hooks는  React에 있던 것을 Flutter에서 구현한 라이브러리다. 이름에서 알수 있듯 갈고리로 원래 있던 객체에 갈고리를 걸어 같이 수행하는 역할을 한다. 추상화를 통해 중복을 제거하고 위젯간의 코드 생산성을 증가시켜주는데에 큰 역할을 한다.

 

StatelessWidget과 StatefulWidget을 대신하여 HookWidget을 제공해준다. HookWidget을 사용하면 해당 위젯이 가진 Hook을 사용할 수 있다. 

 

StatefulWidget의 큰 단점중 하나는 initState나 dispose에서 로직을 재사용하기가 힘들다는 것이다. 예로 AnimationController를 들 수 있다.

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();
  }
}

AnimationController를 구현 한다면 initState(), didUpdateWidget(), dispose(), 3개의 구성요소를 생명주기에 맞춰 구현해야 한다. 이러한 방식은 코더에게 많은 작업을 요구하며,  작은 규모의 프로젝트라면 괜찮을지 몰라도 큰 규모에서는 메모리의 누수를 피할 수 없을 것이다.

 

Dart의 Mixin은 이 문제를 부분적으로 해결할 수 있지만 모든 문제를 해결하지 못한다. 주어진 Mixin은 클래스당 한 번만 사용할 수 있으며, Mixin과 클래스는 동일한 객체를 공유한다. 이는 동일한 이름으로 변수를 정의하는 경우 컴파일 실패부터 알 수 없는 동작까지 결과가 달라질 수 있음을 의미한다. 

 

Hooks는 이러한 문제들을 보완하는 솔루션을 제공한다.

class Example extends HookWidget {
  const Example({Key? key, required this.duration})
      : super(key: key);

  final Duration duration;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    return Container();
  }
}

이 코드는 이전 예제 코드와 완전히 동일한 동작 방식으로 실행된다. 여전히 AnimationController는 duration마다 업데이트 되고, disposes된다. 하지만 우리에게는 그러한 로직이 보이지 않는다. 이는 Hooks에 이미 다 구현이 되어 있기 때문에 우리는 구현된 기능을 가져다 갈고리처럼 걸어 사용하면 된다.

 

Hooks가 제공하는 대표적인 기능들은 다음과 같다.

  • useState : 변수를 생성하고 구독한다.
  • useEffect : 상태를 업데이트 하거나 선택적으로 취소하기에 유용하다.
  • useMemorized : 다양한 객체의 인스턴스를 캐싱한다.
  • useStream : Stream을 구독하고, AsyncSnapShot으로 현재 상태를 반환한다.
  • useFuture : Future를 구독하고, AsyncSnapShot으로 현재 상태를 반환한다.
  • useAnimation : Animation을 구독하고 해당 객체의 Value를 반환한다.
  • useReducer : state가 복잡해 useState를 사용할 수 없을때 사용한다.
  • userTextEditingController : TextEditingController를 생성한다.
  • useTapController : TapController를 생성한다.
  • useScrollController : ScrollController를 생성한다.
  • usePageController : PageController를 생성한다.

이보다 더 많고 다양한 기능을 제공하는데 자세한 내용은 공식문서에 살펴보면 설명이 되어있다.

 

Hooks를 사용하기 위한 규칙은 다음과 같다.

  1. 항상 앞에 use를 붙여야 한다.
  2. 무조건 호출 Hook를 수행해야 한다.
  3. Hook를 조건으로 묶으면 안된다.
Widget build(BuildContext context) {
  // use를 앞에 붙여서 사용해야 하며, 호출 후크를 수행해야 한다.
  useMyHook();
  
  // use없이 사용하면 안된다. ❌
  myHook();
  // ....
  
  // 조건으로 묶으면 안된다. ❌
  if (condition) {
    useMyHook();
  }
  // ....
}

Hook를 만드는 방법은 두가지가 있다.

  • Function

함수는 가장 일반적인 방법으로, 다른 후크를 결합하여 보다 복잡한 사용자 정의 후크를 생성할 수 있다. 이러한 경우에도 함수 앞에 관례적으로 use를 붙여서 사용한다.

다음 코드는 변수를 생성하고 값이 변경될 때마다 해당 값을 콘솔에 기록하는 사용자 지정 후크이다.

ValueNotifier<T> useLoggedState<T>([T initialData]) {
  final result = useState<T>(initialData);
  useValueChanged(result.value, (_, __) {
    print(result.value);
  });
  return result;
}
  • Class

후크가 너무 복잡해 진다면, 클래스로 확장 변환할 수 있다. 클래스로서 후크는 State클래스와 유사하며, 위젯의 수명주기와 initHook, dispose, setState 같은 메서드에서 접근할 수 있다.

일반적으로 함수 아래 클래스를 숨기는것이 좋으며, 다음 코드는 후크가 state된 시간동안 콘솔에 기록하는 사용자 지정 후크이다. 

Result useMyHook() {
  return use(const _TimeAlive());
}

class _TimeAlive extends Hook<void> {
  const _TimeAlive();

  @override
  _TimeAliveState createState() => _TimeAliveState();
}

class _TimeAliveState extends HookState<void, _TimeAlive> {
  DateTime start;

  @override
  void initHook() {
    super.initHook();
    start = DateTime.now();
  }

  @override
  void build(BuildContext context) {}

  @override
  void dispose() {
    print(DateTime.now().difference(start));
    super.dispose();
  }
}

 

 

 

 

참고자료

 

flutter_hooks | Flutter package

A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.

pub.dev

 

 

Making Sense of React Hooks

This week, Sophie Alpert and I presented the “Hooks” proposal at React Conf, followed by a deep dive from Ryan Florence:

medium.com

 

'개발 이야기 > Flutter' 카테고리의 다른 글

Flutter의 Freezed 사용하기  (0) 2024.06.19
Flutter의 Hooks 사용하기 02  (0) 2024.06.18
Flutter의 상태관리 02  (0) 2024.06.16
Flutter의 상태관리 01  (0) 2024.06.15
객체지향 프로그래밍 정리  (1) 2024.06.14