react源碼閱讀筆記(3)batchedUpdates與Transaction

本文使用的是react 15.6.1的代碼

在上篇文章組件的渲染中_renderNewRootComponent中最后調(diào)用了ReactUpdates.batchedUpdates函數(shù)什乙,上文中只是簡(jiǎn)單的分析了最后實(shí)際是在內(nèi)部調(diào)用了其中的第一個(gè)參數(shù)batchedMountComponentIntoNode,其實(shí)并沒有那么簡(jiǎn)單秕噪,今天我們就來看看batchedUpdates實(shí)際做了些什么內(nèi)容

ReactUpdates.batchedUpdates

//批量更新方法
function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected(); //不用理會(huì),單純的打印方法
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

實(shí)際上是調(diào)用了batchingStrategy.batchedUpdates中的方法玉转,但是翻遍了整個(gè)文件钢属,發(fā)現(xiàn)batchingStrategy這個(gè)對(duì)象一直是null,那么batchingStrategy是怎么初始化的呢?在ReactUpdates中郊艘,發(fā)現(xiàn)定義了這樣一個(gè)對(duì)象ReactUpdatesInjection玻佩,其中代碼如下

var ReactUpdatesInjection = {
  injectReconcileTransaction: function(ReconcileTransaction) {
    ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
  },

  injectBatchingStrategy: function(_batchingStrategy) {
    batchingStrategy = _batchingStrategy;
  },
};

其中有一個(gè)方法injectBatchingStrategy出嘹,那么是不是外部誰調(diào)用了該方法,使得最后batchingStrategy被注入進(jìn)來了咬崔,在ReactDOM源碼中税稼,我們發(fā)現(xiàn)有一行

ReactDefaultInjection.inject(); //注入事件烦秩,環(huán)境變量,各種參數(shù)值

查看ReactDefaultInjection.inject()方法后郎仆,又找到了這樣一行代碼

ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);

再次查看ReactInjection中的源代碼

var ReactInjection = {
  Component: ReactComponentEnvironment.injection,
  DOMProperty: DOMProperty.injection,
  EmptyComponent: ReactEmptyComponent.injection,
  EventPluginHub: EventPluginHub.injection,
  EventPluginUtils: EventPluginUtils.injection,
  EventEmitter: ReactBrowserEventEmitter.injection,
  HostComponent: ReactHostComponent.injection,
  Updates: ReactUpdates.injection,
};

也就是說只祠,在ReactDOM之中,會(huì)執(zhí)行到 ReactUpdates.injection.injectBatchingStrategy方法扰肌,同時(shí)抛寝,將ReactDefaultBatchingStrategy傳入其中,所以最后batchingStrategy = ReactDefaultBatchingStrategy,那么回到前文batchingStrategy.batchedUpdates(callback, a, b, c, d, e)曙旭,這里面的batchedUpdates實(shí)際就是執(zhí)行的ReactDefaultBatchingStrategy中的方法
源碼地址[ReactDefaultBatchingStrategy]

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * Call the provided function in a context within which calls to `setState`
   * and friends are batched such that components aren't updated unnecessarily.
   */
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);//重點(diǎn)代碼
    }
  },
};

代碼很簡(jiǎn)單盗舰,第一次調(diào)用的時(shí)候ReactDefaultBatchingStrategy.isBatchingUpdates肯定是false,那么就會(huì)調(diào)用transaction.perform方法,并將相應(yīng)回調(diào)傳入其中,那么我們來看看ReactDefaultBatchingStrategy相關(guān)代碼


var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

// ReactDefaultBatchingStrategyTransaction.prototype添加Transaction對(duì)象的方法桂躏,如reinitializeTransaction钻趋,同時(shí)用新的函數(shù)覆蓋getTransactionWrappers
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});

var transaction = new ReactDefaultBatchingStrategyTransaction();

發(fā)現(xiàn)transation是ReactDefaultBatchingStrategyTransaction的實(shí)例,但是perform方法是Transaction.js提供的剂习,我們看看Transaction方法


'use strict';

var invariant = require('invariant');

var OBSERVED_ERROR = {};

/**
 * `Transaction` creates a black box that is able to wrap any method such that
 * certain invariants are maintained before and after the method is invoked
 * (Even if an exception is thrown while invoking the wrapped method). Whoever
 * instantiates a transaction can provide enforcers of the invariants at
 * creation time. The `Transaction` class itself will supply one additional
 * automatic invariant for you - the invariant that any transaction instance
 * should not be run while it is already being run. You would typically create a
 * single instance of a `Transaction` for reuse multiple times, that potentially
 * is used to wrap several different methods. Wrappers are extremely simple -
 * they only require implementing two methods.
 *
 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
 *
 * Use cases:
 * - Preserving the input selection ranges before/after reconciliation.
 *   Restoring selection even in the event of an unexpected error.
 * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
 *   while guaranteeing that afterwards, the event system is reactivated.
 * - Flushing a queue of collected DOM mutations to the main UI thread after a
 *   reconciliation takes place in a worker thread.
 * - Invoking any collected `componentDidUpdate` callbacks after rendering new
 *   content.
 * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
 *   to preserve the `scrollTop` (an automatic scroll aware DOM).
 * - (Future use case): Layout calculations before and after DOM updates.
 *
 * Transactional plugin API:
 * - A module that has an `initialize` method that returns any precomputation.
 * - and a `close` method that accepts the precomputation. `close` is invoked
 *   when the wrapped process is completed, or has failed.
 *
 * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
 * that implement `initialize` and `close`.
 * @return {Transaction} Single transaction for reuse in thread.
 *
 * @class Transaction
 */
var TransactionImpl = {
  reinitializeTransaction: function(): void {
    //獲取wrappers
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

  _isInTransaction: false,

  /**
   * @abstract
   * @return {Array<TransactionWrapper>} Array of transaction wrappers.
   * 由具體Transaction來提供蛮位,如ReactDefaultBatchingStrategyTransaction
   */
  getTransactionWrappers: null,

  isInTransaction: function(): boolean {
    return !!this._isInTransaction;
  },

  perform: function<
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    T: (a: A, b: B, c: C, d: D, e: E, f: F) => G,
  >(method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F): G {
    var errorThrown;
    var ret;
    try {
      //正在Transaction ing中
      this._isInTransaction = true;
     
      errorThrown = true;

      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        //如果initializeAll沒有拋出異常的話
        if (errorThrown) {
          // If `method` throws, prefer to show that stack trace over any thrown
          // by invoking `closeAll`.
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          // Since `method` didn't throw, we don't want to silence the exception
          // here.
          //進(jìn)入close生命周期
          this.closeAll(0);
        }
      } finally {
        // Transaction結(jié)束
        this._isInTransaction = false;
      }
    }
    return ret;
  },

  initializeAll: function(startIndex: number): void {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
       
        this.wrapperInitData[i] = OBSERVED_ERROR;
        //調(diào)用wrapper的initialize方法
        this.wrapperInitData[i] = wrapper.initialize
          ? wrapper.initialize.call(this)
          : null;
      } finally {
        //如果調(diào)用initialize有問題,則startIndex+1在調(diào)用
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
        
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

 
  closeAll: function(startIndex: number): void {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
      
        errorThrown = true;
        if (initData !== OBSERVED_ERROR && wrapper.close) {
          wrapper.close.call(this, initData);
        }
        errorThrown = false;
      } finally {
        if (errorThrown) {
        
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  },
};

export type Transaction = typeof TransactionImpl;

module.exports = TransactionImpl;

代碼比較少鳞绕,從代碼注釋的簡(jiǎn)圖看失仁,在創(chuàng)建Transations的時(shí)候,會(huì)先注入wrapper,當(dāng)調(diào)用perform方法的時(shí)候猾昆,會(huì)依次調(diào)用wrapper的initialize方法陶因,隨后在調(diào)用perform中傳入的方法骡苞,最后在調(diào)用依次調(diào)用wrapper中的close方法垂蜗,簡(jiǎn)單看下來,有設(shè)計(jì)模式(代理)的思路在里面,我們具體看perform的實(shí)現(xiàn)首先解幽,調(diào)用了initializeAll方法贴见,然后循環(huán)拿到transactionWrappers依次調(diào)用initialize方法,隨后調(diào)用perform的第一參數(shù)躲株,最后又調(diào)用closeAll方法片部,依次調(diào)用transactionWrappers中的close的方法,最后將_isInTransaction置為false表示transation結(jié)束霜定。和圖中描述的一樣档悠,也就是說,不同的Transation會(huì)去實(shí)現(xiàn)不同的wrapper來代理具體的方法望浩,那么ReactDefaultBatchingStrategyTransaction的wrapper是什么辖所?在ReactDefaultBatchingStrategy之中,實(shí)例化ReactDefaultBatchingStrategyTransaction時(shí)會(huì)調(diào)用
this.reinitializeTransaction()方法磨德,該方法中調(diào)用了getTransactionWrappers獲取具體的wrappers缘回,對(duì)應(yīng)的吆视,ReactDefaultBatchingStrategyTransaction重寫了getTransactionWrappers方法,因此酥宴,得到返回值
[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
這是wrapper的實(shí)現(xiàn)

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

代碼比較簡(jiǎn)單啦吧,兩個(gè)wraper都只有close方法,根據(jù)代碼描述拙寡,會(huì)先執(zhí)行FLUSH_BATCHED_UPDATES的close方法也就是ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)

var flushBatchedUpdates = function() {
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      // 從緩存池中獲取ReactUpdatesFlushTransaction對(duì)象
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      // 調(diào)用runBatchedUpdates
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};

react運(yùn)行中當(dāng)存在臟組件的時(shí)候授滓,會(huì)用transaction調(diào)用runBatchedUpdates方法,在執(zhí)行之前,react使用了對(duì)象池這樣的優(yōu)化手段來獲取/生成transaction對(duì)象倒庵,該部分暫時(shí)跳過褒墨,后面在細(xì)讀,我們?cè)趤砜纯磖unBatchedUpdates做了些什么

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;

  // 排序擎宝,保證 dirtyComponent 從父級(jí)到子級(jí)的 render 順序
  dirtyComponents.sort(mountOrderComparator);

  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];

    // 獲取該組件的回調(diào)數(shù)組
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      // Duck type TopLevelWrapper. This is probably always true.
      if (component._currentElement.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }
    // 更新該 dirtyComponent 重點(diǎn)代碼
    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber,
    );

    if (markerName) {
      console.timeEnd(markerName);
    }

    // 如果存在組件回調(diào)函數(shù)郁妈,將其放入回調(diào)隊(duì)列中
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance(),
        );
      }
    }
  }
}

代碼不多,首先是將臟components進(jìn)行排序绍申,變成從父組件到子組件的順序排列噩咪,隨后遍歷組件調(diào)用performUpdateIfNecessary更新數(shù)據(jù),同時(shí)將組件的回調(diào)壓入回調(diào)隊(duì)列中极阅,等待最后調(diào)用胃碾,這里我們注意到并不是更新一個(gè)組件的時(shí)候就調(diào)用其回調(diào),而是等到所有組件都更新完以后才去調(diào)用筋搏,看看核心代碼performUpdateIfNecessary方法仆百,內(nèi)部就一行核心代碼internalInstance.performUpdateIfNecessary(transaction)即調(diào)用了ReactComponent對(duì)象的performUpdateIfNecessary方法,即ReactCompositeComponent.performUpdateIfNecessary方法

performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context,
      );
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      //注意奔脐,調(diào)用了this.updateComponent方法俄周,但是element以及content都是component自己的即_currentElement,_context
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context,
      );
    } else {
      this._updateBatchNumber = null;
    }
  }

繞了一圈,最后還是調(diào)用組件自己的updateComponent方法髓迎,而updateComponent會(huì)去執(zhí)行React組件的生命周期峦朗,如componentWillReceiveProps,shouldComponentUpdate排龄,componentWillUpdate波势,render, componentDidUpdate。完成整套流程橄维。后面我們慢慢介紹React的生命周期尺铣。

回到上面代碼,剛才知道runBatchedUpdates是有事務(wù)去調(diào)用的争舞,react事務(wù)會(huì)先執(zhí)行wrapper中的init方法凛忿,在執(zhí)行close方法,我們?cè)趤砜纯碦eactUpdatesFlushTransaction的wrapper相關(guān)代碼做了什么兑障。


var NESTED_UPDATES = {
  initialize: function() {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function() {
    // 在批量更新侄非,如果有新的dirtyComponents被push蕉汪,那么,需要再一次批量更新逞怨,從新加入的dirtyComponents開始
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      dirtyComponents.splice(0, this.dirtyComponentsLength);
      flushBatchedUpdates();
    } else {
      dirtyComponents.length = 0;
    }
  },
};

var UPDATE_QUEUEING = {
  initialize: function() {
    // 重置回調(diào)隊(duì)列
    this.callbackQueue.reset();
  },
  close: function() {
    // 執(zhí)行回調(diào)方法
    this.callbackQueue.notifyAll();
  },
};

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

看到上面代碼者疤,不得不感嘆react設(shè)計(jì)的巧妙,這里巧妙的使用了transition的生命周期叠赦,在initialize時(shí)獲取當(dāng)前dirtyComponent的個(gè)數(shù)驹马,最后結(jié)束(close)的時(shí)候再次校驗(yàn),如果數(shù)量不對(duì)除秀,那么說明dirtyComponents有新增糯累,再一次進(jìn)行flushBatchedUpdates,因?yàn)楦虏僮魇桥康牟岵龋虼瞬粫?huì)出現(xiàn)改一個(gè)就執(zhí)行一次的情況泳姐,大大提高了性能,第二個(gè)wrapper主要是處理回調(diào)隊(duì)列里面的內(nèi)容暂吉,當(dāng)更新處理完成后胖秒,最后調(diào)用UPDATE_QUEUEING close方法,調(diào)用notifyAll去執(zhí)行所有dirtyComponent的回調(diào)函數(shù)隊(duì)列

總結(jié)

注入的設(shè)計(jì)極大的方便了代碼的維護(hù)慕的,將實(shí)現(xiàn)給原子化阎肝,若以后版本升級(jí),算法更新肮街,可以直接切換injectiion中的原子換成新的版本即可风题。同時(shí),高度抽象的代碼也給閱讀帶來了困難

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嫉父,一起剝皮案震驚了整個(gè)濱河市沛硅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熔号,老刑警劉巖稽鞭,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸟整,死亡現(xiàn)場(chǎng)離奇詭異引镊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)篮条,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門弟头,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涉茧,你說我怎么就攤上這事赴恨。” “怎么了伴栓?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵伦连,是天一觀的道長(zhǎng)雨饺。 經(jīng)常有香客問我,道長(zhǎng)惑淳,這世上最難降的妖魔是什么额港? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮歧焦,結(jié)果婚禮上移斩,老公的妹妹穿的比我還像新娘。我一直安慰自己绢馍,他們只是感情好向瓷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舰涌,像睡著了一般猖任。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓷耙,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天超升,我揣著相機(jī)與錄音,去河邊找鬼哺徊。 笑死室琢,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的落追。 我是一名探鬼主播盈滴,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼轿钠!你這毒婦竟也來了巢钓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤疗垛,失蹤者是張志新(化名)和其女友劉穎症汹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贷腕,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡背镇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泽裳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞒斩。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涮总,靈堂內(nèi)的尸體忽然破棺而出胸囱,到底是詐尸還是另有隱情,我是刑警寧澤瀑梗,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布烹笔,位于F島的核電站裳扯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谤职。R本人自食惡果不足惜嚎朽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柬帕。 院中可真熱鬧哟忍,春花似錦、人聲如沸陷寝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凤跑。三九已至爆安,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仔引,已是汗流浹背扔仓。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咖耘,地道東北人翘簇。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像儿倒,于是被迫代替她去往敵國(guó)和親版保。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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