利用 InheritedWidget 來實(shí)現(xiàn) Flutter 狀態(tài)共享

背景

Flutter 常用的狀態(tài)管理框架 Bloc、 Provider,Bloc 架構(gòu)中使用了 Provide啸箫,而 Provider 是基于 InheritedWidget 組件的冀惭。所以本節(jié)我們通過自實(shí)現(xiàn) InheritedWidget 來了解 Provider 的原理。

setState

當(dāng)不依賴狀態(tài)管理框架時(shí)均芽,更新組件狀態(tài)的方法就是 setState(fn),調(diào)用該方法后单鹿,會(huì)重新調(diào)用 StatefulWidget 的 build 方法重新構(gòu)建組件掀宋,達(dá)到刷新界面的效果。

InheritedWidget

  • 簡(jiǎn)介
    InheritedWidget 是 Flutter 中非常重要的一個(gè)功能型組件仲锄,它提供了一種在 widget 樹中從上到下共享數(shù)據(jù)的方式劲妙,比如我們?cè)趹?yīng)用的根 widget 中通過InheritedWidget共享了一個(gè)數(shù)據(jù),那么我們便可以在任意子widget 中來獲取該共享的數(shù)據(jù)儒喊!
class CountInheritedWidget extends InheritedWidget {
   CountInheritedWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  final CountModel data; // 需要在子樹中共享的數(shù)據(jù)镣奋,保存點(diǎn)擊次數(shù)

 //定義一個(gè)便捷方法,方便子樹中的widget獲取共享數(shù)據(jù)
  static CountInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
  }

  //該回調(diào)決定當(dāng)data發(fā)生變化時(shí)怀愧,是否通知子樹中依賴data的Widget重新build
  @override
  bool updateShouldNotify(CountInheritedWidget old) {
    return old.data != data;
  }
}
class ChildText extends StatefulWidget {
  @override
  State<ChildText> createState() => _ChildTextState();
}

class _ChildTextState extends State<ChildText> {
  @override
  Widget build(BuildContext context) {
    return Text(
      CountInheritedWidget.of(context)?.data.count.toString() ?? '',
    );
  }

  @override 
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時(shí)會(huì)被調(diào)用侨颈。
    //如果build中沒有依賴InheritedWidget,則此回調(diào)不會(huì)被調(diào)用芯义。
    print("ChildText Dependencies change");
  }
}
  • didChangeDependencies
    State對(duì)象有一個(gè) didChangeDependencies 回調(diào)哈垢,它會(huì)在“依賴”發(fā)生變化時(shí)被 Flutter 框架調(diào)用。而這個(gè)“依賴”指的就是子 widget 是否使用了父 widget 中 InheritedWidget 的數(shù)據(jù)扛拨!如果使用了耘分,則代表子 widget 有依賴;如果沒有使用則代表沒有依賴绑警。這種機(jī)制可以使子組件在所依賴的InheritedWidget 變化時(shí)來更新自身求泰!

建立依賴:dependOnInheritedWidgetOfExactType()->dependOnInheritedElement()->
InheritedElement.updateDependencies()->_dependents[dependent] = value;(往依賴表里添加依賴的子組件)
通知依賴更新:
InheritedElement.update->ProxyElement.notifyClients->InheritedElement.notifyClients(遍歷_dependents更新依賴)-InheritedElement.notifyDependent>dependent.didChangeDependencies();

  • 通知數(shù)據(jù)發(fā)生變化
    為了更貼近 Flutter 開發(fā),我們使用Flutter SDK中提供的ChangeNotifier 類 待秃,它實(shí)現(xiàn)了一個(gè) Flutter 風(fēng)格的發(fā)布者-訂閱者模式拜秧。我們將要共享的狀態(tài)放到一個(gè) Model 類中,然后讓它繼承自 ChangeNotifier章郁,這樣當(dāng)共享的狀態(tài)改變時(shí)枉氮,我們只需要調(diào)用 notifyListeners() 來通知訂閱者,然后由訂閱者來重新構(gòu)建 InheritedWidget暖庄,而依賴該InheritedWidget 的子孫 Widget 就會(huì)更新聊替。
class CountModel extends ChangeNotifier {
  int count = 0;
  void add() {
    count++;
    notifyListeners();
  }
}
class _ChangeCountProviderState extends State<ChangeCountProvider> {
  void update() {
    setState(() {});
  }

  @override
  void initState() {
    // 給data添加監(jiān)聽器
    widget.data.addListener(update);
    super.initState();
  }

  @override
  void dispose() {
    // 移除data的監(jiān)聽器
    widget.data.removeListener(update);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CountInheritedWidget(
      data: widget.data,
      child: widget.child,
    );
  }
}
  • 優(yōu)化
    如果我們只想在子組件中引用 CountInheritedWidget 中共享的數(shù)據(jù),但卻不希望在 CountInheritedWidget 發(fā)生變化時(shí)調(diào)用子組件的 didChangeDependencies 方法重新構(gòu)建子組件怎么辦培廓?因?yàn)樽咏M件和 CountInheritedWidget 建立了依賴關(guān)系惹悄,當(dāng) CountInheritedWidget 更新時(shí),依賴它的子組件也會(huì)更新肩钠,所以只要我們解除這種依賴關(guān)系就可以了泣港。通過 getElementForInheritedWidgetOfExactType 方法即可暂殖。dependOnInheritedWidgetOfExactType 和 getElementForInheritedWidgetOfExactType 的區(qū)別就是前者會(huì)注冊(cè)依賴關(guān)系,而后者不會(huì)当纱。
  //添加一個(gè)listen參數(shù)呛每,表示是否建立依賴關(guān)系
   static CountModel? of(BuildContext context, {bool listen = true}) {
  final provider = listen
       ? context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>()
       : context.getElementForInheritedWidgetOfExactType<CountInheritedWidget>()?.widget as CountInheritedWidget;
     return provider?.data;
  }

至此我們計(jì)數(shù)器版的狀態(tài)管理器就實(shí)現(xiàn)了,它具備 Provider Package 中的核心功能坡氯,但是這個(gè) demo 的功能并不全面晨横,實(shí)現(xiàn)這個(gè)迷你 Provider 的主要目的是為了幫助大家了解 Provider Package 底層的原理,所以在實(shí)戰(zhàn)中還是使用 Provider Package箫柳。

Provider

class _ProviderCountViewState extends State<ProviderCountView> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (ctx) => _CountChangeNotifier(),
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: const Text('Provider'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Consumer<_CountChangeNotifier>(builder: (ctx, value, child) {
                return Text(
                  "${value.count}",
                  style: const TextStyle(
                      fontWeight: FontWeight.w500, fontSize: 20),
                );
              }),
              ChildHint(),
              Padding(
                padding: const EdgeInsets.only(top: 50),
                child: Builder(builder: (ctx) {
                  return FloatingActionButton(
                    onPressed: () {
                      var model = ctx.read<_CountChangeNotifier>();
                      model.add();
                    },
                    child: const Icon(Icons.add),
                  );
                }),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class _CountChangeNotifier extends ChangeNotifier {
  int count = 0;

  void add() {
    count++;
    notifyListeners();
  }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末手形,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悯恍,更是在濱河造成了極大的恐慌库糠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涮毫,死亡現(xiàn)場(chǎng)離奇詭異曼玩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)窒百,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豫尽,“玉大人篙梢,你說我怎么就攤上這事∶谰桑” “怎么了渤滞?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榴嗅。 經(jīng)常有香客問我妄呕,道長(zhǎng),這世上最難降的妖魔是什么嗽测? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任绪励,我火速辦了婚禮,結(jié)果婚禮上唠粥,老公的妹妹穿的比我還像新娘疏魏。我一直安慰自己,他們只是感情好晤愧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布大莫。 她就那樣靜靜地躺著,像睡著了一般官份。 火紅的嫁衣襯著肌膚如雪只厘。 梳的紋絲不亂的頭發(fā)上烙丛,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音羔味,去河邊找鬼河咽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛介评,可吹牛的內(nèi)容都是我干的库北。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼们陆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼寒瓦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坪仇,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤杂腰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后椅文,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喂很,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年皆刺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了少辣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡羡蛾,死狀恐怖漓帅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痴怨,我是刑警寧澤忙干,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站浪藻,受9級(jí)特大地震影響捐迫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爱葵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一施戴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萌丈,春花似錦暇韧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春涂乌,著一層夾襖步出監(jiān)牢的瞬間艺栈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工湾盒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湿右,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓罚勾,卻偏偏與公主長(zhǎng)得像毅人,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尖殃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容