React源碼05 - 各類組件的 Update

多種不同類型的組件的更新過程嗦枢,以及如何遍歷節(jié)點(diǎn)形成新的 Fiber 樹谚攒,即 reconcilerChildren 調(diào)和子節(jié)點(diǎn)的過程止毕。

-1. 入口和優(yōu)化

  • 判斷組件更新是否可以優(yōu)化
  • 根據(jù)節(jié)點(diǎn)類型分發(fā)處理
  • 根據(jù) expirationTime 等信息判斷是否可以跳過

幫助優(yōu)化整個樹的更新過程的方法牌芋。
只有 ReactDOM.render() 的時才會更新 RootFiber乎完,其后的更新都是在子節(jié)點(diǎn)上揉抵。

workLoop

function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until the deadline runs out of time.
    while (nextUnitOfWork !== null && !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

performUnitOfWork:更新子樹亡容,調(diào)用了 beginWork:

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;
  // See if beginning this work spawns more work.
  startWorkTimer(workInProgress);
  let next;
    if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }

    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    // 這里返回子節(jié)點(diǎn)
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }
  ReactCurrentOwner.current = null;
  return next;
}

beginWork:

  • 判斷如果是非首次渲染(current !== null):

新老 props 一樣,而且本次更新任務(wù)的優(yōu)先級并沒有超過現(xiàn)有任務(wù)的最高優(yōu)先級冤今,則做一些優(yōu)化的工作闺兢,然后調(diào)用 xxx 用于跳過當(dāng)前 Fiber 樹及其子節(jié)點(diǎn)的所有更新。

  • 然后可能是非首次但沒能跳過戏罢,也可能仍然是首次渲染(代碼太多屋谭,沒貼)阱佛。
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  // 傳入的 current,第一次渲染
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
      oldProps === newProps &&
      !hasLegacyContextChanged() &&
      (updateExpirationTime === NoWork ||
        updateExpirationTime > renderExpirationTime)
    ) {
      // 處理不同類型的節(jié)點(diǎn)
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        case HostRoot:
            // 太多戴而,暫略
      }
      // 用于跳過子節(jié)點(diǎn)的更新
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  }
    
  // 然后可能是非首次但沒能跳過凑术,也可能仍然是首次渲染(代碼太多,沒貼)所意。
}

bailoutOnAlreadyFinishedWork:
用于跳過子節(jié)點(diǎn)的更新淮逊。
但也要看任務(wù)優(yōu)先級也不緊急的話,就函數(shù)返回 null扶踊,外部的 while 遍歷就停止了泄鹏,也就跳過了所有子組件的更新。
但如果優(yōu)先級更高的話秧耗,則克隆 current 上面的 child 并返回备籽,然后再返回到 workLoop 中,進(jìn)入下次 child 更新循環(huán)分井,去嘗試更新子節(jié)點(diǎn)车猬。這就是個不斷向下遍歷節(jié)點(diǎn)的過程。

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  cancelWorkTimer(workInProgress);

  if (current !== null) {
    // Reuse previous context list
    workInProgress.firstContextDependency = current.firstContextDependency;
  }

  if (enableProfilerTimer) {
    // Don't update "base" render times for bailouts.
    stopProfilerTimerIfRunning(workInProgress);
  }

  // Check if the children have any pending work.
  const childExpirationTime = workInProgress.childExpirationTime;
  if (
    childExpirationTime === NoWork ||
    childExpirationTime > renderExpirationTime
  ) {
    // The children don't have any work either. We can skip them.
    // TODO: Once we add back resuming, we should check if the children are
    // a work-in-progress set. If so, we need to transfer their effects.
    return null;
  } else {
    // This fiber doesn't have work, but its subtree does. Clone the child
    // fibers and continue.
    cloneChildFibers(current, workInProgress);
    return workInProgress.child;
  }
}

0. 各種不同類型組件的更新

先說整體概念尺锚,既然是不同類型的組件更新珠闰,因此關(guān)注的粒度就是在一整棵 fiber 樹中,某一層是某一種類型的組件瘫辩,其上的更新伏嗜。而其子組件的更新,會在下一次 workLoop 遍歷的時候再真正處理伐厌。

接下來是各種組件類型的更新承绸,也就是調(diào)和 Fiber 子節(jié)點(diǎn)的過程。
在 react-reconciler/ReactFiberBeginWork.js/beginWork() 方法中:

Fiber 上的 tag 標(biāo)記了不同的組件類型挣轨,在這里用作 switch 的判斷军熏,根據(jù)不同組件類型分別進(jìn)行 fiber 的調(diào)和更新:

  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      const elementType = workInProgress.elementType;
      return mountIndeterminateComponent(
        current,
        workInProgress,
        elementType,
        renderExpirationTime,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    default:
      invariant(
        false,
        'Unknown unit of work tag. This error is likely caused by a bug in ' +
          'React. Please file an issue.',
      );
  }

1. Function component 的更新

updateFunctionComponent:
之前說過每個 Fiber 節(jié)點(diǎn)上的 type 就是指 createReactElement 時傳入第一個參數(shù),即 函數(shù)/class/原生dom標(biāo)簽字符串/內(nèi)置的某些類型(如React.Fragment 什么的刃唐,大多數(shù)時候會是個 symbol 標(biāo)記)羞迷。
所以從 type 上獲取對應(yīng)的組件函數(shù),傳入 nextProps 和 context 執(zhí)行后獲取 nextChildren画饥,也就是函數(shù)組件返回的東西衔瓮,作為自己的 children。
但是 children 是 react element抖甘,因此需要還需要調(diào)用 reconcileChildren 涉及到 轉(zhuǎn)化為 Fiber 對象和更新等热鞍。
然后返回 workInProgress.child,因?yàn)閯偛?reconcileChildren 時會把處理好的 fiber 掛載到 child 上。

函數(shù)組件的更新如此看來是比較簡單的薇宠,主要復(fù)雜的地方在 reconcileChildren 的過程中偷办。

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderExpirationTime,
) {
  const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
  const context = getMaskedContext(workInProgress, unmaskedContext);

  let nextChildren;
  prepareToReadContext(workInProgress, renderExpirationTime);
  if (__DEV__) {
    ReactCurrentOwner.current = workInProgress;
    ReactCurrentFiber.setCurrentPhase('render');
    nextChildren = Component(nextProps, context);
    ReactCurrentFiber.setCurrentPhase(null);
  } else {
    // 這里調(diào)用函數(shù)組件,傳入props和context澄港,等到該函數(shù)組件的子 element 樹椒涯。
    nextChildren = Component(nextProps, context);
  }

  // React DevTools reads this flag.
  workInProgress.effectTag |= PerformedWork;
  // 復(fù)雜的在這個方法中
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

2. reconcileChildren

  • 根據(jù) reactElement 上的 props.children 生成 fiber 子樹。
  • 判斷 Fiber 對象是否可以復(fù)用回梧。因?yàn)橹挥械谝淮问钦w全部渲染废岂,而后續(xù)更新時自然要考慮復(fù)用。
  • 列表根據(jù) key 優(yōu)化狱意。
  • 最終迭代處理完整個 fiber 樹湖苞。

調(diào)和子節(jié)點(diǎn),主要分為第一次渲染详囤,和后續(xù)更新财骨。二者區(qū)別通過變量 shouldTrackSideEffects “是否追蹤副作用” 來區(qū)分,也就是非第一次渲染藏姐,會涉及到相關(guān)副作用的處理和復(fù)用隆箩。
reconcileChildren:

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

props.children 中的合法的成員主要就是 數(shù)組/字符串/數(shù)字 以及 react element。

  • React.Fragment 的是臨時的節(jié)點(diǎn)包各,渲染更新時要被跳過摘仅,newChild = newChild.props.children靶庙,也就是把下一層的 children 賦值為當(dāng)前某個待更新的組件的 children问畅。
  • 找到可復(fù)用的節(jié)點(diǎn)進(jìn)行 return,而不可復(fù)用的節(jié)點(diǎn)六荒,也就是 key 變了护姆,就不會復(fù)用老的 fiber,老的 fiber 被刪除掏击。就涉及到重新創(chuàng)建子節(jié)點(diǎn)卵皂。

而重新創(chuàng)建子節(jié)點(diǎn)時要看子節(jié)點(diǎn)的類型:

對于 REACT_ELEMENT_TYPE:
在 reconcileSingleElement 中根據(jù)不同的組件類型,得到不同的 fiberTag砚亭,然后調(diào)用 createFiber(fiberTag, pendingProps, key, mode) 創(chuàng)建創(chuàng)建不同的 Fiber灯变。

對于 string 或者 number,也就是文本節(jié)點(diǎn):
只看第一個節(jié)點(diǎn)是不是文本節(jié)點(diǎn):

  • 如果此前老的第一個子節(jié)點(diǎn)也是文本節(jié)點(diǎn)捅膘,那么就復(fù)用留著添祸,而刪除相鄰節(jié)點(diǎn),因?yàn)楝F(xiàn)在要更新為文本節(jié)點(diǎn)了寻仗,所以留一個節(jié)點(diǎn)就夠用了刃泌。
  • 如果不是,那么就整個刪除老的子節(jié)點(diǎn)。

對于 Array 或者 IteratorFn(有迭代器的函數(shù)):下一節(jié)再說耙替。
**
如果以上情況都不符合亚侠,那就全部當(dāng)做非法(我編的術(shù)語)子節(jié)點(diǎn),因此就將其全部“刪除”即可俗扇。

嘴上說著刪除硝烂,但實(shí)際上,不能真的刪铜幽,現(xiàn)在是在 workInProgress fiber 樹上進(jìn)行更新操作钢坦,并不會真的刪除 dom,而只是打相應(yīng)的標(biāo)記啥酱,是刪除操作爹凹?那就給 fiber 節(jié)點(diǎn)打上 Deletion 標(biāo)記,也就是:
childToDelete.effectTag = Deletion镶殷。

之前說過更新分兩個階段禾酱,render (有可能被打斷) 和 commit (不會被打斷) 階段,在 render 階段為這些 fiber 打上相應(yīng)的操作標(biāo)記后绘趋,在后面的 commit 階段在根據(jù)這些標(biāo)記颤陶,去真正的操作瀏覽器 dom。

3. key 和數(shù)組調(diào)和

  • key 的作用陷遮。作為對比判斷依據(jù)滓走,從而盡量復(fù)用老的 fiber 節(jié)點(diǎn)。
  • 對比數(shù)組 children 是否可復(fù)用帽馋。
  • generator 和 Array 的區(qū)別搅方,基本差不多,只是前者是 ES6 迭代器相關(guān)知識绽族,需要不斷調(diào)用 next() 來獲取成員姨涡。

使用 react 時如果返回的是數(shù)組(如使用 Array.prototype.map),需要為每個子項(xiàng)指定 key 值吧慢。
**
以相同順序分別遍歷新老 children涛漂,對比 key 是否相同 來決定是否復(fù)用老的 fiber 節(jié)點(diǎn):
直到遇到 key 開始不相同了,就不再對標(biāo)著復(fù)用检诗,而此時 props.children 也就是 react element 的數(shù)組還有剩余匈仗,也就是還沒全部轉(zhuǎn)化為 fiber。那么有兩種情況:

  • 對位的老的子節(jié)點(diǎn) oldFiber 已經(jīng)用完了逢慌,那么就為剩余未轉(zhuǎn)換的 react element 每個都單獨(dú)創(chuàng)建 fiber 對象悠轩。
  • 如果 oldFiber 還有剩余,只是一一對位的 key 開始變得和新的 key 不匹配涕癣,所以才打斷了第一階段的復(fù)用哗蜈。但其實(shí)還有機(jī)會進(jìn)行復(fù)用前标,可以遍歷剩余的 oldFiber,以其 key 作為 Map 數(shù)據(jù)結(jié)構(gòu)的 key距潘,進(jìn)行存儲炼列。然后看新的 key 是否能從 Map 中找到相應(yīng)的 oldFiber,以便進(jìn)行復(fù)用音比。這說明本次更新中俭尖,某個節(jié)點(diǎn)是位置只是位置順序變了。還是可以找到并復(fù)用的洞翩。Map 中剩余的就是真的沒用了稽犁,就標(biāo)記為刪除。

4. ClassComponent

在 react hooks 出現(xiàn)之前骚亿,唯一能引起二次更新的方法已亥,就是 class 實(shí)例上的 setState 和 forceUpdate

  • 計(jì)算新的 state:會使用 Object.assign({}, preState, particalState),用局部 state 對 preState 進(jìn)行淺覆蓋来屠,來生成新的 state虑椎。
  • 在 class 實(shí)例上工秩,分別根據(jù)初次渲染還是后續(xù)更新來調(diào)用不同的生命周期方法姚垃。

5. IndeterminateComponent

在最初第一次渲染時斗搞,對于所有的 functionalComponent 都初始標(biāo)記為 IndeterminateComponent 類型愉镰,
然后主要根據(jù)其返回的 value 中是否有 render 方法,從而才將 workInProgress.tag 其進(jìn)一步明確為 ClassComponent 還是 FunctionComponent伴栓。
基于內(nèi)部這種判斷邏輯贸宏,我們竟然可以通過在函數(shù)式組件中返回的對象上提供 render 函數(shù)颓哮,以此將函數(shù)式組件“模擬”出了 class 組件的形式磕仅。這算是個小 hack 技巧珊豹,實(shí)際中應(yīng)該沒人這么干。

import React from 'react'

export default function TestIndeterminateComponent() {
  return {
    componentDidMount() {
      console.log('invoker')
    },
    render() {
      return <span>aaa</span>
    },
  }
}

6. HostRoot

該特殊類型對應(yīng)的是 FiberRoot 節(jié)點(diǎn)宽涌。

7. HostComponent & HostText

  • HostComponent:原生 dom 節(jié)點(diǎn)平夜,也就是 jsx 中小寫的那種。
  • HostText:文本節(jié)點(diǎn)卸亮。

8. PortalComponent

獨(dú)特地方在于其需要有單獨(dú)的掛載點(diǎn)。

9. ForwardRef

  • 下次更新傳入的 ref 如果沒變化玩裙,會跳過當(dāng)前節(jié)點(diǎn)的更新( bailoutOnAlreadyFinishedWork )兼贸。
  • 要注意被 ForwardRef 包裹后的組件內(nèi)部獲取不到外部提供的 context。
  • 然后同樣是調(diào)和子節(jié)點(diǎn)吃溅,根據(jù)調(diào)用 render 得到新的 react element溶诞,調(diào)和為相應(yīng)的 fiber 節(jié)點(diǎn)。

10. Mode

  • ConCurrentMode
  • StrictMode

這樣的組件類型其實(shí)只是一種標(biāo)記决侈,在 Fiber 的 mode 屬性(通過位運(yùn)算)上進(jìn)行記錄螺垢,在后面的創(chuàng)建更新時,mode 作為計(jì)算不同的 expirationTime 的依據(jù)。

11. MemoComponent

本質(zhì)的更新邏輯和 FunctionalComponent 一樣枉圃,只是多了一步對新老 props 的 shallowEqual 淺比較功茴,從而有機(jī)會跳過本次更新。

LazyComponent 和 SuspenseComponent 后面單獨(dú)研究孽亲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坎穿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子返劲,更是在濱河造成了極大的恐慌玲昧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篮绿,死亡現(xiàn)場離奇詭異孵延,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亲配,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門隙袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弃榨,你說我怎么就攤上這事菩收。” “怎么了鲸睛?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵娜饵,是天一觀的道長。 經(jīng)常有香客問我官辈,道長箱舞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任拳亿,我火速辦了婚禮晴股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肺魁。我一直安慰自己电湘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布鹅经。 她就那樣靜靜地躺著寂呛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘾晃。 梳的紋絲不亂的頭發(fā)上贷痪,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音蹦误,去河邊找鬼劫拢。 笑死肉津,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舱沧。 我是一名探鬼主播妹沙,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狗唉!你這毒婦竟也來了初烘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤分俯,失蹤者是張志新(化名)和其女友劉穎肾筐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缸剪,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吗铐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杏节。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唬渗。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奋渔,靈堂內(nèi)的尸體忽然破棺而出镊逝,到底是詐尸還是另有隱情,我是刑警寧澤嫉鲸,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布撑蒜,位于F島的核電站,受9級特大地震影響玄渗,放射性物質(zhì)發(fā)生泄漏座菠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一藤树、第九天 我趴在偏房一處隱蔽的房頂上張望浴滴。 院中可真熱鬧,春花似錦岁钓、人聲如沸升略。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽降宅。三九已至,卻和暖如春囚霸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背激才。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工拓型, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留额嘿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓劣挫,卻偏偏與公主長得像册养,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子压固,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348