자주 묻는 질문(FAQ)
다음은 커뮤니티에서 자주 묻는 질문입니다:
ref.refresh
와 ref.invalidate
의 차이점은 무엇인가요?
ref
에는 provider가 강제로 재계산(recompute)하도록 하는 두 가지 메서드가 있다는 것을 알고 어떻게 다른지 궁금하셨을 것입니다.
생각보다 간단합니다. ref.refresh
는 invalidate
+ read
를 위한 구문 설탕일 뿐입니다:
T refresh<T>(provider) {
invalidate(provider);
return read(provider);
}
provider를 다시 계산한 후 새 값을 신경 쓰지 않는다면 invalidate
를 사용면 됩니다.
신경 쓰는 경우에는 대신 refresh
를 사용하십시오.
이 로직은 린트 규칙을 통해 자동으로 적용됩니다.
반환된 값을 사용하지 않고 ref.refresh
를 사용하려고 하면 경고가 표시됩니다.
동작의 주요 차이점은 무효화(invalidating) 직후에 provider를 읽으면 provider가 즉시(immediately) 재계산(recomputes)한다는 것입니다.
반면, invalidate
를 호출하고 바로 읽지 않으면 업데이트가 나중에(later) 트리거됩니다.
이 "나중에(later)" 업데이트는 일반적으로 다음 프레임이 시작될 때 이루어집니다. 그러나 현재 수신 중(listened)이 아닌 provider가 무효화되면, 다시 수신(listen)할 때까지 업데이트되지 않습니다.
Ref와 WidgetRef 사이에 공유 인터페이스가 없는 이유는 무엇인가요?
Riverpod은 Ref
와 WidgetRef
를 자발적으로 분리합니다.
이는 조건부로 둘 중 하나에 의존하는 코드를 작성하지 않기 위해 의도적으로 수행됩니다.
한 가지 문제는 Ref
와 WidgetRef
가 비슷해 보이지만 미묘한 차이가 있다는 것입니다.
둘 다에 의존하는 코드는 발견하기 어려운 방식으로 불안정할 수 있습니다.
동시에 WidgetRef
에 의존하는 것은 BuildContext
에 의존하는 것과 같습니다.
이는 사실상 UI 레이어에 로직을 넣는 것이므로 권장하지 않습니다.
이러한 코드는 항상 Ref
를 사용하도록 리팩터링해야 합니다.
이 문제에 대한 해결책은 일반적으로 로직을 Notifier
로 옮긴 다음(부가 작업 수행(Performing side effects) 참조), 로직을 해당 Notifier
의 메서드가 되도록 하는 것입니다.
이렇게 하면 위젯이 이 로직을 호출하고자 할 때 다음과 같은 내용을 작성할 수 있습니다:
ref.read(yourNotifierProvider.notifier).yourMethod();
yourMethod
는 다른 제공자와 상호작용하기 위해 Notifier
의 Ref
를 사용합니다.
원시(raw) StatelessWidget을 사용하는 대신 ConsumerWidget을 확장(extend)해야 하는 이유는 무엇인가요?
이는 InheritedWidget
API의 안타까운 제한 때문입니다.
몇 가지 문제가 있습니다:
InheritedWidget
으로 "on change" 리스너를 구현할 수 없습니다. 즉,ref.listen
과 같은 것을BuildContext
와 함께 사용할 수 없습니다.State.didChangeDependencies
가 가장 비슷하지만 신뢰할 수 없습니다. 한 가지 문제는 특히 위젯 트리가 GlobalKey를 사용하는 경우(일부 Flutter 위젯은 이미 내부적으로 그렇게 하고 있습니다) 종속성이 변경되지 않더라도 수명 주기가 트리거될 수 있다는 것입니다.InheritedWidget
을 수신하는 위젯은 수신이 중단되지 않습니다. 이는 일반적으로 "테마" 또는 "미디어 쿼리"와 같은 순수한 메타데이터의 경우 괜찮습니다.비즈니스 로직의 경우 이는 문제가 됩니다. 페이지가 지정된 API를 표현하기 위해 provider를 사용한다고 가정해 봅시다. 페이지 오프셋이 변경되면 위젯이 이전에 표시된 페이지를 계속 수신하고 싶지 않을 것입니다.
InheritedWidget
은 위젯이 언제 청취(listening)를 중단하는지 추적할 수 있는 방법이 없습니다. Riverpod은 때때로 provider가 수신 대기 중인지 여부를 추적하는 데 의존합니다.
이 기능은 자동 폐기 메커니즘(auto dispose mechanism)과 provider에게 인수를 전달하는 기능 모두에 매우 중요합니다.
이러한 기능들이 Riverpod를 강력하게 만드는 원동력입니다.
먼 미래에는 이러한 문제가 해결될 수도 있습니다. 이 경우 Riverpod는 Ref
대신 BuildContext
를 사용하도록 마이그레이션할 것입니다.
이렇게 되면 ConsumerWidget
대신 StatelessWidget
을 사용할 수 있게 됩니다.
하지만 그건 다음 기회에!
hooks_riverpod이 flutter_hooks을 export하지 않는 이유는?
이는 올바른 버전 관리 관행을 존중하기 위한 것입니다.
hooks_riverpod
은 flutter_hooks
없이 사용할 수 없지만, 두 패키지는 독립적으로 버전이 관리됩니다.
한 쪽에서는 변경 사항이 발생해도 다른 쪽에서는 발생하지 않을 수 있습니다.
Riverpod이 업데이트를 필터링할 때 ==
대신 identical
을 사용하는 이유는 무엇인가요?
Notifiers는 ==
대신 identical
을 사용하여 업데이트를 필터링합니다.
이는 riverpod 사용자들이 copyWith 구현을 위해 Freezed/built_value와 같은 코드 생성기를 사용하는 것이 일반적이기 때문입니다.
이러한 패키지는 ==
를 재정의하여 객체를 심층적으로 비교합니다.
객체 심층 비교는 상당히 많은 비용이 듭니다.
"비즈니스 로직" 모델에는 많은 프로퍼티가 있는 경향이 있습니다.
더 나쁜 것은 목록, 지도 등과 같은 컬렉션도 있다는 것입니다.
동시에 복잡한 "비즈니스" 객체를 사용할 때 대부분의 state = newState
호출은 항상 알림(notification)을 발생시킵니다.(그렇지 않으면 setter를 호출할 필요가 없습니다)
일반적으로 현재 상태와 새 상태가 같을 때 state = newState
를 호출하는 주된 경우는 원시 객체(primitive objects)(lists/classes/...가 아닌 ints, enums, strings)에 대한 것입니다.
이러한 객체는 "기본적으로 표준화(canonicalized by default)"됩니다. 이러한 객체가 같으면 일반적으로 "동일(identical)"합니다.
따라서 riverpod은 identical
을 사용하여 업데이트를 필터링하는 것은 두 가지 모두에 좋은 기본값을 갖기 위한 시도입니다.
객체 필터링에 크게 신경 쓰지 않고 코드 생성기가 기본적으로 ==
오버라이드를 생성하기 때문에 ==
가 비쌀 수 있는 복잡한 객체의 경우, identical
을 사용하면 리스너에게 효율적으로 알림을 제공할 수 있습니다.
동시에 단순한 객체의 경우 identical
은 중복 알림을 올바르게 필터링합니다.
마지막으로, Notifiers에서 updateShouldNotify
메서드를 재정의하여 이 동작을 변경할 수 있습니다.
모든 providers를 한 번에 재설정(reset)하는 방법이 있나요?
아니요, 모든 providers를 한 번에 재설정할 수 있는 방법은 없습니다.
이는 안티 패턴으로 간주되기 때문에 일부러 그렇게 한 것입니다. 모든 공급업체를 한 번에 재설정하면 재설정하지 않으려던 공급업체도 재설정되는 경우가 많습니다.
이는 사용자가 로그아웃할 때 애플리케이션의 상태를 초기화하려는 사용자가 주로 요청하는 기능입니다.
이 기능을 원하는 경우, 대신 사용자의 상태에 따라 달라지는 모든 것을 "user" provider를 ref.watch
로 설정해야 합니다.
그러면 사용자가 로그아웃하면 그에 따라 모든 providers가 자동으로 재설정되지만 다른 모든 것은 그대로 유지됩니다.
"Cannot use "ref" after the widget was disposed"라는 오류가 발생했는데 무엇이 문제인가요?
"Bad state: No ProviderScope found"이라는 동일한 문제의 이전 오류 메시지가 표시될 수도 있습니다.
이 오류는 더 이상 마운트되지 않은 위젯에서 ref
를 사용하려고 할 때 발생합니다.
이 오류는 일반적으로 await
이후에 발생합니다:
ElevatedButton(
onPressed: () async {
await future;
ref.read(...); // "위젯이 폐기된 후 "ref"를 사용할 수 없습니다." 를 throw할 수 있습니다.
}
)
해결책은 BuildContext
와 마찬가지로 ref
를 사용하기 전에 mounted
를 확인하는 것입니다:
ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // 더 이상 throw하지 않음
}
)