언어: English | Português | Chinese | Japanese | Korean
Dart 언어를 위한 MobX.
투명한 함수형 반응형 프로그래밍(TFRP, Transparent Functional Reactive Programming)을 사용하여 Dart 앱의 상태 관리를 강화합니다.
MobX는 애플리케이션의 반응형 데이터를 UI와 간편하게 연결할 수 있는 상태 관리 라이브러리입니다. 이 연결은 완전히 자동으로 이루어지며 매우 자연스럽게 느껴집니다. 애플리케이션 개발자는 두 데이터를 동기화할 걱정 없이 UI(및 다른 곳)에서 어떤 리액티브 데이터를 사용해야 하는지에만 집중할 수 있습니다.
이것은 정말 마법은 아니지만, 무엇이(observables) 어디에서(reactions) 소비되고 있는지를 파악하고 자동으로 추적하는 스마트한 기능을 갖추고 있습니다. observables 이 변경되면 모든 reactions 이 다시 실행됩니다. 흥미로운 점은 이러한 반응이 단순한 콘솔 로그, 네트워크 호출, UI 재렌더링 등 무엇이든 될 수 있다는 것 입니다.
MobX는 자바스크립트 앱에 매우 효과적인 라이브러리였으며, 이번 Dart 언어 포팅은 동일한 수준의 생산성을 제공하는 것을 목표로 합니다.
우리는 후원자들에게 매우 감사하게 생각합니다. 덕분에 우리는 오픈소스 소프트웨어(OSS) 프로그램의 일부가 될 수 있었습니다. [후원하기]
MobX.dart 웹사이트의 시작하기 가이드를 따르세요.
MobX에 대한 자세한 내용은 MobX 빠른 시작 가이드를 참조하세요. 이 책에서는 자바스크립트 버전의 MobX를 사용하지만, 개념은 Dart와 Flutter에 100% 적용할 수 있습니다.
MobX의 핵심에는 세 가지 중요한 개념이 있습니다: Observables, Actions, Reactions.
observables은 애플리케이션의 반응 상태를 나타냅니다. 단순한 스칼라부터 복잡한 객체 트리까지 다양합니다. 애플리케이션의 상태를 observables 트리로 정의하면 UI(또는 앱의 다른 observer)가 소비하는 reactive-state-tree 를 노출할 수 있습니다.
간단한 리액티브 카운터는 다음과 같은 observables로 표현됩니다:
import 'package:mobx/mobx.dart';
final counter = Observable(0);
클래스와 같은 더 복잡한 observables도 만들 수 있습니다.
class Counter {
Counter() {
increment = Action(_increment);
}
final _value = Observable(0);
int get value => _value.value;
set value(int newValue) => _value.value = newValue;
Action increment;
void _increment() {
_value.value++;
}
}
언뜻 보기에 이 코드는 상용구 코드처럼 보이지만 금방 지겨워질 수 있습니다! 그래서 위의 코드를 다음과 같이 대체할 수 있는 mobx_codegen 를 추가했습니다:
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
어노테이션을 사용하여 클래스의 observables 속성을 표시하는 것에 주목하세요. 예, 여기에는 헤더 상용구가 있지만 모든 클래스에 대해 고정되어 있습니다. 더 복잡한 클래스를 만들면 이 상용구는 사라지고 대부분 중괄호 안의 코드에 집중하게 될 것입니다.
참고: 어노테이션은 mobx_codegen 패키지를 통해 사용할 수 있습니다.
코드를 줄이려면 @observable
을 @readonly
로 바꿀 수 있습니다.
모든 비공개 변수에 대해 스토어 클라이언트에서 값을 변경할 수 없도록 공개 getter를 생성합니다. 자세한 내용은 여기를 참조하세요.
도출할 수 있는 것은 반드시 도출해야 합니다. 자동으로.
애플리케이션의 상태는 핵심 상태(core-state) 와 파생 상태(derived-state) 로 구성됩니다. 핵심 상태(core-state) 는 현재 다루고 있는 도메인에 고유한 상태입니다. 예를 들어, Contact
엔티티가 있는 경우 firstName
과 lastName
은 Contact
의 core-state 를 구성합니다. 그러나 fullName
은 firstName
과 lastName
을 결합하여 얻은 파생 상태(derived-state) 입니다.
core-state 또는 other derived-state 에 의존하는 이러한 derived state 를 Computed Observable 이라고 합니다. observables이 변경되면 자동으로 동기화 상태를 유지합니다.
MobX의 상태 = 핵심 상태 + 파생 상태
import 'package:mobx/mobx.dart';
part 'contact.g.dart';
class Contact = ContactBase with _$Contact;
abstract class ContactBase with Store {
@observable
String firstName;
@observable
String lastName;
@computed
String get fullName => '$firstName, $lastName';
}
위의 예에서 firstName
및/또는 lastName
이 변경되면 fullName
이 자동으로 동기화됩니다.
action은 observables을 변경하는 방법입니다. 액션은 직접 변경하는 대신 변화에 의미론적 의미를 추가합니다.
예를 들어, 단순히 value+
를 실행하는 대신 increment()
action을 실행하면 더 많은 의미를 전달할 수 있습니다.
또한, action은 모든 알림을 일괄 처리하여 변경이 완료된 후에만 알림을 받도록 합니다.
따라서 observer들은 action이 원자적으로 완료될 때만 알림을 받습니다.
action은 중첩될 수도 있으며, 이 경우 최상위 action이 완료되면 알림이 발송됩니다.
final counter = Observable(0);
final increment = Action((){
counter.value++;
});
클래스 내에서 action을 만들 때 어노테이션을 활용할 수 있습니다!
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
MobX.dart는 비동기 동작을 자동으로 처리하며 runInAction
로 코드를 래핑할 필요가 없습니다.
@observable
String stuff = '';
@observable
bool loading = false;
@action
Future<void> loadStuff() async {
loading = true; //This notifies observers
stuff = await fetchStuff();
loading = false; //This also notifies observers
}
reaction은 observables, actions, reactions의 MobX triad 를 완성합니다.
이들은 반응형 시스템의 observer이며, 추적하는 observable이 변경될 때마다 알림을 받습니다.
변경될 때마다 알림을 받습니다. reaction은 아래와 같이 몇 가지 종류가 있습니다. 이들 모두는 reaction을 삭제(dispose)기 위해 호출할 수 있는 함수인 ReactionDisposer
를 반환합니다.
reaction의 눈에 띄는 특징 중 하나는 명시적인 연결 없이 모든 observable을 자동으로 추적 한다는 것입니다. reaction 내에서 observable을 읽는 행위만으로도 충분히 추적할 수 있습니다!
MobX로 작성하는 코드는 말 그대로 의식이 없는 것처럼 보입니다!
ReactionDisposer autorun(Function(Reaction) fn)
Runs the reaction immediately and also on any change in the observables used inside
fn
.
import 'package:mobx/mobx.dart';
final greeting = Observable('Hello World');
final dispose = autorun((_){
print(greeting.value);
});
greeting.value = 'Hello MobX';
// Done with the autorun()
dispose();
// Prints:
// Hello World
// Hello MobX
ReactionDisposer reaction<T>(T Function(Reaction) predicate, void Function(T) effect)
predicate()
함수 내에서 사용되는 observables를 모니터링하고, predicate()
가 다른 값을 반환하면 effect()
를 실행합니다.
predicate()
내부의 observables만 추적됩니다.
import 'package:mobx/mobx.dart';
final greeting = Observable('Hello World');
final dispose = reaction((_) => greeting.value, (msg) => print(msg));
greeting.value = 'Hello MobX'; // Cause a change
// Done with the reaction()
dispose();
// Prints:
// Hello MobX
ReactionDisposer when(bool Function(Reaction) predicate, void Function() effect)
when
은 predicate()
내부에 사용된 observables를 관찰하고 true
를 반환할 때 effect()
를 실행합니다. effect()
가 실행된 후에는 when
가 자동으로 폐기됩니다. 따라서 when 은 일회성 reaction
으로 생각할 수 있습니다. 또한 when()
을 미리 폐기할 수도 있습니다.
import 'package:mobx/mobx.dart';
final greeting = Observable('Hello World');
final dispose = when((_) => greeting.value == 'Hello MobX', () => print('Someone greeted MobX'));
greeting.value = 'Hello MobX'; // Causes a change, runs effect and disposes
// Prints:
// Someone greeted MobX
Future<void> asyncWhen(bool Function(Reaction) predicate)
when
과 비슷하지만 Future
를 반환합니다. 이는 predicate()
가 true
를 반환할 때 완료됩니다. 이는 predicate()
가 true
가 될 때까지 기다릴 수 있는 편리한 방법입니다.
final completed = Observable(false);
void waitForCompletion() async {
await asyncWhen(() => _completed.value == true);
print('Completed');
}
Observer
앱에서 가장 시각적으로 반응하는 부분 중 하나는 UI입니다. Observer 위젯([flutter_mobx
]https://github.com/mobxjs/mobx.dart/tree/master/flutter_mobx) 패키지의 일부인)은 builder
함수에서 사용되는 observables에 대한 세분화된 observer를 제공합니다. 이러한 observables가 변경될 때마다 Observer
는 다시 빌드하고 렌더링합니다.
아래는 Counter 예제 전체입니다.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
abstract class CounterBase with Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
}
class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);
@override
_CounterExampleState createState() => _CounterExampleState();
}
class _CounterExampleState extends State<CounterExample> {
final _counter = Counter();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
여기까지 읽으셨다면 🎉🎉🎉. MobX.dart
의 성장하는 커뮤니티에 기여할 수 있는 몇 가지 방법이 있습니다.
- "good first issue"로 표시된 issue를 선택합니다
- 기능, 개선 사항 제안하기
- 버그 신고하기
- 버그 수정하기
- 토론에 참여하여 의사 결정에 도움 주기
- 문서를 작성하고 개선하세요. 문서화는 매우 중요하며 그 중요성은 아무리 강조해도 지나치지 않습니다!
- 풀 리퀘스트 보내기 :-)
- 에서 채팅에 참여하세요
Thanks goes to these wonderful people (emoji key):
이 프로젝트는 all-contributors 규정을 따릅니다. 모든 종류의 기여를 환영합니다!