9. Flutte3.0遙遙領(lǐng)先系列|一文教你完全掌握getX, 手寫getX框架

目錄:
1.優(yōu)點
2.優(yōu)點分析: GetX怎么將邏輯層和界面層解耦的
3.優(yōu)點分析: GetX怎么實現(xiàn)狀態(tài)管理的? Obx的基本原理是什么? 局部刷新原理? obx和obs?
4.優(yōu)點分析: binding基本原理是什么?
5.優(yōu)點分析: 路由管理基本原理是什么?
6.getx的缺點是啥?
7.手寫getX

1. GetX相關(guān)優(yōu)勢

1.1 )依賴注入

GetX是通過依賴注入的方式,存儲相應(yīng)的XxxGetxController;已經(jīng)脫離了InheritedWidget那一套玩法,自己手動去管理這些實例,使用場景被大大拓展
簡單的思路,卻能產(chǎn)生深遠的影響:優(yōu)雅的跨頁面功能便是基于這種設(shè)計而實現(xiàn)的、獲取實例無需BuildContext、GetBuilder自動化的處理及其減少了入?yún)⒌鹊?/p>

1.2 )跨頁面交互的狀態(tài)管理

這絕對是GetX的一個優(yōu)點!對于復(fù)雜的生產(chǎn)環(huán)境,跨頁面交互的場景,實在太常見了,GetX的跨頁面交互,實現(xiàn)的也較為優(yōu)雅

1.3 )路由管理

getx內(nèi)部實現(xiàn)了路由管理,而且用起來,非常簡單!bloc沒實現(xiàn)路由管理,我不得不找一個star量高的路由框架,就選擇了fluro,但是不得不吐槽下,fluro用起來真的很折磨人,每次新建一個頁面,最讓我抗拒的就是去寫fluro路由代碼,橫跨幾個文件來回寫,頭皮發(fā)麻
GetX實現(xiàn)了動態(tài)路由傳參,也就是說直接在命名路由上拼參數(shù),然后能拿到這些拼在路由上的參數(shù),也就是說用flutter寫H5,直接能通過Url傳值,OMG!可以無腦舍棄復(fù)雜的fluro了

1.4 ) 實現(xiàn)了全局BuildContext
1.5 )國際化,主題實現(xiàn)
生命周期

用了Getx的state管理之后, 你再也用不著StatefulWidget了. 僅僅StatelessWidget就夠你用了! 性能自然也提升很多!

2. GetX怎么將邏輯層和界面層解耦的

此處需要劃分三個結(jié)構(gòu)了:state(狀態(tài)層),logic(邏輯層),view(界面層)

為什么寫成這樣三個模塊,需要把State單獨提出來,為了復(fù)雜的業(yè)務(wù), 顯的更簡單!

舉例:

之前的寫法

class IdentificationCard extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return IdentificationState();
  }
}

class IdentificationState extends State<IdentificationCard> {
  String date = "555";
  String name = "666";

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
                Text('date : $date'),
                Text('name : $name'), 
                GestureDetector(onTap: () {
                  setState(() {
                    date = "777";
                  });
                }, child: const Text('修改'))],
    );
  }
}

用getx的實現(xiàn)
新建一個類, 定義一個狀態(tài), GetxController
添加一個Obx就能自動監(jiān)聽狀態(tài)的改變并且刷新UI了

/// 狀態(tài)
class IdentificationState {
  RxString date = "555".obs;
  RxString name = "666".obs;
}

/// 業(yè)務(wù)邏輯
class IdentificationController extends GetxController {
  IdentificationState state = IdentificationState();
}

/// 展示
class IdentificationCard extends StatelessWidget {
  IdentificationController controller = Get.put(IdentificationController());

  @override
  Widget build(BuildContext context) {
    return Obx(() {
      return Column(
        children: [
          Text('date : ${controller.state.date}'),
          Text('name : ${controller.state.name}'),
          GestureDetector(
              onTap: () {
                controller.state.date.value = "777";
              },
              child: const Text('修改'))
        ],
      );
    });
  }
}

3.GetX實現(xiàn)狀態(tài)管理

GetX 的刷新方案分為手動刷新與自動刷新,GetBuilder 與 Obx ,分別對應(yīng)范圍刷新與局部刷新

2者的區(qū)別:

Obx 響應(yīng)式狀態(tài)管理

Obx 可以配合響應(yīng)式字段局部的精準(zhǔn)刷新避免父容器無效重構(gòu),缺點是字段變?yōu)轫憫?yīng)式的Rx包裝類,布局也需要被Obx包裹了,破壞了原生代碼觀賞性。

GetBuilder 狀態(tài)管理器

GetBuilder 就是指定區(qū)域范圍手動去刷新的,可以分區(qū)設(shè)置多個刷新區(qū)域,可選擇單個控件或容器,在一些特定場景下有奇效,但是如果不理解濫用一樣會導(dǎo)致性能問題。

3.1 Obx 的基本原理是什么?

Obx是配合Rx響應(yīng)式變量使用
這樣一來我們就明白了Obx實際上是一個StatefulWidget,它里面監(jiān)聽了一個GetStream,一旦GetStream有事件通知,它就會進行setState重新進行Widget的構(gòu)造.
GetBuilder 與 Obx 兩者結(jié)合,一個指定區(qū)域范圍手動刷新,一個是局部控件點對點刷新

var build = () => Text(name.value)

Obx(build);

源碼: Obx繼承了一個抽象ObxWidget類,將傳遞進來的build方法給了ObxWidget

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}

ObxWidget繼承了有狀態(tài)組件,并且build函數(shù)讓Obx類實現(xiàn)了

abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key? key}) : super(key: key);

  @override
  _ObxState createState() => _ObxState();

  @protected
  Widget build();
}
對GexX的狀態(tài)管理做一個簡單總結(jié),

基于Obx收集依賴狀態(tài), 實際一個StatefulWidget,它的State也就是ObxState中監(jiān)聽了GetStream事件流,通過接收GetStream事件流調(diào)用setState重新構(gòu)建Obx,Rx對象在改變value的時候會向GetStream事件流發(fā)送事件,這樣就會導(dǎo)致Obx進行刷新了.

Rx原理.jpg
3.2 GetBuilder

GetBuilder 是一個 Widget 組件, 在 GetX 的狀態(tài)管理中,GetBuilder 的主要作用是結(jié)合 GetxController 實現(xiàn)界面數(shù)據(jù)的更新
demo使用

class CounterBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => CounterController());
  }
}


class CounterController extends GetxController {
  int count = 0;
  
  void increase(){
    count += 1;
    update();
  }
}

class CounterPage extends StatelessWidget {

  final controller = Get.find<CounterController>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Counter"),
      ),
      body: Center(
        child: GetBuilder<CounterController>(builder: (logic) {
          return Text("${controller.count}", style: const TextStyle(fontSize: 50),);
        }),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: controller.increase,
      ),
    );
  }
}

demo調(diào)用總結(jié): 然后調(diào)用 update 方法更新界面數(shù)據(jù),從而實現(xiàn)計數(shù)器的功能。
狀態(tài)管理源碼分析:

class GetBuilder<T extends GetxController> extends StatefulWidget {
  final GetControllerBuilder<T> builder;
  final bool global;
  final Object? id;
  final String? tag;
  final bool autoRemove;
  final bool assignId;
  final Object Function(T value)? filter;
  final void Function(GetBuilderState<T> state)? initState,
      dispose,
      didChangeDependencies;
  final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
      didUpdateWidget;
  final T? init;

  const GetBuilder({
    Key? key,
    this.init,
    this.global = true,
    required this.builder,
    this.autoRemove = true,
    this.assignId = false,
    this.initState,
    this.filter,
    this.tag,
    this.dispose,
    this.id,
    this.didChangeDependencies,
    this.didUpdateWidget,
  }) : super(key: key);

  @override
  GetBuilderState<T> createState() => GetBuilderState<T>();
}

GetBuilder 是繼承自 StatefulWidget

GetBuilder 就是指定區(qū)域范圍手動去刷新的,可以分區(qū)設(shè)置多個刷新區(qū)域,可選擇單個控件或容器,在一些特定場景下有奇效,但是如果不理解濫用一樣會導(dǎo)致性能問題。

GetBuilderState

class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
    with GetStateUpdaterMixin {
  T? controller;
  bool? _isCreator = false;
  VoidCallback? _remove;
  Object? _filter;

  @override
  void initState() {...}

  void _subscribeToController() {...}

  void _filterUpdate() {...}

  @override
  void dispose() {...}

  @override
  void didChangeDependencies() {...}

  @override
  void didUpdateWidget(GetBuilder oldWidget) {...}

  @override
  Widget build(BuildContext context) {...}
}

方法: build()

@override
Widget build(BuildContext context) {
  return widget.builder(controller!);
}
getbuild.jpg

通過對 GetBuilder 的源碼分析,基本了解了 GetBuilder 各個參數(shù)的作用和實現(xiàn)原理。

GetBuilder 參數(shù)作用總結(jié)如下:

builder: Widget 構(gòu)建器,創(chuàng)建界面顯示的 Widget
init: 初始化 Controller 值,當(dāng) global 為 false 時使用該值作為 Controller,當(dāng) global 為 true 時且 Controller 未注冊依賴,則將 init 的值注入依賴使用。
global: 是否全局,作用于 Controller 初始化中,與 init 結(jié)合使用
autoRemove: 是否自動移除 Controller 依賴,結(jié)合 assignId 一起使用
assignId: 為 true 時結(jié)合 autoRemove 使用會自動移除 Controller 依賴關(guān)系
filter: 過濾器,通過返回值過濾是否需要刷新,返回值變化時才會刷新界面
tag: Controller 依賴注入的 tag,根據(jù) tag 獲取 Controller 實例
id: 刷新標(biāo)識,結(jié)合 Controller 的 update 使用,可以刷新指定 GetBuilder 控件內(nèi)的 Widget
initState: 回調(diào)函數(shù),生命周期 initState 方法中調(diào)用
dispose: 回調(diào)函數(shù),生命周期 dispose 中調(diào)用
didUpdateWidget: 回調(diào)函數(shù),生命周期 didUpdateWidget 中調(diào)用
didChangeDependencies: 回調(diào)函數(shù),生命周期 didChangeDependencies 中調(diào)用

4. 依賴管理: Binding:

依賴注入: 就是賦值, 但是很多類給你的類賦值, 這樣就很亂了
Binding的使用: 一般和controller在一起使用
binding模塊需要在getx路由頁面進行綁定;進入頁面的時候,統(tǒng)一懶注入binding模塊的GetXController

依賴注入.jpg
class _GetImpl extends GetInterface {}

final Get = _GetImpl();

extension Inst on GetInterface {
  S put<S>(S dependency,
          {String? tag,
          bool permanent = false,
          InstanceBuilderCallback<S>? builder}) =>
      GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
}
class GetInstance {
  factory GetInstance() => _getInstance ??= GetInstance._();

  const GetInstance._();

  static GetInstance? _getInstance;

  static final Map<String, _InstanceBuilderFactory> _singl = {};

  S put<S>(
    S dependency, {
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

  void _insert<S>({
    bool? isSingleton,
    String? name,
    bool permanent = false,
    required InstanceBuilderCallback<S> builder,
    bool fenix = false,
  }) {
    final key = _getKey(S, name);
    _singl.putIfAbsent(
      key,
      () => _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
      ),
    );
  }

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }

  S find<S>({String? tag}) {
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
      if (_singl[key] == null) {
        if (tag == null) {
          throw 'Class "$S" is not registered';
        } else {
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? _singl[key]!.getDependency() as S;
    } else {
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }
}

5. 路由管理之命名路由

特點:封裝了context, 封裝了攔截器!
路由管理之簡單路由

GetMaterialApp(
    unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
    routingCallback: (routing) {
      if(routing?.current == '/second'){
       ///處理一些業(yè)務(wù)
      }
    },
    initialRoute: '/',
    getPages: [
      GetPage(name: '/first', page: ()=>First()),
      GetPage(name: '/second', page: ()=>Second())
    ],
  )
路由管理.jpg
問題: 為何不用Flutter自己的Router系統(tǒng)?

使用時還需要有一個context實例. 但我們并不是隨時隨地都持有一個context的, 這也局限了我們的使用場景.

6. GETX的缺點:

第一個缺點

Get.to(widgetObj, bindings)是可以注入binding.

但是Get.toNamed()并不支持binding參數(shù)啊. 我的跳轉(zhuǎn)一般都是用toNamed的, 所以注定了這種方式我用不了.

第二個缺點

這個缺點很隱藏, 很容易出問題. 以上面的binding為例

HomeBinding中提供了 HomeController, Service 兩個對象
DetailsBinding中提供了 DetailsController 對象 但其實我們的Details頁中也會用到Service對象.
之所以不出現(xiàn)"details頁中說找不到Service"的crash, 是因為用戶先打開的home頁, Home已經(jīng)往Get中寫入了Service對象了, 所以等之后打開detail頁時, serivce對象已經(jīng)有了, 能夠Get.find()得到, 所以不會有NPE錯誤.

但要是deep link的場景呢?

: 你直接跳到了Detail頁, 結(jié)果就因為沒有經(jīng)過home頁, 所以Service service = Get.find()找不到service對象, 應(yīng)用會crash.

所以現(xiàn)在就明白了, 第二個缺點就是: 上面兩個Binding有隱藏的依賴性 DetailsBinding其實依賴于HomeBinding. HomeBinding不先放好service, 那DetailsBinding提供不了Serivce, 就可能會讓Detail頁crash.

第三個缺點: obs會頻繁刷新;

7. 手寫getX

3大核心功能
7.1 依賴注入

///依賴注入,外部可將實例,注入該類中,由該類管理
class Easy {
  ///注入實例
  static T put<T>(T dependency, {String? tag}) =>
      _EasyInstance().put(dependency, tag: tag);

  ///獲取注入的實例
  static T find<T>({String? tag, String? key}) =>
      _EasyInstance().find<T>(tag: tag, key: key);

  ///刪除實例
  static bool delete<T>({String? tag, String? key}) =>
      _EasyInstance().delete<T>(tag: tag, key: key);
}

///具體邏輯
class _EasyInstance {
  factory _EasyInstance() => _instance ??= _EasyInstance._();

  static _EasyInstance? _instance;

  _EasyInstance._();

  static final Map<String, _InstanceInfo> _single = {};

  ///注入實例
  T put<T>(T dependency, {String? tag}) {
    final key = _getKey(T, tag);
    //只保存第一次注入:針對自動刷新機制優(yōu)化,每次熱重載的時候,數(shù)據(jù)不會重置
    _single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
    return find<T>(tag: tag);
  }

  ///獲取注入的實例
  T find<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    var info = _single[newKey];

    if (info?.value != null) {
      return info!.value;
    } else {
      throw '"$T" not found. You need to call "Easy.put($T())""';
    }
  }

  ///刪除實例
  bool delete<T>({String? tag, String? key}) {
    final newKey = key ?? _getKey(T, tag);
    if (!_single.containsKey(newKey)) {
      print('Instance "$newKey" already removed.');
      return false;
    }

    _single.remove(newKey);
    print('Instance "$newKey" deleted.');
    return true;
  }

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }
}

class _InstanceInfo<T> {
  _InstanceInfo(this.value);

  T value;
}

7.2 狀態(tài)管理

///自定義個監(jiān)聽觸發(fā)類
class EasyXNotifier {
  List<VoidCallback> _listeners = [];

  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  void removeListener(VoidCallback listener) {
    for (final entry in _listeners) {
      if (entry == listener) {
        _listeners.remove(entry);
        return;
      }
    }
  }

  void dispose() {
    _listeners.clear();
  }

  void notify() {
    if (_listeners.isEmpty) return;

    for (final entry in _listeners) {
      try {
        entry.call();
      } catch (e) {
        print(e.toString());
      }
    }
  }
}

7.3 路由管理

///刷新控件,自帶回收機制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
  final Widget Function(T logic) builder;

  final String? tag;
  final bool autoRemove;

  const EasyBuilder({
    Key? key,
    required this.builder,
    this.autoRemove = true,
    this.tag,
  }) : super(key: key);

  @override
  _EasyBuilderState<T> createState() => _EasyBuilderState<T>();
}

class _EasyBuilderState<T extends EasyXController>
    extends State<EasyBuilder<T>> {
  late T controller;

  @override
  void initState() {
    super.initState();

    controller = Easy.find<T>(tag: widget.tag);
    controller.xNotifier.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    if (widget.autoRemove) {
      Easy.delete<T>(tag: widget.tag);
    }
    controller.xNotifier.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(controller);
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容