Flutter狀態(tài)管理-Provider的使用和源碼解析

使用

Provider的使用非常簡(jiǎn)單铲掐,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用來(lái)實(shí)現(xiàn)狀態(tài)的管理與Widget的更新豪硅。其中ChangeNotifier是系統(tǒng)提供的,用來(lái)負(fù)責(zé)數(shù)據(jù)的變化通知和橙。ChangeNotifierProvider本質(zhì)上其實(shí)就是Widget仔燕,它作為父節(jié)點(diǎn)Widget,可將數(shù)據(jù)共享給其所有子節(jié)點(diǎn)Widget使用或更新魔招。具體的原理解析在后續(xù)章節(jié)會(huì)進(jìn)行說(shuō)明晰搀。所以通常我們只需要三步即可利用Provider來(lái)實(shí)現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifierModel办斑,用來(lái)實(shí)現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽(tīng)數(shù)據(jù)的變化外恕。
2.創(chuàng)建ChangeNotifierProvider,用來(lái)聲明Provider乡翅,實(shí)現(xiàn)跨組建的數(shù)據(jù)共享鳞疲。
3.接收共享數(shù)據(jù)。
我們來(lái)舉個(gè)例子蠕蚜,看看它是怎么在父子之間進(jìn)行數(shù)據(jù)共享的:

例1 Provider的使用:
  • 創(chuàng)建Model
class ProviderViewModel with ChangeNotifier {
  int _number = 0;

  get number => _number;

  void addNumber() {
    _number++;
    notifyListeners();
  }
}

上面的代碼很簡(jiǎn)單尚洽,調(diào)用addNumber()方法讓_number加1,并調(diào)用notifyListeners()通知給監(jiān)聽(tīng)方靶累。

  • 創(chuàng)建ChangeNotifierProvider
class ProviderTestPage extends StatelessWidget {
  final _providerViewModel = ProviderViewModel();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Provider Test"),
      ),
      body: ChangeNotifierProvider.value(
        value: _providerViewModel,
        builder: (context, child) {
          return Column(
            children: [
              const Text("我是父節(jié)點(diǎn)"),
              Text(
                  "Parent number is: ${Provider.of<ProviderViewModel>(context).number}"),
              ChildA(),
            //ChildB(),
            //ChildC()
            ],
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          _providerViewModel.addNumber();
        }, //使用context.read不會(huì)調(diào)用rebuild
      ),
    );
  }
}

我們用ChangeNotifierProvider將父布局包裹腺毫,在父或子節(jié)點(diǎn)ChildA通過(guò)Provider.of<T>(BuildContext context, {bool listen = true})進(jìn)行數(shù)據(jù)操作,可同步更新父與子的數(shù)據(jù)與UI挣柬。其中listen默認(rèn)為true可監(jiān)聽(tīng)數(shù)據(jù)的變化潮酒,為false的情況只可讀取數(shù)據(jù)的值。

  • 接收共享數(shù)據(jù):
class ChildA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childA build");
    return Container(
      width: double.infinity,
      color: Colors.amberAccent,
      child: Column(
        children: [
          Text(
              "Child A number: ${Provider.of<ProviderViewModel>(context).number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                Provider.of<ProviderViewModel>(context, listen: false)
                    .addNumber();
              })
        ],
      ),
    );
  }
}

來(lái)看一下效果:


例1

我們可以看到不管是在父節(jié)點(diǎn)還是在子節(jié)點(diǎn)邪蛔,都可以對(duì)ProviderViewModel的數(shù)據(jù)進(jìn)行操作和監(jiān)聽(tīng)急黎。例1在操作與讀取時(shí)使用的是Provider.of<T>(BuildContext context, {bool listen = true})的方式,為了可以更明確對(duì)于Provider的操作,我們可將它替換為context.watch<>()和context.read<>()方式勃教。 我們可以通過(guò)源碼看到淤击,context.watch<>()context.read<>()方法其實(shí)都是調(diào)用Provider.of<T>(BuildContext context, {bool listen = true})來(lái)實(shí)現(xiàn)的:


T watch<T>() {
    return Provider.of<T>(this);
  }
 
T read<T>() {
    return Provider.of<T>(this, listen: false);
  }

語(yǔ)義更加清晰明確。 如:

class ChildB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childB build");
    return Container(
      width: double.infinity,
      color: Colors.red,
      child: Column(
        children: [
          const Text("我是子節(jié)點(diǎn)"),
          Text("Child B number: ${context.watch<ProviderViewModel>().number}"),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                context.read<ProviderViewModel>().addNumber();
              })
        ],
      ),
    );
  }
}

ChildBChildA實(shí)際上是一致的故源。我們把ProviderTestPageChildB()放開(kāi):

image.png

其中遭贸,每點(diǎn)擊一次父Widget右下角的加號(hào)或子Widget的Add Number按鈕,我們看一下Log打印的結(jié)果:

flutter: childA build
flutter: child build

我們會(huì)發(fā)現(xiàn)每一次的操作心软,都會(huì)導(dǎo)致ChildAChildB整體重新build。但實(shí)際上從代碼中我們可知著蛙,在ChildAChildB中删铃,只有以下的Text()會(huì)監(jiān)聽(tīng)ProviderViewModel的數(shù)據(jù)更新:

//ChildA:
Text("Child A number: ${Provider.of<ProviderViewModel>(context).number}")
 
//ChildB:
Text("Child B number: ${context.watch<ProviderViewModel>().number}")

那么我們希望可以實(shí)現(xiàn)局部的更新該如何實(shí)現(xiàn)?Flutter提供了Consumer<>()來(lái)進(jìn)行支持踏堡。下面我們來(lái)看一下Consumer<>()的用法:

class ChildC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("childC build");
    return Container(
      width: double.infinity,
      color: Colors.blue,
      child: Column(
        children: [
          const Text("我是子節(jié)點(diǎn)"),
          Consumer<ProviderViewModel>(builder: (context, value, child) {
            print("ChildC Consumer builder");
            return Text("Child C number: ${value.number}");
          }),
          MaterialButton(
              child: const Text("Add Number"),
              color: Colors.white,
              onPressed: () {
                context.read<ProviderViewModel>().addNumber();
              })
        ],
      ),
    );
  }
}

由于我們只希望Text()來(lái)監(jiān)聽(tīng)ProviderViewModel的數(shù)據(jù)更新猎唁,我們用Consumer<>()包裹住Text(),其中builder的傳參value即是ProviderViewModel對(duì)象顷蟆,把ProviderTestPageChildC()放開(kāi)诫隅,我們看一下結(jié)果:

image.png

再打印一下Log

flutter: child build
flutter: childA build
flutter: Child Consumer builder

Log中我們可以得知,ChildC并沒(méi)有被rebuild帐偎,而是由Consumer調(diào)用內(nèi)部的builder來(lái)實(shí)現(xiàn)局部更新的逐纬。 到此為止,一個(gè)簡(jiǎn)單的Provider使用就介紹完成削樊。另外Provider還提供了ProxyProvider豁生,從名字上來(lái)看,我們可知這是個(gè)代理Provider漫贞,它是用來(lái)協(xié)調(diào)ModelModel之間的更新甸箱,比如一個(gè)ModelA依賴(lài)另一個(gè)ModelBModelB更新迅脐,他就要讓依賴(lài)它的ModelA也隨之更新芍殖。我們直接上代碼來(lái)看一下它的用法,還是分三步:

例2 ProxyProvider的使用:
  • 創(chuàng)建ProxyProviderViewModel
class ProxyProviderViewModel with ChangeNotifier {
  int number;
 
  ProxyProviderViewModel(this.number);
 
  String get title {
    return "The number is: $number";
  }
}

這個(gè)類(lèi)只是簡(jiǎn)單的在構(gòu)造方法例傳入int值谴蔑,并創(chuàng)建get方法得到一段文本豌骏。

  • 創(chuàng)建Provider:
class ProxyProviderTestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Provider Test"),
      ),
      body: MultiProvider(
        providers: [
          ChangeNotifierProvider<ProviderViewModel>(
              create: (_) => ProviderViewModel()),
          ChangeNotifierProxyProvider<ProviderViewModel,
                  ProxyProviderViewModel>(
              create: (context) => ProxyProviderViewModel(
                  context.read<ProviderViewModel>().number),
              update: (context, providerViewModel, proxyProviderViewModel) =>
                  ProxyProviderViewModel(providerViewModel.number))
        ],
        builder: (context, child){
          return Column(
            children: [
              ChildProxy(),
              MaterialButton(
                  child: const Text("Add Number"),
                  color: Colors.amberAccent,
                  onPressed: () {
                    context.read<ProviderViewModel>().addNumber();
                  })
            ],
          );
        },
      ),
    );
  }
}

我們?cè)?code>body中用MultiProvider來(lái)包裹布局。MultiProvider的作用是同時(shí)可聲明多個(gè)Provider供使用树碱,為參數(shù)providers添加Provider數(shù)組肯适。我們首先聲明一個(gè)ChangeNotifierProvider,同例1中的ProviderViewModel成榜。接著我們聲明一個(gè)ChangeNotifierProxyProvider用來(lái)做代理Provider框舔。其中create參數(shù)是ProxyProvider的創(chuàng)建,update參數(shù)是ProxyProvider的更新。在我們的例子中刘绣,實(shí)際上是對(duì)ProviderViewModel進(jìn)行數(shù)據(jù)操作樱溉,由ProxyProviderViewModel監(jiān)聽(tīng)ProviderViewModel的數(shù)據(jù)變化。

  • 接收共享數(shù)據(jù):
class ChildProxy extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      child: Column(
        children: [
          Text(context.watch<ProxyProviderViewModel>().title),
        ],
      ),
    );
  }
}

ChildProxy中纬凤,我們監(jiān)聽(tīng)ProxyProviderViewModel的數(shù)據(jù)變化福贞,并將title顯示在Text()中,進(jìn)行UI上的更新停士。 我們看一下效果:

image.png

我們調(diào)用context.read<ProviderViewModel>().addNumber()對(duì)ProviderViewModel的數(shù)據(jù)進(jìn)行更新挖帘,可同時(shí)更新ProxyProviderViewModelnumber對(duì)象,而ChildProxy由于監(jiān)聽(tīng)了ProxyProviderViewModel的數(shù)據(jù)變化恋技,會(huì)因此更新UI中title的內(nèi)容拇舀。
好了,到此為止蜻底,Provider的使用介紹到這里骄崩,下面我們將針對(duì)Provider的原理進(jìn)行說(shuō)明。Provider實(shí)際上是對(duì)InheritedWidget進(jìn)行了封裝薄辅,它才是真正實(shí)現(xiàn)父與子數(shù)據(jù)共享的重要元素要拂,所以為了理清Provider的原理,我們必須先弄清楚InheritedWidget的實(shí)現(xiàn)過(guò)程站楚。

InheritedWidget的原理及解析

InheritedWidget提供了沿樹(shù)向下脱惰,共享數(shù)據(jù)的功能,系統(tǒng)中的Provider窿春,Theme等實(shí)現(xiàn)都是依賴(lài)于它枪芒。弄清楚它的原理,對(duì)于理解Flutter的數(shù)據(jù)共享方式會(huì)有很大的幫助谁尸。本章節(jié)將先通過(guò)實(shí)例說(shuō)明InheritedWidget的用法舅踪,然后進(jìn)行原理的解析。

使用

我們舉個(gè)簡(jiǎn)單的例子:


image.png

這是一個(gè)簡(jiǎn)單的樹(shù)結(jié)構(gòu)良蛮,其中ChildAChildC需要共享Data這個(gè)數(shù)據(jù)抽碌,ChildB不需要。傳遞方式有很多種决瞳,比如說(shuō)通過(guò)構(gòu)造方法傳遞货徙,通過(guò)函數(shù)調(diào)用,函數(shù)回調(diào)傳遞等皮胡。但是如果樹(shù)層級(jí)非常多的話(huà)痴颊,剛才提到的傳遞方式將會(huì)對(duì)整個(gè)代碼結(jié)構(gòu)帶來(lái)災(zāi)難,包括代碼耦合度過(guò)高屡贺,回調(diào)地獄等蠢棱。我們看看InheritedWidget是怎么處理的:
1.作為整個(gè)樹(shù)的父節(jié)點(diǎn)锌杀,需要使ChildA繼承InheritedWidget

class ChildA extends InheritedWidget {
  int number;
 
  ChildA({required Widget child, required this.number}) : super(child: child);
 
  static ChildA? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ChildA>();
  }
 
  @override
  bool updateShouldNotify(covariant ChildA oldWidget) {
    return oldWidget.number != number;
  }
}
  • 其中updateShouldNotify()方法需要被重寫(xiě),用來(lái)判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致泻仙,是否需要傳遞給已注冊(cè)的子組件糕再。
  • of()方法是一種約定俗成的通用寫(xiě)法,只是起到方便調(diào)用的作用玉转。其中context.dependOnInheritedWidgetOfExactType()是為它的子組件注冊(cè)了依賴(lài)關(guān)系突想。 通過(guò)這樣的方式,將ChildA聲明成了一個(gè)給子組件共享數(shù)據(jù)的Widget究抓。
    ChildB就是一個(gè)中間層級(jí)的普通Widget猾担,用來(lái)連接樹(shù)結(jié)構(gòu):
class ChildB extends StatefulWidget {
  final Widget child;
 
  ChildB({Key? key, required this.child}) : super(key: key);
 
  @override
  _ChildBState createState() => _ChildBState();
}
 
class _ChildBState extends State<ChildB> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("ChildB didChangeDependencies");
  }
 
  @override
  Widget build(BuildContext context) {
    print("ChildB build");
    return Container(
      width: double.infinity,
      color: Colors.amberAccent,
      child: Column(
        children: [const Text("我是子節(jié)點(diǎn) ChildB"), widget.child],
      ),
    );
  }
}

ChildC依賴(lài)ChildA,并讀取ChildA的共享數(shù)據(jù)刺下,代碼如下:

class ChildC extends StatefulWidget {
  ChildC({Key? key}) : super(key: key);
 
  @override
  _ChildCState createState() => _ChildCState();
}
 
class _ChildCState extends State<ChildC> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("ChildC didChangeDependencies");
  }
 
  @override
  Widget build(BuildContext context) {
    print("ChildC build");
    return Container(
      width: double.infinity,
      color: Colors.red,
      child: Column(
        children: [
          const Text("我是子節(jié)點(diǎn) ChildC"),
          Text("Child C number: ${ChildA.of(context)?.number}"),
        ],
      ),
    );
  }
}

ChildC通過(guò)ChildA.of(context)?.number的方式讀取ChildA的共享數(shù)據(jù)垒探。 我們把這個(gè)樹(shù)串起來(lái):

class InheritedWidgetTestPage extends StatefulWidget {
  InheritedWidgetTestPage({Key? key}) : super(key: key);
 
  @override
  _InheritedWidgetTestPageState createState() =>
      _InheritedWidgetTestPageState();
}
 
class _InheritedWidgetTestPageState extends State<InheritedWidgetTestPage> {
  int _number = 10;
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("InheritedWidget Test"),
      ),
      body: ChildA(
        number: _number,
        child: ChildB(
          child: ChildC(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _number++;
          });
        },
      ),
    );
  }
}

在點(diǎn)擊floatingActionButton的時(shí)候,修改_number的值怠李,通過(guò)構(gòu)造方法傳遞給ChildA,整個(gè)樹(shù)重新build蛤克。ChildC讀取ChildA的數(shù)據(jù)捺癞,并進(jìn)行UI的更新,我們看一下結(jié)果:

image.png

ChildC接收到了數(shù)據(jù)變化并進(jìn)行了更新构挤,我們?cè)賮?lái)看一下Log

flutter: ChildB build
flutter: Child didChangeDependencies
flutter: Child build

在這個(gè)過(guò)程中髓介,會(huì)發(fā)現(xiàn)ChildBChildC都進(jìn)行了rebuild,由于ChildC依賴(lài)ChildA的共享數(shù)據(jù)筋现,ChildCrebuild之前執(zhí)行了didChangeDependencies()方法唐础,說(shuō)明ChildC的依賴(lài)關(guān)系發(fā)生了改變;而ChildB由于不依賴(lài)ChildA的共享數(shù)據(jù)所以并沒(méi)有執(zhí)行didChangeDependencies()矾飞。
這個(gè)例子給出了InheritedWidget的一個(gè)基本使用方式一膨,但需要注意的是,在整個(gè)樹(shù)結(jié)構(gòu)中洒沦,其實(shí)ChildB是不依賴(lài)ChildA的共享數(shù)據(jù)的豹绪,按理來(lái)說(shuō),在數(shù)據(jù)發(fā)生變化申眼,我們是不希望ChildB進(jìn)行rebuild的瞒津。所以需要說(shuō)明的是,InheritedWidget的正確用法并不是通過(guò)setState()來(lái)實(shí)現(xiàn)rebuild的括尸,這里用setState()舉例僅僅是為了將整個(gè)流程串起來(lái)巷蚪。這個(gè)例子的重點(diǎn)在于,依賴(lài)父組件的共享數(shù)據(jù)的子組件濒翻,將在生命周期中執(zhí)行didChangeDependencies()方法屁柏。我們可以通過(guò)ValueNotifier+ValueListenable來(lái)進(jìn)行局部的更新啦膜,這部分出離了本文的內(nèi)容,先不作展開(kāi)前联。
接下來(lái)我們分析一下InheritedWidget是如何實(shí)現(xiàn)父與子之間的數(shù)據(jù)共享的功戚。

原理及解析

為了實(shí)現(xiàn)父與子的數(shù)據(jù)共享,我們需要弄清楚兩件事:

  • 父綁定子的方式
  • 父通知子的方式
父綁定子的方式

我們先來(lái)看一下InheritedWidget這個(gè)類(lèi):

abstract class InheritedWidget extends ProxyWidget {
  const InheritedWidget({ Key? key, required Widget child })
    : super(key: key, child: child);
 
  @override
  InheritedElement createElement() => InheritedElement(this);
 
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

InheritedWidget繼承ProxyWidget似嗤,最終繼承的是Widget啸臀。它只有兩個(gè)方法,一個(gè)是updateShouldNotify()烁落,在上面的例子中可知是用來(lái)判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致乘粒,是否需要傳遞給已注冊(cè)的子組件的。另外還重寫(xiě)了createElement()方法伤塌,創(chuàng)建一個(gè)InheritedElement對(duì)象灯萍。InheritedElement最終繼承Element,我們先看一下它的結(jié)構(gòu):

image.png

從命名中我們就可知setDependencies()是用來(lái)綁定依賴(lài)關(guān)系的每聪。接下來(lái)我們從子組件獲取InheritedWidget實(shí)例開(kāi)始看起旦棉,看看具體的綁定流程。如實(shí)例中的如下代碼:

static ChildA? of(BuildContext context) {
  return context.dependOnInheritedWidgetOfExactType<ChildA>();
}

我們看一下context.dependOnInheritedWidgetOfExactType<ChildA>()的流程:

//BuildContext
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
//Element
  @override
  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
    if (ancestor != null) {
      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }

真正的實(shí)現(xiàn)是在Element中進(jìn)行的药薯。其中_inheritedWidgets是個(gè)Map绑洛,keyT的類(lèi)型。從上面代碼我們可以知道童本,先從_inheritedWidgets里尋找類(lèi)型為TInheritedElement真屯,即父的InheritedElement_updateInheritance()是在mount()activate()調(diào)用的穷娱,_inheritedWidgets的初始化在子類(lèi)InheritedElement_updateInheritance()中的實(shí)現(xiàn)如下:

//InheritedElement
@override
void _updateInheritance() {
  assert(_lifecycleState == _ElementLifecycle.active);
  final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
  if (incomingWidgets != null)
    _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
  else
    _inheritedWidgets = HashMap<Type, InheritedElement>();
  _inheritedWidgets![widget.runtimeType] = this;
}

Element中的實(shí)現(xiàn)如下:

//Element
void _updateInheritance() {
  assert(_active);
  _inheritedWidgets = _parent?._inheritedWidgets;
}

從上面的代碼我們可以得知绑蔫,普通Element組件在生命周期的初始階段,它的_inheritedWidgets為父組件的_inheritedWidgets泵额。而_inheritedWidgetsInheritedElement配深,會(huì)將自己添加到_inheritedWidgets中,從而通過(guò)此方式將組件和InheritedWidgets的依賴(lài)關(guān)系層層向下傳遞嫁盲,每一個(gè)Element中都含有_inheritedWidgets集合凉馆,此集合中包含了此組件的父組件且是InheritedWidgets組件的引用關(guān)系。接下來(lái)我們回到dependOnInheritedWidgetOfExactType()方法亡资,ancestor已經(jīng)被加到_inheritedWidgets中澜共,所以它不為空,我們繼續(xù)看里面dependOnInheritedElement()的實(shí)現(xiàn):

//Element 
@override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies!.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
//InheritedElement 
@protected
  void updateDependencies(Element dependent, Object? aspect) {
    setDependencies(dependent, null);
  }
//InheritedElement 
  @protected
  void setDependencies(Element dependent, Object? value) {
    _dependents[dependent] = value;
  }

好到此為止锥腻,我們證實(shí)了之前的猜測(cè)嗦董,子組件找到InheritedElement類(lèi)型的父組件,父組件調(diào)用setDependencies()瘦黑,為子組件向_dependents中添加注冊(cè)京革,InheritedWidget組件更新時(shí)可以根據(jù)此列表通知子組件奇唤。將以上過(guò)程總結(jié)一下,如下圖:

image.png

  • 父組件在InheritedElement的初始階段:mount()activate()的時(shí)候調(diào)用_updateInheritance()方法將自己添加到_inheritedWidgets中匹摇。其他Element子組件會(huì)直接拿父組件的_inheritedWidgets咬扇。
  • 子組件在調(diào)用context.dependOnInheritedWidgetOfExactType<>()時(shí),將自己注冊(cè)給_inheritedWidgets中獲取的InheritedElement類(lèi)型的父組件的dependents中廊勃,從而實(shí)現(xiàn)了依賴(lài)關(guān)系的確定懈贺。
    接下來(lái)我們看一下當(dāng)組件發(fā)生變化時(shí),父通知子的方式坡垫。
父通知子的方式

在實(shí)例中梭灿,當(dāng)setState()發(fā)生數(shù)據(jù)改變的時(shí)候,經(jīng)過(guò)一系列處理后冰悠,會(huì)走到InheritedElement的updated()方法中去:

@override
void updated(InheritedWidget oldWidget) {
  if (widget.updateShouldNotify(oldWidget))
    super.updated(oldWidget);
}

當(dāng)執(zhí)行了我們自定義InheritedWidgetupdateShouldNotify()判斷現(xiàn)有共享數(shù)據(jù)和舊的共享數(shù)據(jù)是否一致需要更新后堡妒,繼續(xù)執(zhí)行父類(lèi)ProxyElement的updated()方法:

//ProxyElement
@protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }
//InheritedElement
  @override
  void notifyClients(InheritedWidget oldWidget) {
    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
    for (final Element dependent in _dependents.keys) {
      assert(() {
        // check that it really is our descendant
        Element? ancestor = dependent._parent;
        while (ancestor != this && ancestor != null)
          ancestor = ancestor._parent;
        return ancestor == this;
      }());
      // check that it really depends on us
      assert(dependent._dependencies!.contains(this));
      notifyDependent(oldWidget, dependent);
    }
  }

從這段代碼中我們可以看出,在notifyClients()中會(huì)對(duì)_dependentskey進(jìn)行遍歷溉卓,然后執(zhí)行notifyDependent()進(jìn)行通知皮迟。接著我們看notifyDependent()都做了什么:

@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
  dependent.didChangeDependencies();
}
@mustCallSuper
void didChangeDependencies() {
  assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
  assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
  markNeedsBuild();
}

它調(diào)用了每個(gè)dependentdidChangeDependencies()方法,來(lái)通知InheritedWidget依賴(lài)發(fā)生了變化桑寨,當(dāng)前element需要被標(biāo)記為dirty伏尼,重新進(jìn)行build。到此為止西疤,完成了當(dāng)數(shù)據(jù)發(fā)生變化時(shí),父通知子的流程休溶。我們看一下父通知子的流程圖:

image.png

總結(jié)一下就是當(dāng)InheritedElement數(shù)據(jù)發(fā)生變化而更新的時(shí)候代赁,父InheritedWidget會(huì)遍歷_dependents,子會(huì)執(zhí)行didChangeDependencies()方法將子組件標(biāo)記為dirty而重新build兽掰。
了解了InheritedWidget的實(shí)現(xiàn)后芭碍,我們下個(gè)章節(jié)對(duì)Provider進(jìn)行解析。

Provider解析

接下來(lái)我們分析一下Provider的實(shí)現(xiàn)孽尽。還記著文章開(kāi)頭窖壕,我們說(shuō)明需要三步來(lái)利用Provider來(lái)實(shí)現(xiàn)狀態(tài)管理。
1.創(chuàng)建混合或繼承ChangeNotifierModel杉女,用來(lái)實(shí)現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽(tīng)數(shù)據(jù)的變化瞻讽。
2.創(chuàng)建ChangeNotifierProvider,用來(lái)聲明Provider熏挎,實(shí)現(xiàn)跨組建的數(shù)據(jù)共享速勇。
3.接收共享數(shù)據(jù)。
我們從創(chuàng)建Model開(kāi)始講起:

ChangeNotifier

ChangeNotifier實(shí)現(xiàn)了Listenable接口坎拐,而Listenable實(shí)際上就是一個(gè)觀(guān)察者模型烦磁。我們先來(lái)看一下ChangeNotifier的結(jié)構(gòu):

image.png

ChangeNotifier里維護(hù)了一個(gè)_listeners對(duì)象养匈,通過(guò)addListener()removeListener()進(jìn)行添加或刪除。在調(diào)用notifyListeners()的時(shí)候?qū)?shù)據(jù)通知給_listeners都伪。ChangeNotifier非常簡(jiǎn)單呕乎,我們接著來(lái)分析ChangeNotifierProvider的實(shí)現(xiàn)。

ChangeNotifierProvider

ChangeNotifierProvider繼承了多個(gè)層級(jí):ListenableProvider->InheritedProvider->SingleChildStatelessWidget->StatelessWidget陨晶,實(shí)際上它是個(gè)StatelessWidget猬仁。我們從ChangeNotifierProvider.value()方法開(kāi)始:

//ChangeNotifierProvider
  ChangeNotifierProvider.value({
    Key? key,
    required T value,
    TransitionBuilder? builder,
    Widget? child,
  }) : super.value(
          key: key,
          builder: builder,
          value: value,
          child: child,
        );

其中required T valueChangeNotifier對(duì)象,我們繼續(xù)看super()的調(diào)用:


//ListenableProvider
  ListenableProvider.value({
    Key? key,
    required T value,
    UpdateShouldNotify<T>? updateShouldNotify,
    TransitionBuilder? builder,
    Widget? child,
  }) : super.value(
          key: key,
          builder: builder,
          value: value,
          updateShouldNotify: updateShouldNotify,
          startListening: _startListening,
          child: child,
        );
 
  static VoidCallback _startListening(
    InheritedContext e,
    Listenable? value,
  ) {
    value?.addListener(e.markNeedsNotifyDependents);
    return () => value?.removeListener(e.markNeedsNotifyDependents);
  }

ListenableProvider創(chuàng)建了一個(gè)VoidCallback對(duì)象珍逸,其中value是個(gè)Listenable對(duì)象逐虚,就是我們傳入的ChangeNotifier對(duì)象。它的實(shí)現(xiàn)是為ChangeNotifier添加listener谆膳,這個(gè)listener將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()方法钳枕,這個(gè)我們之后再做討論拴疤。總而言之,ListenableProvider的作用就是幫我們?yōu)?code>ChangeNotifier添加了listener口渔。我們接著往下看:

//InheritedProvider
  InheritedProvider.value({
    Key? key,
    required T value,
    UpdateShouldNotify<T>? updateShouldNotify,
    StartListening<T>? startListening,
    bool? lazy,
    this.builder,
    Widget? child,
  })
      : _lazy = lazy,
        _delegate = _ValueInheritedProvider(
          value: value,
          updateShouldNotify: updateShouldNotify,
          startListening: startListening,
        ),
        super(key: key, child: child);

到了InheritedProvider這一層,我們發(fā)現(xiàn)builder沒(méi)有被繼續(xù)傳下去了法希,InheritedProvider持有了一個(gè)_ValueInheritedProvider類(lèi)型的_delegate悠反。它的父類(lèi)_Delegate的代碼如下:

abstract class _Delegate<T> {
  _DelegateState<T, _Delegate<T>> createState();
 
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {}
}

看到有個(gè)看上去跟狀態(tài)相關(guān)的方法需要重寫(xiě):createState(),我們繼續(xù)看一下_ValueInheritedProvider重寫(xiě)的createState()的實(shí)現(xiàn):

@override
_ValueInheritedProviderState<T> createState() {
  return _ValueInheritedProviderState<T>();
}

返回了_ValueInheritedProviderState對(duì)象注盈,_ValueInheritedProviderState繼承了_DelegateState晃危,而_DelegateState持有了一個(gè)_InheritedProviderScopeElement對(duì)象。繼續(xù)看一下_ValueInheritedProviderState的結(jié)構(gòu):

image.png

它定義了willUpdateDelegate()dispose()這兩個(gè)方法老客,用來(lái)做更新和注銷(xiāo)僚饭。這么看來(lái)_ValueInheritedProviderState這個(gè)類(lèi)實(shí)際上是個(gè)狀態(tài)的代理類(lèi),類(lèi)似StatefulWidgetState的關(guān)系胧砰。我們一開(kāi)始提到其實(shí)ChangeNotifierProvider是個(gè)StatelessWidget鳍鸵,那么它的狀態(tài)肯定是由其他類(lèi)代理的,由此可知尉间,ChangeNotifierProvider的狀態(tài)是由_ValueInheritedProviderState來(lái)代理偿乖。
ChangeNotifierProvider對(duì)于Widget的實(shí)現(xiàn)實(shí)際上是在父類(lèi)InheritedProvider進(jìn)行的,我們看一下InheritedProvider的結(jié)構(gòu):

image.png

終于看到了buildWithChild()這個(gè)方法哲嘲,這是真正我們想看的Widget的內(nèi)部結(jié)構(gòu)的創(chuàng)建:

@override
Widget buildWithChild(BuildContext context, Widget? child) {
  assert(
  builder != null || child != null,
  '$runtimeType used outside of MultiProvider must specify a child',
  );
  return _InheritedProviderScope<T>(
    owner: this,
    // ignore: no_runtimetype_tostring
    debugType: kDebugMode ? '$runtimeType' : '',
    child: builder != null
        ? Builder(
      builder: (context) => builder!(context, child),
    )
        : child!,
  );
}

我們看到我們所創(chuàng)建的builderchild實(shí)際上是被_InheritedProviderScope()進(jìn)行了包裹贪薪。我們繼續(xù)分析_InheritedProviderScope

class _InheritedProviderScope<T> extends InheritedWidget {
  const _InheritedProviderScope({
    required this.owner,
    required this.debugType,
    required Widget child,
  }) : super(child: child);
 
  final InheritedProvider<T> owner;
  final String debugType;
 
  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }
 
  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}

到這我們終于看到_InheritedProviderScope繼承了我們熟悉的InheritedWidget,說(shuō)明我們的創(chuàng)建的Widget都是被InheritedWidget進(jìn)行了包裹眠副。在createElement()時(shí)返回了_InheritedProviderScopeElement對(duì)象古掏。_InheritedProviderScopeElement繼承InheritedElement,并實(shí)現(xiàn)了InheritedContext接口侦啸。我們先看一下它的結(jié)構(gòu):

image.png

首先我們關(guān)注到有個(gè)_delegateState的變量丧枪,對(duì)應(yīng)的就是我們上面所提到的_ValueInheritedProvider,看一下它初始化的位置:

void performRebuild() {
  if (_firstBuild) {
    _firstBuild = false;
    _delegateState = widget.owner._delegate.createState()
      ..element = this;
  }
  super.performRebuild();
}

performRebuild的時(shí)候私恬,調(diào)用widget_delegate對(duì)象的createState()方法债沮,即_ValueInheritedProvider的createState()方法,得到一個(gè)_ValueInheritedProviderState對(duì)象本鸣。并將自己賦值給_ValueInheritedProviderStateelement對(duì)象疫衩。 還記不記著在講ListenableProvider的時(shí)候提到它添加了listener,這個(gè)listener將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()方法荣德,而markNeedsNotifyDependents()的定義就在_InheritedProviderScope里:

@override
void markNeedsNotifyDependents() {
  if (!_isNotifyDependentsEnabled) {
    return;
  }
 
  markNeedsBuild();
  _shouldNotifyDependents = true;
}

這里我看看到它將_InheritedProviderScopeElement標(biāo)志為markNeedsBuild()闷煤,即需要被rebuild的組件,然后將_shouldNotifyDependents標(biāo)志為true涮瞻。
回到我們的ChangeNotifier:當(dāng)我們調(diào)用notifyListeners()來(lái)通知數(shù)據(jù)變化的時(shí)候鲤拿,如果有listener被注冊(cè),實(shí)際上會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()方法署咽,具體會(huì)執(zhí)行到的位置在ChangeNotifierProvider組件的父類(lèi)InheritedProvider包裹的_InheritedProviderScope這個(gè)InheritedWidget對(duì)應(yīng)的_InheritedProviderScopeElementmarkNeedsNotifyDependents()方法近顷。
整個(gè)過(guò)程可總結(jié)為下圖:

image.png

不過(guò)到目前為止,我們只是知道了這個(gè)流程宁否,但是listener什么時(shí)候被注冊(cè)窒升,子組件又是如何被刷新的呢?我們繼續(xù)從實(shí)例中的Provider.of<>()分析起家淤。

Provider.of<>()

在取數(shù)據(jù)的時(shí)候异剥,如實(shí)例代碼:Provider.of<ProviderViewModel>(context).number;瑟由,來(lái)看of()方法的實(shí)現(xiàn):

static T of<T>(BuildContext context, {bool listen = true}) {
  assert(
    context.owner!.debugBuilding ||
        listen == false ||
        debugIsInInheritedProviderUpdate,
  );
 
  final inheritedElement = _inheritedElementOf<T>(context);
 
  if (listen) {
    context.dependOnInheritedElement(inheritedElement);
  }
  return inheritedElement.value;
}

首先獲取context_InheritedProviderScopeElement對(duì)象絮重,然后調(diào)用context.dependOnInheritedElement()方法。這個(gè)方法我們很熟悉了歹苦,在上個(gè)章節(jié)介紹InheritedWidget的時(shí)候了解過(guò)青伤,作用是讓子組件找到InheritedElement類(lèi)型的父組件,父組件調(diào)用setDependencies()殴瘦,為子組件向_dependents中添加注冊(cè)以形成依賴(lài)關(guān)系狠角,InheritedWidget組件更新時(shí)可以根據(jù)此列表通知子組件。接著調(diào)用inheritedElement.value返回一個(gè)ChangeNotifier對(duì)象蚪腋。這個(gè)就是之前我們?cè)?code>ChangeNotifierProvider.value()過(guò)程中傳入的ChangeNotifier對(duì)象丰歌。那么在取值的過(guò)程中還做了些什么呢姨蟋?我們繼續(xù)分析:

@override
T get value => _delegateState.value;

它實(shí)際上取的是_ValueInheritedProviderStatevalue

@override
T get value {
  element!._isNotifyDependentsEnabled = false;
  _removeListener ??= delegate.startListening?.call(element!, delegate.value);
  element!._isNotifyDependentsEnabled = true;
  assert(delegate.startListening == null || _removeListener != null);
  return delegate.value;
}

從這段代碼中,我們看到了立帖,在取值的過(guò)程中眼溶,調(diào)用了delegate.startListening?.call(element!, delegate.value),為上一節(jié)所提到的listener進(jìn)行了注冊(cè)晓勇。意味著當(dāng)notifyListeners()時(shí)堂飞,這個(gè)listener將會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()方法。我們還記著在分析InheritedWidget的時(shí)候說(shuō)明父通知子的時(shí)候绑咱,會(huì)調(diào)用InheritedElement的notifyDependent()方法绰筛,那么在Provider中,會(huì)在其子類(lèi)_InheritedProviderScopeElement進(jìn)行實(shí)現(xiàn)描融,代碼如下:

@override
void notifyDependent(InheritedWidget oldWidget, Element dependent) {
  final dependencies = getDependencies(dependent);
 
  if (kDebugMode) {
    ProviderBinding.debugInstance.providerDidChange(_debugId);
  }
 
  var shouldNotify = false;
  if (dependencies != null) {
    if (dependencies is _Dependency<T>) {
      if (dependent.dirty) {
        return;
      }
 
      for (final updateShouldNotify in dependencies.selectors) {
        try {
          assert(() {
            _debugIsSelecting = true;
            return true;
          }());
          shouldNotify = updateShouldNotify(value);
        } finally {
          assert(() {
            _debugIsSelecting = false;
            return true;
          }());
        }
        if (shouldNotify) {
          break;
        }
      }
    } else {
      shouldNotify = true;
    }
  }
 
  if (shouldNotify) {
    dependent.didChangeDependencies();
  }
}

先取shouldNotify的值铝噩,由于我們沒(méi)有用selector,這時(shí)候shouldNotifytrue稼稿,當(dāng)前Widget將會(huì)進(jìn)行rebuild薄榛。那么如果我們并沒(méi)有用Consumer,這時(shí)候的Provider.of<ProviderViewModel>(context)context實(shí)際上是ChangeNotifierProvider對(duì)應(yīng)的context让歼,整個(gè)ChangeNotifierProvider都會(huì)進(jìn)行rebuild操作敞恋。Consumer的局部更新如何實(shí)現(xiàn)的呢?

Consumer

其實(shí)這個(gè)很簡(jiǎn)單谋右,看一下Consumer的實(shí)現(xiàn):

class Consumer<T> extends SingleChildStatelessWidget {
  /// {@template provider.consumer.constructor}
  /// Consumes a [Provider<T>]
  /// {@endtemplate}
  Consumer({
    Key? key,
    required this.builder,
    Widget? child,
  }) : super(key: key, child: child);
 
 
  final Widget Function(
    BuildContext context,
    T value,
    Widget? child,
  ) builder;
 
  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    return builder(
      context,
      Provider.of<T>(context),
      child,
    );
  }
}

buildWithChild()中實(shí)際上也是使用了Provider.of<T>(context)硬猫,不過(guò)要注意的是,這個(gè)context是當(dāng)前組件的context改执,所以最終只有被Consumer包裹住的子組件才會(huì)向_dependents中添加注冊(cè)以形成依賴(lài)關(guān)系啸蜜,才會(huì)被標(biāo)記為dirty從而進(jìn)行rebuild
好了到此為止辈挂,Provider的解析已經(jīng)完成了衬横,總結(jié)一下:

  • Provider實(shí)際上是個(gè)無(wú)狀態(tài)的StatelessWidget,通過(guò)包裝了InheritedWidget實(shí)現(xiàn)父子組件的數(shù)據(jù)共享终蒂,通過(guò)自定義InheritedElement實(shí)現(xiàn)刷新蜂林。
  • Provider通過(guò)與ChangeNotifier配合使用,實(shí)現(xiàn)了觀(guān)察者模式拇泣,Provider會(huì)將子組件添加到父組件的依賴(lài)關(guān)系中噪叙,在notifyListeners()時(shí),會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()霉翔,將組件標(biāo)記為dirty等待重繪睁蕾。
  • Consumer會(huì)只將被它包裹住的子組件注冊(cè)給父的_dependents形成依賴(lài)關(guān)系,從而實(shí)現(xiàn)了局部更新。
    下面我們看一下幾種在Flutter中比較流行的狀態(tài)同步框架并進(jìn)行比較子眶。
幾種狀態(tài)同步框架的對(duì)比和選擇
image.png

這幾個(gè)狀態(tài)同步框架瀑凝,包括其衍生的一些框架的核心原理都是利用了InheritedWidget實(shí)現(xiàn)的。雖然Google官方推薦的使用Provider臭杰,但在開(kāi)發(fā)過(guò)程中需要根據(jù)項(xiàng)目大小猜丹,開(kāi)發(fā)人員習(xí)慣等因素去考慮。
轉(zhuǎn)載自(https://mp.weixin.qq.com/s/vUhDvHaStrTwbE4tovOqtA)硅卢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末射窒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子将塑,更是在濱河造成了極大的恐慌脉顿,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件点寥,死亡現(xiàn)場(chǎng)離奇詭異艾疟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)敢辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蔽莱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人戚长,你說(shuō)我怎么就攤上這事盗冷。” “怎么了同廉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵仪糖,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我迫肖,道長(zhǎng)锅劝,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任蟆湖,我火速辦了婚禮故爵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隅津。我一直安慰自己诬垂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布饥瓷。 她就那樣靜靜地躺著剥纷,像睡著了一般痹籍。 火紅的嫁衣襯著肌膚如雪呢铆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天蹲缠,我揣著相機(jī)與錄音棺克,去河邊找鬼悠垛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娜谊,可吹牛的內(nèi)容都是我干的确买。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纱皆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼湾趾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起派草,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搀缠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后近迁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體艺普,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年鉴竭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歧譬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搏存,死狀恐怖瑰步,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情璧眠,我是刑警寧澤面氓,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛆橡,受9級(jí)特大地震影響舌界,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泰演,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一呻拌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧睦焕,春花似錦藐握、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至本谜,卻和暖如春初家,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工溜在, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陌知,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓掖肋,卻偏偏與公主長(zhǎng)得像仆葡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子志笼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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