react源碼解剖——setState的異步模型

在我的另一篇文章 憑什么說virtual DOM是React的精髓所在 中提到過猫牡,react的性能優(yōu)化践宴,要歸功于批量DOM處理和Diff算法旧找。關(guān)于Diff算法的文章回俐,各平臺一抓一大把苍蔬,有興趣的同學可以自行查閱诱建。今天,我們就React的批量DOM處理碟绑,從一個小例子聊起俺猿,來探索一下setState 之后,究竟發(fā)生了什么格仲。

拋磚引玉

我們先來看一個簡單的例子押袍,十秒鐘思考然后確定控制臺彈出了什么:

var Test = React.createClass({
  getInitialState: function(){
    return {value: 'Hello Mangee'}
  },
  handleChangeValue: function(){
    this.setState({
      value: 'Goodbye Mangee'
    });
    console.log('newValue is', this.state.value);
  },
  render: function(){
    return <button onClick={this.handleChangeValue}>changeValue</button>;
  }
})
ReactDOM.render(
  <Test />,
  document.getElementById('example')
);

看完這個例子,大多數(shù)人都會自然而然認為控制臺彈出了 “newValue is Goodbye Mangee”凯肋,但事與愿違谊惭,控制臺實際上是彈出了原先的值—— “newValue is Hello Mangee”。

這是為什么呢侮东?我們來看看官方對于 setState 的一段注解:

Sets a subset of the state. Always use this to mutate
state. You should treat this.state as immutable.

There is no guarantee that this.state will be immediately updated, so
accessing this.state after calling this method may return the old value.

There is no guarantee that calls to setState will run synchronously,
as they may eventually be batched together. You can provide an optional
callback that will be executed when the call to setState is actually
completed.

從這段話中可以得知圈盔,setState對state的更新是異步的,原因正是為了實現(xiàn)我們的文首提及的批量DOM處理悄雅。

于是我們可以得到這樣一條信息:依靠 setState 的異步性驱敲,React在一段時間間隔內(nèi),將所有DOM更新收集起來宽闲,然后批量處理众眨。因此,學習 setState 的異步模型容诬,也有助于你對 React 性能優(yōu)化策略的進一步了解娩梨。

異步模型解剖

由于 React 源碼使用了大量的繼承和依賴注入,部分對象的方法需要依據(jù)依賴或繼承關(guān)系一層層追溯览徒,這里我不做逐步分析狈定,想要深入了解的同學可以自行研究。

那么接下來吱殉,就跟隨筆者的腳步掸冤,通過源碼來探尋一下從 setState 到 state 改變的完整過程。

在此之前友雳,你需要準備好 React 的兩個包稿湿,React 和 ReactDOM。

npm install react
npm install react-dom
從 setState 說起
```   
this.setState({});
```   

當執(zhí)行到 setState 時押赊,我們需要來找找 setState 是在哪定義的饺藤,以此來探尋 setState 后的第一步。

// react/ReactComponent
ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

我們發(fā)現(xiàn)流礁,setState 調(diào)用了組件本身的 updater 對象的兩個方法enqueueSetState 和 enqueueCallback涕俗,其中,callback 是更新完成后執(zhí)行的回調(diào)函數(shù)神帅。

updater(更新器)再姑,每個 React 組件都擁有的、用于驅(qū)動 state 更新的工具對象找御,按照繼承依賴關(guān)系元镀,可以追溯到 updater 的本體,即react-dom/ReactUpdateQueue霎桅,其中定義了我們所要找的 enqueueSetState 和 enqueueCallback 兩個方法栖疑。

那么擇其一,enqueueSetState 里邊滔驶,又發(fā)生了什么遇革?

// react-dom/ReactUpdateQueue
enqueueSetState: function (publicInstance, partialState) {

  // 獲得 internalInstance  實例
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

  // 將 partialState 推入實例自身的 _pendingStateQueue (狀態(tài)隊列)等候更新
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  queue.push(partialState);

  // 驅(qū)動更新
  enqueueUpdate(internalInstance);
}

enqueueCallback 的實現(xiàn)步驟跟以上一樣,最終結(jié)果也是將回調(diào)函數(shù) callback 推入回調(diào)隊列揭糕,等待執(zhí)行萝快。

到目前,待更新的 state 已經(jīng)在狀態(tài)隊列里候著了著角,什么時候拿出來更新呢杠巡?這就得來繼續(xù)看看 enqueueUpdate 這個函數(shù)了。

// react-dom/ReactUpdateQueue
function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

原來是執(zhí)行了 ReactUpdates 模塊的 enqueueUpdate 方法雇寇,讓我們把頻道切換到 react-dom/ReactUpdates氢拥。

// react-dom/ReactUpdates
function enqueueUpdate(component) {

  // 若 batchingStrategy 不處于批量更新收集狀態(tài),則 batchedUpdates 所有隊列中的更新
  // 值得注意的是锨侯,此時傳入的 component 將不參加當前批的更新嫩海,而是作為下一批進行更新
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  // 若 batchingStrategy 處于批量更新收集狀態(tài),則把 component 推進 dirtyComponents 數(shù)組等待更新
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

這里囚痴,batchingStrategy 對象是作為一個批處理的管理者叁怪,依照指定的批量策略,對到來的一系列 component 更新做分批深滚。

設(shè)想一個場景:我們?nèi)ネ孢^山車時奕谭,管理員會分批安排游客進場涣觉,等這一批游客玩完之后,再安排下一批進場血柳,而在當前批游客正在玩的過程中官册,有游客到來,都需要先排隊难捌。

在這里膝宁,component 就是游客,batchingStrategy 就是管理員根吁,isBatchingUpdates 標志就是有沒有游客正在玩员淫。當一批DOM處理完成后,調(diào)用 batchedUpdates 方法击敌,更新下一批 dirtyComponents介返。

有些人可能會有疑問,為什么這里感覺像是開了兩個線程沃斤,一個在完成“排隊”映皆,一個在完成“批處理”。實際上不是的轰枝,js是單線程的捅彻,所以當一個event loop內(nèi)陸陸續(xù)續(xù)有新的 component 更新驅(qū)動來到這里時,都會被阻塞在 dirtyComponents 中鞍陨,等到全部收集完畢步淹,才進行批處理,不存在邊處理邊排隊的情況诚撵。

另外缭裆,值得注意的是,batchingStrategy 對象是通過 injection 方法注入的寿烟,經(jīng)過一番艱難追溯之后澈驼,發(fā)現(xiàn)了 batchingStrategy 就是 ReactDefaultBatchingStrategy。讓我們看看這個模塊調(diào)用 batchedUpdates 方法之后筛武,發(fā)生了什么缝其。

// react-dom/ReactDefaultBatchingStrategy 
var ReactDefaultBatchingStrategy = {

  isBatchingUpdates: false,

  batchedUpdates: function (callback, a, b, c, d, e) {

    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};

可以看出,ReactDefaultBatchingStrategy 對象十分簡潔徘六,isBatchingUpdates 是批收集判斷的標志位内边,batchedUpdates 方法用于發(fā)動一個批處理。在其中我們可以發(fā)現(xiàn)待锈,isBatchingUpdates 標志位就是在 batchedUpdates 發(fā)起的時候置為 true 的漠其。那 isBatchingUpdates 又是在哪里復(fù)位為 false 的呢?這就得引出一個React 框架設(shè)計的核心概念——Transaction (事務(wù))。

隨處可見的Transaction

Transaction(事務(wù))是一個針對函數(shù)執(zhí)行的包裝(wrapper)和屎,React關(guān)于 Transaction 的源碼中拴驮,出現(xiàn)了這樣一幅有趣而形象的圖:

 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>

從上圖可知,Transaction 實例 transaction 在創(chuàng)建的時候向自身注入 wrapper柴信,實現(xiàn)的效果是套啤,通過 transaction.perform 執(zhí)行的函數(shù) anyMethod,先執(zhí)行 transaction 的所有 initialize 方法颠印,再執(zhí)行 anyMethod纲岭,執(zhí)行完再執(zhí)行所有的 close 方法抹竹。引用來自 React 源碼剖析系列 - 解密 setState 的一個簡單的例子說明:

// react-dom/Transaction 
var Transaction = require('./Transaction');

// 我們自己定義的 Transaction
var MyTransaction = function() {
  // do sth.
};

Object.assign(MyTransaction.prototype, Transaction.Mixin, {
  getTransactionWrappers: function() {
    return [{
      initialize: function() {
        console.log('before method perform');
      },
      close: function() {
        console.log('after method perform');
      }
    }];
  };
});

var transaction = new MyTransaction();
var testMethod = function() {
  console.log('test');
}
transaction.perform(testMethod);

// before method perform
// test
// after method perform

基于此线罕,我們回過頭來看看,ReactDefaultBatchingStrategy.batchedUpdates 執(zhí)行后窃判,發(fā)生了什么钞楼。

// react-dom/ReactDefaultBatchingStrategy 
var ReactDefaultBatchingStrategy = {

  isBatchingUpdates: false,

  batchedUpdates: function (callback, a, b, c, d, e) {

    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};

batchedUpdates 方法中,transaction 是 ReactDefaultBatchingStrategyTransaction 的實例袄琳,也是一類事務(wù)询件,perform 方法傳入的 callback 正是我們前邊探究過的、用于做DOM批收集的 enqueueUpdate 函數(shù)∷舴現(xiàn)在讓我們把注意力轉(zhuǎn)移到它的 initialize 和 close 方法上:

// react-dom/ReactDefaultBatchingStrategy 
// 定義復(fù)位 wrapper
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

// 定義批更新 wrapper
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

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

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
});

initialize 方法是兩個空函數(shù)宛琅,我們不關(guān)注,close 方法逗旁,按照順序嘿辟,將在 enqueueUpdate 執(zhí)行結(jié)束后,先把 isBatchingUpdates 復(fù)位片效,再發(fā)起一個 DOM 的批更新红伦。到這里我們恍然大悟,所謂的批處理淀衣,實際上是明確地分為了批收集和批更新兩個步驟昙读,而上邊所有的內(nèi)容,都只是在完成批收集這個環(huán)節(jié)膨桥。

React 對于這個核心環(huán)節(jié)的設(shè)計可是一點都不含糊蛮浑,所以懵逼了的同學請翻回去重新來一遍,還沒吐的同學請堅持只嚣。

批更新

整理一下妝容陵吸,我們繼續(xù)來看看這后續(xù)的批更新環(huán)節(jié)是如何實現(xiàn)的。

對于批更新這部分介牙,涉及到關(guān)于 React 從 virtual DOM 向真實 DOM 反饋的許多細節(jié)考慮壮虫,一來筆者未能完全滲透,二來與本文所要探究的問題無關(guān),因此接下來貼出的源碼是經(jīng)過大量刪減的囚似,只保留了我們需要關(guān)注的部分剩拢。

銜接批收集的最后一步,ReactUpdates 模塊調(diào)用了 flushBatchedUpdates 方法饶唤。

// react-dom/ReactUpdates 
var flushBatchedUpdates = function () {

  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      // 創(chuàng)建一個 ReactUpdatesFlushTransaction 實例
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      // 調(diào)用實例的 perform 方法進行更新
      transaction.perform(runBatchedUpdates, null, transaction);
      // 釋放實例徐伐,回歸實例池
      ReactUpdatesFlushTransaction.release(transaction);
    }
  }
};

核心步驟又出現(xiàn)了另外一個 transaction,它執(zhí)行了一個 runBatchedUpdates 函數(shù)募狂。當然办素,老規(guī)矩,遇到 transaction祸穷,查查它的 initialize 和 close 方法是很必要的性穿,但由于 runBatchedUpdates 執(zhí)行的調(diào)用棧比較深入,要講的略多雷滚,所以我們放到 runBatchedUpdates 執(zhí)行完畢再來看需曾。先關(guān)注 runBatchedUpdates 完成了哪些:

// react-dom/ReactUpdates 
function runBatchedUpdates(transaction) {

  var len = transaction.dirtyComponentsLength;

  // 排序,保證 dirtyComponent 從父級到子級的 render 順序
  dirtyComponents.sort(mountOrderComparator);
  updateBatchNumber++;

  // 遍歷 dirtyComponents
  for (var i = 0; i < len; i++) {

    var component = dirtyComponents[i];

    // 取到該 dirtyComponent 的回調(diào)數(shù)組
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;
    // 更新該 dirtyComponent
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);

    // 當存在 callbacks 時祈远,將 callbacks 逐項提取呆万,推入 transaction.callbackQueue 
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
      }
    }
  }
}

遍歷 dirtyComponents 數(shù)組,并且利用一個新模塊 ReactReconciler 的
performUpdateIfNecessary 方法將 dirtyComponent 逐個更新车份。

讓我們來看看 ReactReconciler.performUpdateIfNecessary 完成了什么:

// react-dom/ReactReconciler
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
  internalInstance.performUpdateIfNecessary(transaction);
}

調(diào)用了組件的 performUpdateIfNecessary 方法谋减,而又一番艱苦追溯,我們發(fā)現(xiàn)扫沼,組件為 ReactCompositeComponent 的實例出爹,因而也在 ReactCompositeComponent 中發(fā)現(xiàn)了關(guān)于它的定義:

// react-dom/ReactReconciler
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {

  var inst = this._instance;
  var nextContext = inst.context;
  var nextProps = nextParentElement.props;

  ``` // 對 comtext 和 props 的一系列校驗

  // 關(guān)注的核心
  var nextState = this._processPendingState(nextProps, nextContext);

  ``` // 拿到更新后的 nextState 進行反饋到真實 DOM 上的更新
}

最終,整個過程算是繞了一圈充甚,調(diào)用了組件上的 _processPendingState 方法以政,在這個方法中,我們終于完成了對 state 的合并更新:

_processPendingState: function (props, context) {
  var inst = this._instance;
  var queue = this._pendingStateQueue;
  var replace = this._pendingReplaceState;
  this._pendingReplaceState = false;
  this._pendingStateQueue = null;

  if (!queue) {
    return inst.state;
  }

  if (replace && queue.length === 1) {
    return queue[0];
  }

  var nextState = _assign({}, replace ? queue[0] : inst.state);
  // 將該組件狀態(tài)隊列里所有的 state 更新統(tǒng)一處理合并
  for (var i = replace ? 1 : 0; i < queue.length; i++) {
    var partial = queue[i];
    _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
  }

  return nextState;
}

咦伴找,說好的回調(diào)函數(shù)會在更新完成后調(diào)用的呢盈蛮?
別急,不是還漏了前文提到的那個 transaction 的 close 方法沒瞧瞧嘛:

var NESTED_UPDATES = {
  initialize: function () {
    this.dirtyComponentsLength = dirtyComponents.length;
  },
  close: function () {
    // 移除已遍歷過的 dirtyComponents
    if (this.dirtyComponentsLength !== dirtyComponents.length) {
      dirtyComponents.splice(0, this.dirtyComponentsLength);
      flushBatchedUpdates();
    } else {
      dirtyComponents.length = 0;
    }
  }
};

var UPDATE_QUEUEING = {
  initialize: function () {
    this.callbackQueue.reset();
  },
  close: function () {
    // 完成更新后執(zhí)行 callbackQueue 的回調(diào)函數(shù)
    this.callbackQueue.notifyAll();
  }
};

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

function ReactUpdatesFlushTransaction() {
}

_assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
}

看技矮,配合得真完美抖誉,不出所料,正是利用了 transaction 的 close 方法衰倦,將一開始緩存在 callbacks 隊列中的回調(diào)函數(shù)袒炉,逐一取出并執(zhí)行,這里我就不做展開了樊零。

捋一捋思路

經(jīng)過了這樣一系列復(fù)雜而深入的調(diào)用我磁,setState 終于完成了 state 的合并更新孽文。但其實,我所提取的只是 setState 的一個通用過程夺艰,文首拋出的例子芋哭,其實早在 click 事件觸發(fā)的那一刻起,就已經(jīng)執(zhí)行了一個 batchedUpdates郁副,因此等執(zhí)行到 setState 的時候减牺,已經(jīng)置身于一個大的 transaction 中,其調(diào)用棧已經(jīng)非常深入了存谎。但是篇幅限制拔疚,也因筆者能力有限,故而放棄對 react 完整的事件觸發(fā)機制進行深入探討既荚,這里就大致地還原一下setState的異步流機制稚失,給看到這里還沒崩潰的同學,總結(jié)一下吧:

1固以、click事件觸發(fā)墩虹;
2嘱巾、React 內(nèi)置事件監(jiān)聽器啟動一個事務(wù)(transaction) 憨琳,把批策略(ReactDefaultBatchingStrategy)的批收集標志位置為 true;
3旬昭、在事務(wù)的 perform 中篙螟,setState發(fā)起;
4问拘、觸發(fā)更新器(updater)上的 enqueueSetState 和 enqueueCallback遍略,把 state 和 callback 推入等待隊列,并且驅(qū)動 enqueueUpdate 更新骤坐;
5绪杏、觸發(fā) batchingStrategy 的 batchedUpdates 方法,啟動一個事務(wù)纽绍,進行批收集蕾久;
6、收集完成后拌夏,觸發(fā)事務(wù)的 close 方法僧著,復(fù)位標志位,并執(zhí)行批處理障簿;
7盹愚、觸發(fā) ReactUpdates 的 flushBatchedUpdates 方法,啟動另外一個事務(wù)站故,執(zhí)行一系列的調(diào)用最終完成更新皆怕;
8、更新完成后,觸發(fā)事務(wù)的 close 方法愈腾,調(diào)用隊列里的回調(diào)函數(shù)朗兵;
9、最外層的事務(wù)完成顶滩,釋放調(diào)用棧余掖。

關(guān)于 setState 的異步模型解析就到這里,學藝不精礁鲁,恐有錯漏盐欺,歡迎吐槽!

參考文獻如下仅醇,極力推薦:
拆解setState[一][一源看世界][之React]
拆解setState[二][一源看世界][之React]
拆解setState[三][一源看世界][之React]
setState 之后發(fā)生了什么 —— 淺談 React 中的 Transaction
React 源碼剖析系列 - 解密 setState
React源碼分析5 — setState機制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冗美,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子析二,更是在濱河造成了極大的恐慌粉洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叶摄,死亡現(xiàn)場離奇詭異属韧,居然都是意外死亡,警方通過查閱死者的電腦和手機蛤吓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門宵喂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人会傲,你說我怎么就攤上這事锅棕。” “怎么了淌山?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵裸燎,是天一觀的道長。 經(jīng)常有香客問我泼疑,道長德绿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任王浴,我火速辦了婚禮脆炎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘氓辣。我一直安慰自己秒裕,他們只是感情好,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布钞啸。 她就那樣靜靜地躺著几蜻,像睡著了一般喇潘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梭稚,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天颖低,我揣著相機與錄音,去河邊找鬼弧烤。 笑死忱屑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的暇昂。 我是一名探鬼主播莺戒,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼急波!你這毒婦竟也來了从铲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤澄暮,失蹤者是張志新(化名)和其女友劉穎名段,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泣懊,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡伸辟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗅定。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片自娩。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡用踩,死狀恐怖渠退,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脐彩,我是刑警寧澤碎乃,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站惠奸,受9級特大地震影響梅誓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜佛南,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一梗掰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗅回,春花似錦及穗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苛白。三九已至,卻和暖如春焚虱,著一層夾襖步出監(jiān)牢的瞬間购裙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工鹃栽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躏率,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓民鼓,卻偏偏與公主長得像禾锤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摹察,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348

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