React生命周期詳解

1 React生命周期流程

調(diào)用流程可以參看上圖勘天。分為實(shí)例化,存在期和銷毀三個不同階段捉邢。介紹生命周期流程的文章很多脯丝,相信大部分同學(xué)也有所了解,我們就不詳細(xì)分析了伏伐。很多同學(xué)肯定有疑問宠进,這些方法在React內(nèi)部是在哪些方法中被調(diào)用的呢,他們觸發(fā)的時機(jī)又是什么時候呢藐翎。下面我們來詳細(xì)分析材蹬。

2 實(shí)例化生命周期

getDefaultProps

在React.creatClass()初始化組件類時实幕,會調(diào)用getDefaultProps(),將返回的默認(rèn)屬性掛載到defaultProps變量下堤器。這段代碼之前已經(jīng)分析過了昆庇,參考

React源碼分析1 — 組件和對象的創(chuàng)建(createClass,createElement).

這里要提的一點(diǎn)是闸溃,初始化組件類只運(yùn)行一次整吆。可以把它簡單類比為Java中的Class對象辉川。初始化組件類就是ClassLoader加載Class對象的過程表蝙。類對象的初始化不要錯誤理解成了實(shí)例對象的初始化。一個React組件類可能會在JSX中被多次調(diào)用员串,產(chǎn)生多個組件對象勇哗,但它只有一個類對象,也就是類加載后getDefaultProps就不會再調(diào)用了寸齐。

getInitialState

這個方法在創(chuàng)建組件實(shí)例對象的時候被調(diào)用欲诺,具體代碼位于React.creatClass()的Constructor函數(shù)中。之前文章中已經(jīng)分析了渺鹦,參考
React源碼分析1 — 組件和對象的創(chuàng)建(createClass扰法,createElement)

mountComponent

componentWillMount毅厚,render塞颁,componentDidMount都是在mountComponent中被調(diào)用。在
React源碼分析2 — React組件插入DOM流程
一文中吸耿,我們講過mountComponent被調(diào)用的時機(jī)祠锣。它是在渲染新的ReactComponent中被調(diào)用的。輸入ReactComponent咽安,返回組件對應(yīng)的HTML伴网。把這個HTML插入到DOM中,就可以生成組件對應(yīng)的DOM對象了妆棒。所以mountComponent尤其關(guān)鍵澡腾。不同的React組件的mountComponent實(shí)現(xiàn)都有所區(qū)別。下面我們來重點(diǎn)分析React自定義組件類糕珊,也就是ReactCompositeComponent的mountComponent动分。

 mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) {
    this._context = context;
    this._mountOrder = nextMountID++;
    this._nativeParent = nativeParent;
    this._nativeContainerInfo = nativeContainerInfo;

    // 做propTypes是否合法的判斷,這個只在開發(fā)階段有用
    var publicProps = this._processProps(this._currentElement.props);
    var publicContext = this._processContext(context);

    var Component = this._currentElement.type;

    // 初始化公共類
    var inst = this._constructComponent(publicProps, publicContext);
    var renderedElement;

    // inst或者inst.render為空對應(yīng)的是stateless組件红选,也就是無狀態(tài)組件
    // 無狀態(tài)組件沒有實(shí)例對象澜公,它本質(zhì)上只是一個返回JSX的函數(shù)而已。是一種輕量級的React組件
    if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
      renderedElement = inst;
      warnIfInvalidElement(Component, renderedElement);
      inst = new StatelessComponent(Component);
    }

    // 設(shè)置變量
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = ReactUpdateQueue;
    this._instance = inst;

    // 存儲實(shí)例對象的引用到map中纠脾,方便以后查找
    ReactInstanceMap.set(inst, this);

    // 初始化state玛瘸,隊(duì)列等
    var initialState = inst.state;
    if (initialState === undefined) {
      inst.state = initialState = null;
    }
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    var markup;
    if (inst.unstable_handleError) {
      // 掛載時出錯蜕青,進(jìn)行一些錯誤處理,然后performInitialMount糊渊,初始化掛載
      markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
    } else {
      // 初始化掛載
      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
    }

    if (inst.componentDidMount) {
      // 調(diào)用componentDidMount右核,以事務(wù)的形式
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
  },

mountComponent先做實(shí)例對象的props,state等初始化,然后調(diào)用performInitialMount初始化掛載渺绒,完成后調(diào)用componentDidMount贺喝。這個調(diào)用鏈還是很清晰的。下面我們重點(diǎn)來分析performInitialMountWithErrorHandling和performInitialMount

performInitialMountWithErrorHandling: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
    var markup;
    var checkpoint = transaction.checkpoint();
    try {
      // 放到try-catch中宗兼,如果沒有出錯則調(diào)用performInitialMount初始化掛載剔宪∶浣溃可見這里沒有什么特別的操作塔插,也就是做一些錯誤處理而已
      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
    } catch (e) {
      // handleError桐经,卸載組件,然后重新performInitialMount初始化掛載
      transaction.rollback(checkpoint);
      this._instance.unstable_handleError(e);
      if (this._pendingStateQueue) {
        this._instance.state = this._processPendingState(this._instance.props, this._instance.context);
      }
      checkpoint = transaction.checkpoint();

      this._renderedComponent.unmountComponent(true);
      transaction.rollback(checkpoint);

      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);
    }
    return markup;
  },

可見performInitialMountWithErrorHandling只是多了一層錯誤處理而已主到,關(guān)鍵還是在performInitialMount中茶行。

performInitialMount: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
    var inst = this._instance;
    if (inst.componentWillMount) {
      // render前調(diào)用componentWillMount
      inst.componentWillMount();
      // 將state提前合并,故在componentWillMount中調(diào)用setState不會觸發(fā)重新render登钥,而是做一次state合并畔师。這樣做的目的是減少不必要的重新渲染
      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context);
      }
    }

    // 如果不是stateless,即無狀態(tài)組件牧牢,則調(diào)用render看锉,返回ReactElement
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent();
    }

    // 得到組件類型,如空組件ReactNodeTypes.EMPTY塔鳍,自定義React組件ReactNodeTypes.COMPOSITE伯铣,DOM原生組件ReactNodeTypes.NATIVE
    this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
    // 由ReactElement生成ReactComponent,這個方法在之前講解過轮纫。根據(jù)不同type創(chuàng)建不同Component對象
    // 參考 http://blog.csdn.net/u013510838/article/details/55669769
    this._renderedComponent = this._instantiateReactComponent(renderedElement);

    // 遞歸渲染懂傀,渲染子組件
    var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent, nativeContainerInfo, this._processChildContext(context));

    return markup;
  },

performInitialMount中先調(diào)用componentWillMount(),再將setState()產(chǎn)生的state改變進(jìn)行state合并蜡感,然后調(diào)用_renderValidatedComponent()返回ReactElement,它會調(diào)用render()方法恃泪。然后由ReactElement創(chuàng)建ReactComponent郑兴。最后進(jìn)行遞歸渲染。下面來看renderValidatedComponent()

_renderValidatedComponent: function () {
    var renderedComponent;
    ReactCurrentOwner.current = this;
    try {
      renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext();
    } finally {
      ReactCurrentOwner.current = null;
    }
    !(
    return renderedComponent;
  },

  _renderValidatedComponentWithoutOwnerOrContext: function () {
    var inst = this._instance;
    // 調(diào)用render方法贝乎,得到ReactElement情连。JSX經(jīng)過babel轉(zhuǎn)譯后其實(shí)就是createElement()方法。這一點(diǎn)在前面也講解過
    var renderedComponent = inst.render();
    return renderedComponent;
  },

3 存在期生命周期
組件實(shí)例對象已經(jīng)生成時览效,我們可以通過setState()來更新組件却舀。setState機(jī)制后面會有單獨(dú)文章分析虫几,現(xiàn)在只用知道它會調(diào)用updateComponent()來完成更新即可。下面來分析updateComponent

updateComponent: function(transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext
  ) {
    var inst = this._instance;
    var willReceive = false;
    var nextContext;
    var nextProps;

    // context對象如果有改動,則檢查propTypes等,這在開發(fā)階段可以報錯提醒
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    // 如果父元素類型相同,則跳過propTypes類型檢查
    if (prevParentElement === nextParentElement) {
      nextProps = nextParentElement.props;
    } else {
      nextProps = this._processProps(nextParentElement.props);
      willReceive = true;
    }

    // 調(diào)用componentWillReceiveProps,如果通過setState進(jìn)入的updateComponent挽拔,則沒有這一步
    if (willReceive && inst.componentWillReceiveProps) {
      inst.componentWillReceiveProps(nextProps, nextContext);
    }

    // 提前合并state,componentWillReceiveProps中調(diào)用setState不會重新渲染,在此處做合并即可,因?yàn)楹竺嬉彩且{(diào)用render的
    // 這樣可以避免沒必要的渲染
    var nextState = this._processPendingState(nextProps, nextContext);

    // 調(diào)用shouldComponentUpdate給shouldUpdate賦值
    // 如果通過forceUpdate進(jìn)入的updateComponent辆脸,即_pendingForceUpdate不為空,則不用判斷shouldComponentUpdate.
    var shouldUpdate = true;
    if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
      shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
    }

    // 如果shouldUpdate為true,則會執(zhí)行渲染,否則不會
    this._updateBatchNumber = null;
    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      // 執(zhí)行更新渲染,后面詳細(xì)分析
      this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext
      );
    } else {
      // shouldUpdate為false,則不會更新渲染
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
},

updateComponent中螃诅,先調(diào)用componentWillReceiveProps啡氢,然后合并setState導(dǎo)致的state變化。然后調(diào)用shouldComponentUpdate判斷是否需要更新渲染术裸。如果需要倘是,則調(diào)用_performComponentUpdate執(zhí)行渲染更新,下面接著分析performComponentUpdate袭艺。

_performComponentUpdate: function(nextElement,nextProps,nextState,nextContext,transaction,
                                     unmaskedContext
  ) {
    var inst = this._instance;

    // 判斷是否已經(jīng)update了
    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
      prevProps = inst.props;
      prevState = inst.state;
      prevContext = inst.context;
    }

    // render前調(diào)用componentWillUpdate
    if (inst.componentWillUpdate) {
      inst.componentWillUpdate(nextProps, nextState, nextContext);
    }

    // state props等屬性設(shè)置到內(nèi)部變量inst上
    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    // 內(nèi)部會調(diào)用render方法搀崭,重新解析ReactElement并得到HTML
    this._updateRenderedComponent(transaction, unmaskedContext);

    // render后調(diào)用componentDidUpdate
    if (hasComponentDidUpdate) {
      transaction.getReactMountReady().enqueue(
        inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
        inst
      );
    }
},

_performComponentUpdate會調(diào)用componentWillUpdate,然后在調(diào)用updateRenderedComponent進(jìn)行更新渲染猾编,最后調(diào)用componentDidUpdate瘤睹。下面來看看updateRenderedComponent中怎么調(diào)用render方法的。

_updateRenderedComponent: function(transaction, context) {
    var prevComponentInstance = this._renderedComponent;
    var prevRenderedElement = prevComponentInstance._currentElement;

    // _renderValidatedComponent內(nèi)部會調(diào)用render,得到ReactElement
    var nextRenderedElement = this._renderValidatedComponent();

    // 判斷是否做DOM diff袍镀。React為了簡化遞歸diff,認(rèn)為組件層級不變,且type和key不變(key用于listView等組件,很多時候我們沒有設(shè)置type)才update,否則先unmount再重新mount
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      // 遞歸updateComponent,更新子組件的Virtual DOM
      ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
    } else {
      var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance);

      // 不做DOM diff,則先卸載掉,然后再加載默蚌。也就是先unMountComponent,再mountComponent
      ReactReconciler.unmountComponent(prevComponentInstance, false);

      this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);

      // 由ReactElement創(chuàng)建ReactComponent
      this._renderedComponent = this._instantiateReactComponent(nextRenderedElement);

      // mountComponent掛載組件,得到組件對應(yīng)HTML
      var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context));

      // 將HTML插入DOM中
      this._replaceNodeWithMarkup(oldNativeNode, nextMarkup, prevComponentInstance);
    }
},

_renderValidatedComponent: function() {
    var renderedComponent;
    ReactCurrentOwner.current = this;
    try {
      renderedComponent =
        this._renderValidatedComponentWithoutOwnerOrContext();
    } finally {
      ReactCurrentOwner.current = null;
    }

    return renderedComponent;
},

_renderValidatedComponentWithoutOwnerOrContext: function() {
    var inst = this._instance;
    // 看到render方法了把,應(yīng)該放心了把~
    var renderedComponent = inst.render();

    return renderedComponent;
},

和mountComponent中一樣苇羡,updateComponent也是用遞歸的方式將各子組件進(jìn)行update的绸吸。這里要特別注意的是DOM diff。DOM diff是React中渲染加速的關(guān)鍵所在设江,它會幫我們算出virtual DOM中真正變化的部分锦茁,并對這部分進(jìn)行原生DOM操作。為了避免循環(huán)遞歸對比節(jié)點(diǎn)的低效率叉存,React中做了假設(shè)码俩,即只對層級不變,type不變歼捏,key不變的組件進(jìn)行Virtual DOM更新稿存。這其中的關(guān)鍵是shouldUpdateReactComponent,下面分析

function shouldUpdateReactComponent(prevElement, nextElement) {
  var prevEmpty = prevElement === null || prevElement === false;
  var nextEmpty = nextElement === null || nextElement === false;
  if (prevEmpty || nextEmpty) {
    return prevEmpty === nextEmpty;
  }

  var prevType = typeof prevElement;
  var nextType = typeof nextElement;
  // React DOM diff算法
  // 如果前后兩次為數(shù)字或者字符,則認(rèn)為只需要update(處理文本元素)
  // 如果前后兩次為DOM元素或React元素,則必須在同一層級內(nèi),且type和key不變(key用于listView等組件,很多時候我們沒有設(shè)置type)才update,否則先unmount再重新mount
  if (prevType === 'string' || prevType === 'number') {
    return (nextType === 'string' || nextType === 'number');
  } else {
    return (
      nextType === 'object' &&
      prevElement.type === nextElement.type &&
      prevElement.key === nextElement.key
    );
  }
}

4 銷毀

前面提到過瞳秽,更新組件時瓣履,如果不滿足DOM diff條件,會先unmountComponent, 然后再mountComponent练俐,下面我們來分析下unmountComponent時都發(fā)生了什么事袖迎。和mountComponent的多態(tài)一樣,不同type的ReactComponent也會有不同的unmountComponent行為。我們來分析下React自定義組件燕锥,也就是ReactCompositeComponent中的unmountComponent辜贵。

unmountComponent: function(safely) {
    if (!this._renderedComponent) {
      return;
    }
    var inst = this._instance;

    // 調(diào)用componentWillUnmount
    if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
      inst._calledComponentWillUnmount = true;
      // 安全模式下,將componentWillUnmount包在try-catch中归形。否則直接componentWillUnmount
      if (safely) {
        var name = this.getName() + '.componentWillUnmount()';
        ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
      } else {
        inst.componentWillUnmount();
      }
    }

    // 遞歸調(diào)用unMountComponent來銷毀子組件
    if (this._renderedComponent) {
      ReactReconciler.unmountComponent(this._renderedComponent, safely);
      this._renderedNodeType = null;
      this._renderedComponent = null;
      this._instance = null;
    }

    // reset等待隊(duì)列和其他等待狀態(tài)
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;
    this._pendingCallbacks = null;
    this._pendingElement = null;

    // reset內(nèi)部變量,防止內(nèi)存泄漏
    this._context = null;
    this._rootNodeID = null;
    this._topLevelWrapper = null;

    // 將組件從map中移除,還記得我們在mountComponent中將它加入了map中的吧
    ReactInstanceMap.remove(inst);
  },

可見托慨,unmountComponent還是比較簡單的,它就做三件事

調(diào)用componentWillUnmount()
遞歸調(diào)用unmountComponent(),銷毀子組件
將內(nèi)部變量置空连霉,防止內(nèi)存泄漏

5 總結(jié)

React自定義組件創(chuàng)建期榴芳,存在期,銷毀期三個階段的生命周期調(diào)用上面都講完了跺撼。三個入口函數(shù)mountComponent窟感,updateComponent,unmountComponent尤其關(guān)鍵歉井。大家如果有興趣柿祈,還可以自行分析ReactDOMEmptyComponent,ReactDOMComponent和ReactDOMTextComponent的這三個方法哩至。

深入學(xué)習(xí)React生命周期源碼可以幫我們理清各個方法的調(diào)用順序躏嚎,明白它們都是什么時候被調(diào)用的,哪些條件下才會被調(diào)用等等菩貌。閱讀源碼雖然有點(diǎn)枯燥卢佣,但能夠大大加深對上層API接口的理解,并體會設(shè)計者設(shè)計這些API的良苦用心箭阶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虚茶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仇参,更是在濱河造成了極大的恐慌嘹叫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诈乒,死亡現(xiàn)場離奇詭異罩扇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)怕磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門喂饥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肠鲫,你說我怎么就攤上這事仰泻。” “怎么了滩届?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我帜消,道長棠枉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任泡挺,我火速辦了婚禮辈讶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娄猫。我一直安慰自己贱除,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布媳溺。 她就那樣靜靜地躺著月幌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悬蔽。 梳的紋絲不亂的頭發(fā)上扯躺,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音蝎困,去河邊找鬼录语。 笑死,一個胖子當(dāng)著我的面吹牛禾乘,可吹牛的內(nèi)容都是我干的澎埠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼始藕,長吁一口氣:“原來是場噩夢啊……” “哼蒲稳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳄虱,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤弟塞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拙已,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體决记,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年倍踪,在試婚紗的時候發(fā)現(xiàn)自己被綠了系宫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡建车,死狀恐怖扩借,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缤至,我是刑警寧澤潮罪,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響嫉到,放射性物質(zhì)發(fā)生泄漏沃暗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一何恶、第九天 我趴在偏房一處隱蔽的房頂上張望孽锥。 院中可真熱鬧,春花似錦细层、人聲如沸惜辑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盛撑。三九已至,卻和暖如春虚缎,著一層夾襖步出監(jiān)牢的瞬間撵彻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工实牡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陌僵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓创坞,卻偏偏與公主長得像碗短,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子题涨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353