징웬 챌린지 참여 19일째 되는 날입니다. 자세한 내용은 다음을 확인하세요.업데이트 챌린지
MVVM이란 무엇이며 장점
MVVM은 Model-View-ViewModel의 줄임말입니다. 본질적으로 MVC의 개선된 버전입니다. MVVM은 뷰의 상태와 동작을 추상화하여 뷰 UI와 비즈니스 로직을 분리할 수 있습니다. 물론 ViewModel은 이미 이러한 작업을 수행했으며 Model의 데이터를 가져오고 콘텐츠를 표시해야 하므로 View와 관련된 비즈니스 로직을 처리하는 데 도움이 될 수 있습니다.
이점
MVVM 패턴은 MVC 패턴과 동일하며 주요 목적은보다(보기) 및 모델(모델),
1. 낮은 결합 . View는 Model과 독립적으로 변경 및 수정될 수 있습니다. ViewModel은 다른 "View"에 바인딩될 수 있습니다. View가 변경되면 Model은 변경되지 않은 상태로 유지되고 Model이 변경되면 View도 변경되지 않은 상태로 유지될 수 있습니다.
2. 재사용성 . ViewModel에 일부 보기 논리를 넣고 많은 보기에서 이 보기 논리를 재사용하도록 할 수 있습니다.
3. 독립적인 개발 . 개발자는 비즈니스 로직 및 데이터(ViewModel) 개발에 집중할 수 있고, 디자이너는 페이지 디자인에 집중할 수 있으며, Expression Blend를 사용하면 인터페이스를 쉽게 디자인하고 xaml 코드를 생성할 수 있습니다.
4. 테스트 가능 . 인터페이스는 항상 테스트하기 어려웠고 테스트는 ViewModel에 대해 작성될 수 있습니다.
View는 ViewModel에 바인딩한 다음 몇 가지 명령을 실행하여 ViewModel에서 작업을 요청합니다. 차례로 ViewModel은 모델과 통신하여 UI에 대한 응답으로 업데이트하도록 지시합니다. 따라서 애플리케이션용 UI를 매우 쉽게 구축할 수 있습니다.
Flutter에서 MVVM 패턴의 여러 가지 방법
타사 패키지를 사용하지 않을 때 공식도 좋은 선택을 제공합니다. 즉 StatefulWidget
, UI를 새로 고치기 위해 상태를 변경해야 할 때 setState()
메서드를 호출하기만 하면 됩니다.
이 방법은 간단하고 직접적이며 MVVM 패턴으로도 이해할 수 있지만 View와 Model은 여전히 함께 결합되어 있고 ViewModel은 적절한 역할을 하지 않습니다. 프로젝트가 커질수록 코드 setState()
는 점점 더 혼란스러워지고 때로는 호출하는 것을 잊어버려 setState()
문제를 찾는 데 많은 시간을 낭비하게 됩니다.
또한 초기에 공식적으로 제공되는 상태 관리 모드를 라고 BLOC
합니다. 이 방법은 타사 패키지에 의존하고 rxDart
Stream 방식으로 문제를 잘 해결합니다 setState()
. 그러나 이러한 종류의 학습은 어렵고 Flutter를 처음 접하는 사람들에게 친숙하지 않습니다. 나중에 상태 관리 및 종속성 주입을 위한 고급 도구인 타사 라이브러리가 등장했으며 Provider
배우기 쉽고 이해하기 쉽기 때문에 현재 첫 번째 선택으로 권장됩니다 Provider
.
ViewModel을 통한 상태 관리
MVVM 모드에서 APP를 개발하기 때문에 ViewModel은 필수입니다. 즉, state 속성이 변경되면 그에 상응하는 변경을 하기 위해 UI(즉, View 레이어)가 필요합니다.
상태가 변경되었는지 여부를 모니터링하는 데 도움 이 되는 Provider가 ChangeNotifierProvider
있으며 해당 하위 매개변수는 Consumer
상태를 사용하는 데 도움이 되는 변경입니다. 평신도의 관점에서 위젯의 빌드 메소드는 UI를 새로 고치기 위해 여기에서 호출됩니다.
그렇다면 상태 변경 알림을 트리거할 위치는 어디입니까? 대답은 를 사용 ChangeNotifier
하는 것입니다. notifyListeners()
메서드가 호출되면 리스너에 새로 고침을 알릴 수 있습니다 ChangeNotifierProvider
.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
child: Consumer<T>(
//Widget的builder方法与child
builder: widget.builder,
child: widget.child,
),
create: (BuildContext context) {
//这里是我们的ViewModel,一个ChangeNotifier
return model;
},
);
}
复制代码
이제 생산자와 소비자가 모두 있으므로 MVVM 패턴을 완성할 수 있습니다.
기본 페이지 로직 분석
일반적으로 페이지가 로드될 때 로드 페이지가 표시되고 로드 후에 데이터가 성공적으로 표시되고 실패하면 실패한 페이지가 표시됩니다.
![스크린샷 2021-06-20 11.47.16 PM](/Users/liaoyp/Desktop/Screenshot 2021-06-20 PM 11.47.16.png)
따라서 페이지 상태를 열거하십시오.
enum ViewState { Idle, Busy,Error }
复制代码
ViewModel은 페이지 상태 속성이 변경된 후 ui를 업데이트하고 일반적으로 notifyListeners를 호출하고 이 단계를 BaseModel로 이동합니다.
class BaseModel extends ChangeNotifier {
ViewState _state = ViewState.Loading;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
复制代码
ChangeNotifierProvider는 ui에 Model을 제공하고 ui를 Consumer로 업데이트해야 합니다. 그래서 BaseView에도 내장했습니다.
enum ViewState { Loading, Success, Failure, None }
class BaseModel extends ChangeNotifier {
ViewState _state = ViewState.None;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
final Widget Function(BuildContext context, T model, Widget child) builder;
final T model;
final Widget child;
final Function(T) onModelReady;
BaseWidget({
Key key,
this.builder,
this.model,
this.child,
this.onModelReady,
}) : super(key: key);
_BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}
class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
T model;
@override
void initState() {
model = widget.model;
if (widget.onModelReady != null) {
widget.onModelReady(model);
}
super.initState();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (BuildContext context) => model,
child: Consumer<T>(
builder: widget.builder,
child: widget.child,
),
);
}
}
复制代码
캡슐화된 기본 클래스로 로그인 페이지를 완료합니다.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BaseWidget<LoginViewModel>(
model: LoginViewModel(loginServive: LoginServive()),
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: Text('provider'),
),
body: Column(
children: <Widget>[
model.state == ViewState.Loading
? Center(
child: CircularProgressIndicator(),
)
: Text(model.info),
FlatButton(
color: Colors.tealAccent,
onPressed: () => model.login("pwd"),
child: Text("登录")),
],
),
),
);
}
}
/// viewModel
class LoginViewModel extends BaseModel {
LoginServive _loginServive;
String info = '请登录';
LoginViewModel({@required LoginServive loginServive})
: _loginServive = loginServive;
Future<String> login(String pwd) async {
setState(ViewState.Loading);
info = await _loginServive.login(pwd);
setState(ViewState.Success);
}
}
/// api
class LoginServive {
static const String Login_path = 'xxxxxx';
Future<String> login(String pwd) async {
return new Future.delayed(const Duration(seconds: 1), () => "登录成功");
}
}
复制代码