공식 문서 읽기 프로젝트 - GetX 상태관리

Broccolism·2021년 7월 13일
5
post-thumbnail


https://pub.dev/packages/get

이 글은 플러터에서 사용 가능한 프레임워크 중 하나인 GetX에 대한 글입니다. 공식 github repository의 README.md에서 얻은 정보를 재구성한 내용임을 알려드립니다.


3줄 요약

  1. GetX 상태 관리 방식에는 2가지가 있고, 상황에 따라 맞춰 쓰면 됩니다.
  2. 기존 방식과 유사하고 커스터마이징이 가능한 방식: simple manager
  3. 문법이 간단하고 worker를 쓸 수 있는 방식: reactive manager

GetX의 철학

이 패키지의 목표는 당신에게 최소의 종속성(pubspec의 dependencies)으로 라우트/상태/종속성 관리를 위한 완전한 솔루션을 제공하는 것입니다.

  1. Performance: 리소스를 적게 사용하면서 좋은 성능을 내는 것
  2. Productivity: 쉬운 문법 + garbage collection
  3. Organization: 뷰 / 비즈니스로직의 분리 + 프레젠테이션 로직 분리

이러한 철학을 바탕으로 GetX에서는 3가지 Manager를 제공합니다. 바로 상태 관리자, 종속성 (혹은 의존성) 관리자, 그리고 라우트 관리자입니다. 3가지 관리자 모두에 해당되는 GetX의 가장 큰 특징은 바로, '간결한 코드'입니다. 이번 글에서는 상태 관리자에 대해 다룹니다.


GetX 상태 관리자는 2가지가 있습니다.

  1. Simple State Manager
  2. Reactive State Manager

먼저 simple manager부터 알아봅시다.

1. Simple Manager

지금껏 플러터에서 흔히 사용된 Provider-Consumer 패턴과 아주 유사합니다.

  • getBuilder를 사용합니다.
  • GetX의 또다른 상태 관리자보다 메모리를 덜 차지합니다. (하지만 엄청나게 큰 차이가 있는 것은 아닙니다)
  • "수동적인 상태 관리자"라고도 부릅니다.
// 컨트롤러 파일
class BuilderController extends GetxController {
	var count = 0;
	
	increment() {
		count++;
		update(); // => 기존 Provider 패턴에서 notify 역할을 합니다.
	}
}

// 뷰 파일
GetBuilder<BuilderController> ( //타입 명시 안 하면 버그
	init: BuilderController(),
	builder: (_) {
		return Text("count: ${_.count}");
	},
),

// GetBuilder 밖에서 컨트롤러 찾기
Get.find<BuilderController>().increment();

controller.increment();

어디서 많이 본 것 같은 패턴이지 않나요? 기존 방식처럼 update() 라는 코드를 직접 적어줘야 상태가 바뀐 것을 알릴 수 있기 때문에 '수동적인 상태 관리자'라고도 부릅니다.

onInit(), onClose() 함수

아래 코드는 기존 뷰에서 initState()를 해주던 방식과 유사합니다.

GetBuilder<Controller>(
  initState: (_) => Controller.to.fetchApi(),
  dispose: (_) => Controller.to.closeStreams(),
  builder: (s) => Text('${s.username}'),
)

하지만 문서에서는 빌더가 아닌 컨트롤러 코드를 적는 것을 권장하고 있습니다.


void onInit() { // initState()
  fetchApi();
  super.onInit();
}


void onClose() { // stream 닫기
	// ...
	super.onClose();
}

UniqeID

위젯에 아이디를 지정해서, 해당 위젯에서만 재 랜더링이 일어나게 할 수 있습니다. 그러니까 똑같은 컨트롤러 A로부터 상태를 받고 있는 위젯 X, Y가 있다면 X, Y에 각각 ID를 부여해서 원하는 위젯 한 쪽에만 다시 랜더링이 일어나게 할 수 있습니다.

또한 업데이트 함수에 추가적인 parameter를 넘겨서, 원하는 조건이 만족될 때만 랜더링이 일어나도록 할 수 있습니다. 이 때 랜더링만 다시 되지 않는 것이고 값은 업데이트됩니다.

아래 예제에서는 counter 변수 값이 5 이하일 때, wanna update 이라는 아이디를 갖고 있는 첫 번째 위젯만 재 랜더링이 일어납니다.

// 컨트롤러 파일
class BuilderController extends GetxController {
  final counter = 0.obs;

  increment() {
    counter.value++;
    update(["wanna update"], counter <= 5);
  }
}

// 뷰 파일
GetBuilder<BuilderController>(
    id: "wanna update",
    init: controller,
    builder: (_) => Text(
        '${Get.find<BuilderController>().counter.value}', 
        style: TextStyle(fontSize: 30),
    ),
  ),
  GetBuilder<BuilderController>(
    id: "don't wanna update",
    builder: (_) => Text(
      '${Get.find<BuilderController>().counter.value}', 
      style: TextStyle(fontSize: 30),
    ),
),

2. Reactive Manager

GetX 고유의 상태 관리자라고 할 수 있습니다.

  • Obx 를 사용합니다.
  • stream, notifier가 필요없습니다. 내부적으로 GetValue, GetStream을 생성하기 때문에 성능이 좋습니다.
  • 앞서 소개한 'GetX의 철학'이 적용된 상태 관리자입니다.
// 컨트롤러 파일
class Controller extends GetxController {
  final count = 0.obs;
  increment() => count.value++;
}

// 뷰 파일
final Controller controller = Get.put(Controller());
// ...
Obx(() => Text("count: ${controller.count.value}"));

Observable 변수 (Reactive 변수)

= Obx 함수를 사용해서 변화를 감지할 수 있는 변수입니다.

  • Obx = Observer of Rx (변수의 관찰자)
  • 내부적으로는 StatefulWidget을 사용합니다.
  • 이 변수의 값이 바뀌고, 뷰의 수정이 필요하다면 GetX에서 자동으로 랜더링합니다.
  • 뷰의 수정이 필요없다면 (e.g: 현재 화면에 보이는 것과 동일한 값으로 수정) re-build는 일어나지 않습니다.
    • '자동': GetBuilder를 사용할 때 썼던 update() 함수가 필요없다는 의미!

변수를 observable하게 만드는 3가지 방법

모든 변수는 observable 변수로 만들 수 있습니다.

  • 클래스 인스턴스, 리스트 등도 가능합니다.

다트가 null safety를 지향하기(로 했기) 때문에, 선언과 동시에 초기화 해 주는 것을 권장하고 있습니다.

  1. Rx{type} 사용: 초기값 설정이 필수는 아니지만 권장됨.
        final name = RxString('');
        final isLogged = RxBool(false);
        final count = RxInt(0);
  1. Rx<type> 사용
        final name = Rx<String>('');
        final isLogged = Rx<Bool>(false);
        final count = Rx<Int>(0);
  1. .obs 사용
        final name = ''.obs;
        final isLogged = false.obs;
        final count = 0.obs;

공식 문서의 예제에서는 3번 방식을 가장 많이 사용하고 있습니다.

.value

primitive type (int, String 등)값에 접근할 때 사용하고, 클래스 인스턴스에 접근할 때에도 필요합니다. 하지만 리스트 접근 시에는 없어도 됩니다.

    final title = "this is a title".obs;
    Text(controller.title.value);

    final user = User().obs;
    Text("Name ${user.value.name}: Age: ${user.value.age}")

    final list = [1, 2, 3, 4, 5].obs;
    ListView.builder (
      itemCount: controller.list.length // lists don't need it
    );

Worker

reactive manager 방식을 쓸 때에만 사용 가능한 콜백 함수입니다. 총 4가지가 있습니다. (1, 2번은 대상의 타입만 다르고 하는 일은 같습니다.)

  1. ever: 변수가 업데이트 될 때마다 실행
  2. everAll: ever 와 같은 역할. 대신 리스트를 다룰 때 사용
  3. once: 변수의 값이 첫번째로 바뀌었을 때만 실행
  4. debounce: 변수의 값이 마지막으로 바뀐 지 특정 시간이 지난 후에 실행
  5. interval: 변수의 값이 바뀌는동안 특정 시간마다 실행

공식문서 피셜, 쓰면 좋을 것 같은 상황은

  • debounce: 검색 API

입니다. 사용자가 '고구마'를 검색하고자 할 때 'ㄱ', 'ㅗ', 'ㄱ', ... 이런식으로 한 글자씩 입력 할 때마다 검색 request를 보내면 매우 비효율적입니다. debounce를 사용하면 사용자가 빠르게 '고ㄱ' 까지 입력하다가 잠깐 쉬는 타이밍, 이 타이밍을 감지해서 그 때에만 한번 실행됩니다. 사용자가 다시 입력을 시작해서 '고구마'까지 완성시키고 동작을 멈추면 그 때 또 한번 실행됩니다. 따라서 '실시간 검색이 되는 것처럼 보이는' 효과를 줄 수 있죠.

그래서 언제 뭘 쓰나?

  • GetBuilder: init 등 좀 더 세분화된 커스터마이징이 필요할 때
  • GetX/Obx: 간결한 코드. Worker를 쓰고 싶을 때

"개별" 위젯이 많은 경우에는 GetX의 성능이 뛰어나고, 상태 변화가 여러 번 일어나는 경우에는 GetBuilder의 성능이 뛰어납니다.

참고

GetX에서는 컨트리뷰터를 환영합니다!🎉
저도 독스를 읽으면서 오타 및 자연스럽지 않은 문장이 있어 풀리퀘를 넣었는데, 주인장이 꽤 빠르게 반응을 해줬습니다. 2번째 풀리퀘는 넣자마자 거의 1분도 되지 않아 머지 되어서 놀랬던 기억이 있네요. 아무튼 한국어로 번역된 README.md도 있으니 가서 이상한 부분이 있으면 고쳐줍시다. 😇

작고 귀여운 내 컨트리뷰트 내역 ^,^
profile
코드도 적고 그림도 그리고 글도 씁니다. 넓고 얕은 경험을 쌓고 있습니다.

2개의 댓글

comment-user-thumbnail
2021년 9월 2일

아무 생각 없이 번역 시작했다가 생각보다 너무 길어서 지쳤었는데, 그래서 그런지 번역을 매끄럽게 못하고 오타도 있었나 보네요.

1개의 답글