React Fiber是如何運(yùn)轉(zhuǎn)的

前言

從 React 16 開始属百,React 采用了 Fiber 機(jī)制替代了原先基于原生執(zhí)行棧遞歸遍歷 VDOM 的方案秃症,提高了頁面渲染性能和用戶體驗(yàn)候址。乍一聽 Fiber 好像挺神秘,在原生執(zhí)行棧都還沒搞懂的情況下种柑,又整出個 Fiber岗仑,還能不能愉快的寫代碼了。別慌聚请,老鐵荠雕!下面就來嘮嘮關(guān)于 Fiber 那點(diǎn)事兒。

什么是 Fiber

Fiber 的英文含義是“纖維”,它是比線程(Thread)更細(xì)的線舞虱,比線程(Thread)控制得更精密的執(zhí)行模型欢际。在廣義計算機(jī)科學(xué)概念中,F(xiàn)iber 又是一種協(xié)作的(Cooperative)編程模型矾兜,幫助開發(fā)者用一種【既模塊化又協(xié)作化】的方式來編排代碼损趋。

簡單點(diǎn)說,F(xiàn)iber 就是 React 16 實(shí)現(xiàn)的一套新的更新機(jī)制椅寺,讓 React 的更新過程變得可控浑槽,避免了之前一竿子遞歸到底影響性能的做法。

關(guān)于 Fiber 你需要知道的基礎(chǔ)知識

1 瀏覽器刷新率(幀)

頁面的內(nèi)容都是一幀一幀繪制出來的返帕,瀏覽器刷新率代表瀏覽器一秒繪制多少幀桐玻。目前瀏覽器大多是 60Hz(60幀/s),每一幀耗時也就是在 16ms 左右荆萤。原則上說 1s 內(nèi)繪制的幀數(shù)也多镊靴,畫面表現(xiàn)就也細(xì)膩。那么在這一幀的(16ms) 過程中瀏覽器又干了啥呢链韭?

通過上面這張圖可以清楚的知道偏竟,瀏覽器一幀會經(jīng)過下面這幾個過程:

接受輸入事件

執(zhí)行事件回調(diào)

開始一幀

執(zhí)行 RAF (RequestAnimationFrame)

頁面布局,樣式計算

渲染

執(zhí)行 RIC (RequestIdelCallback)

第七步的 RIC 事件不是每一幀結(jié)束都會執(zhí)行敞峭,只有在一幀的 16ms 中做完了前面 6 件事兒且還有剩余時間踊谋,才會執(zhí)行。這里提一下旋讹,如果一幀執(zhí)行結(jié)束后還有時間執(zhí)行 RIC 事件殖蚕,那么下一幀需要在事件執(zhí)行結(jié)束才能繼續(xù)渲染,所以 RIC 執(zhí)行不要超過 30ms沉迹,如果長時間不將控制權(quán)交還給瀏覽器睦疫,會影響下一幀的渲染,導(dǎo)致頁面出現(xiàn)卡頓和事件響應(yīng)不及時鞭呕。

2. JS 原生執(zhí)行棧

React Fiber 出現(xiàn)之前笼痛,React 通過原生執(zhí)行棧遞歸遍歷 VDOM。當(dāng)瀏覽器引擎第一次遇到 JS 代碼時琅拌,會產(chǎn)生一個全局執(zhí)行上下文并將其壓入執(zhí)行棧缨伊,接下來每遇到一個函數(shù)調(diào)用,又會往棧中壓入一個新的上下文进宝。比如:

function A(){

? B();

? C();

}

function B(){}

function C(){}

A();

引擎在執(zhí)行的時候刻坊,會形成如下這樣的執(zhí)行棧:?

瀏覽器引擎會從執(zhí)行棧的頂端開始執(zhí)行,執(zhí)行完畢就彈出當(dāng)前執(zhí)行上下文党晋,開始執(zhí)行下一個函數(shù)谭胚,直到執(zhí)行棧被清空才會停止徐块。然后將執(zhí)行權(quán)交還給瀏覽器。由于 React 將頁面視圖視作一個個函數(shù)執(zhí)行的結(jié)果灾而。每一個頁面往往由多個視圖組成胡控,這就意味著多個函數(shù)的調(diào)用。

如果一個頁面足夠復(fù)雜旁趟,形成的函數(shù)調(diào)用棧就會很深昼激。每一次更新,執(zhí)行棧需要一次性執(zhí)行完成锡搜,中途不能干其他的事兒橙困,只能"一心一意"。結(jié)合前面提到的瀏覽器刷新率耕餐,JS 一直執(zhí)行凡傅,瀏覽器得不到控制權(quán),就不能及時開始下一幀的繪制肠缔。如果這個時間超過 16ms夏跷,當(dāng)頁面有動畫效果需求時,動畫因?yàn)闉g覽器不能及時繪制下一幀明未,這時動畫就會出現(xiàn)卡頓槽华。不僅如此,因?yàn)槭录憫?yīng)代碼是在每一幀開始的時候執(zhí)行亚隅,如果不能及時繪制下一幀硼莽,事件響應(yīng)也會延遲庶溶。

3. 時間分片(Time Slicing)

時間分片指的是一種將多個粒度小的任務(wù)放入一個時間切片(一幀)中執(zhí)行的一種方案煮纵,在 React Fiber 中就是將多個任務(wù)放在了一個時間片中去執(zhí)行。

4. 鏈表

在 React Fiber 中用鏈表遍歷的方式替代了 React 16 之前的棧遞歸方案偏螺。在 React 16 中使用了大量的鏈表行疏。例如:

使用多向鏈表的形式替代了原來的樹結(jié)構(gòu)

例如下面這個組件:

<div id="id">

? A1

<div id="B1">

? ? B1

<div id="C1"></div>

</div>

<div id="B2">

? ? ? B2

</div>

</div>

會使用下面這樣的鏈表表示:?

副作用單鏈表

狀態(tài)更新單鏈表

...

鏈表是一種簡單高效的數(shù)據(jù)結(jié)構(gòu),它在當(dāng)前節(jié)點(diǎn)中保存著指向下一個節(jié)點(diǎn)的指針套像,就好像火車一樣一節(jié)連著一節(jié)

遍歷的時候酿联,通過操作指針找到下一個元素。但是操作指針時(調(diào)整順序和指向)一定要小心夺巩。

鏈表相比順序結(jié)構(gòu)數(shù)據(jù)格式的好處就是:

操作更高效贞让,比如順序調(diào)整、刪除柳譬,只需要改變節(jié)點(diǎn)的指針指向就好了喳张。

不僅可以根據(jù)當(dāng)前節(jié)點(diǎn)找到下一個節(jié)點(diǎn),在多向鏈表中美澳,還可以找到他的父節(jié)點(diǎn)或者兄弟節(jié)點(diǎn)销部。

但鏈表也不是完美的摸航,缺點(diǎn)就是:

比順序結(jié)構(gòu)數(shù)據(jù)更占用空間,因?yàn)槊總€節(jié)點(diǎn)對象還保存有指向下一個對象的指針舅桩。

不能自由讀取酱虎,必須找到他的上一個節(jié)點(diǎn)。

React 用空間換時間擂涛,更高效的操作可以方便根據(jù)優(yōu)先級進(jìn)行操作读串。同時可以根據(jù)當(dāng)前節(jié)點(diǎn)找到其他節(jié)點(diǎn),在下面提到的掛起和恢復(fù)過程中起到了關(guān)鍵作用歼指。

React Fiber 是如何實(shí)現(xiàn)更新過程可控爹土?

前面講完基本知識,現(xiàn)在正式開始介紹今天的主角 Fiber踩身,看看 React Fiber 是如何實(shí)現(xiàn)對更新過程的管控胀茵。

更新過程的可控主要體現(xiàn)在下面幾個方面:

任務(wù)拆分

任務(wù)掛起棺蛛、恢復(fù)正罢、終止

任務(wù)具備優(yōu)先級

1. 任務(wù)拆分

前面提到,React Fiber 之前是基于原生執(zhí)行棧攀芯,每一次更新操作會一直占用主線程附鸽,直到更新完成脱拼。這可能會導(dǎo)致事件響應(yīng)延遲,動畫卡頓等現(xiàn)象坷备。

在 React Fiber 機(jī)制中熄浓,它采用"化整為零"的戰(zhàn)術(shù),將調(diào)和階段(Reconciler)遞歸遍歷 VDOM 這個大任務(wù)分成若干小任務(wù)省撑,每個任務(wù)只負(fù)責(zé)一個節(jié)點(diǎn)的處理赌蔑。例如:

importReactfrom"react";

importReactDomfrom"react-dom"

constjsx = (

<div id="A1">

? ? A1

? ? <div id="B1">

? ? ? B1

? ? ? <div id="C1">C1</div>

? ? ? <div id="C2">C2</div>

? ? </div>

? ? <div id="B2">B2</div>

? </div>

)

ReactDom.render(jsx,document.getElementById("root"))

這個組件在渲染的時候會被分成八個小任務(wù),每個任務(wù)用來分別處理 A1(div)竟秫、A1(text)娃惯、B1(div)、B1(text)肥败、C1(div)趾浅、C1(text)、C2(div)馒稍、C2(text)皿哨、B2(div)、B2(text)纽谒。再通過時間分片证膨,在一個時間片中執(zhí)行一個或者多個任務(wù)。這里提一下佛舱,所有的小任務(wù)并不是一次性被切分完成椎例,而是處理當(dāng)前任務(wù)的時候生成下一個任務(wù)挨决,如果沒有下一個任務(wù)生成了,就代表本次渲染的 Diff 操作完成订歪。

. 掛起脖祈、恢復(fù)、終止

再說掛起刷晋、恢復(fù)盖高、終止之前,不得不提兩棵 Fiber 樹眼虱,workInProgress tree 和 currentFiber tree喻奥。

workInProgress 代表當(dāng)前正在執(zhí)行更新的 Fiber 樹。在 render 或者 setState 后捏悬,會構(gòu)建一顆 Fiber 樹撞蚕,也就是 workInProgress tree,這棵樹在構(gòu)建每一個節(jié)點(diǎn)的時候會收集當(dāng)前節(jié)點(diǎn)的副作用过牙,整棵樹構(gòu)建完成后甥厦,會形成一條完整的副作用鏈。

currentFiber 表示上次渲染構(gòu)建的 Filber 樹寇钉。在每一次更新完成后 workInProgress 會賦值給 currentFiber刀疙。在新一輪更新時 workInProgress tree 再重新構(gòu)建,新 workInProgress 的節(jié)點(diǎn)通過 alternate 屬性和 currentFiber 的節(jié)點(diǎn)建立聯(lián)系扫倡。

在新 workInProgress tree 的創(chuàng)建過程中谦秧,會同 currentFiber 的對應(yīng)節(jié)點(diǎn)進(jìn)行 Diff 比較,收集副作用撵溃。同時也會復(fù)用和 currentFiber 對應(yīng)的節(jié)點(diǎn)對象疚鲤,減少新創(chuàng)建對象帶來的開銷。也就是說無論是創(chuàng)建還是更新征懈,掛起石咬、恢復(fù)以及終止操作都是發(fā)生在 workInProgress tree 創(chuàng)建過程中揩悄。workInProgress tree 構(gòu)建過程其實(shí)就是循環(huán)的執(zhí)行任務(wù)和創(chuàng)建下一個任務(wù)卖哎,大致過程如下:

當(dāng)沒有下一個任務(wù)需要執(zhí)行的時候,workInProgress tree 構(gòu)建完成删性,開始進(jìn)入提交階段亏娜,完成真實(shí) DOM 更新。

在構(gòu)建 workInProgressFiber tree 過程中可以通過掛起蹬挺、恢復(fù)和終止任務(wù)维贺,實(shí)現(xiàn)對更新過程的管控。下面簡化了一下源碼巴帮,大致實(shí)現(xiàn)如下:

letnextUnitWork =null;//下一個執(zhí)行單元

//開始調(diào)度

function shceduler(task){

? ? nextUnitWork = task;

}

//循環(huán)執(zhí)行工作

function workLoop(deadline){

letshouldYield =false;//是否要讓出時間片交出控制權(quán)

while(nextUnitWork && !shouldYield){

? ? nextUnitWork = performUnitWork(nextUnitWork)

shouldYield = deadline.timeRemaining()<1// 沒有時間了溯泣,檢出控制權(quán)給瀏覽器

? }

if(!nextUnitWork) {

conosle.log("所有任務(wù)完成")

//commitRoot() //提交更新視圖

? }

// 如果還有任務(wù)虐秋,但是交出控制權(quán)后,請求下次調(diào)度

requestIdleCallback(workLoop,{timeout:5000})

}

/*

* 處理一個小任務(wù),其實(shí)就是一個 Fiber 節(jié)點(diǎn)垃沦,如果還有任務(wù)就返回下一個需要處理的任務(wù)客给,沒有就代表整個

*/

function performUnitWork(currentFiber){

? ....

? return FiberNode

}

掛起

當(dāng)?shù)谝粋€小任務(wù)完成后,先判斷這一幀是否還有空閑時間肢簿,沒有就掛起下一個任務(wù)的執(zhí)行靶剑,記住當(dāng)前掛起的節(jié)點(diǎn),讓出控制權(quán)給瀏覽器執(zhí)行更高優(yōu)先級的任務(wù)池充。

恢復(fù)

在瀏覽器渲染完一幀后桩引,判斷當(dāng)前幀是否有剩余時間,如果有就恢復(fù)執(zhí)行之前掛起的任務(wù)收夸。如果沒有任務(wù)需要處理坑匠,代表調(diào)和階段完成,可以開始進(jìn)入渲染階段卧惜。這樣完美的解決了調(diào)和過程一直占用主線程的問題笛辟。

那么問題來了他是如何判斷一幀是否有空閑時間的呢?答案就是我們前面提到的 RIC (RequestIdleCallback) 瀏覽器原生 API序苏,React 源碼中為了兼容低版本的瀏覽器手幢,對該方法進(jìn)行了 Polyfill。

當(dāng)恢復(fù)執(zhí)行的時候又是如何知道下一個任務(wù)是什么呢忱详?答案在前面提到的鏈表围来。在 React Fiber 中每個任務(wù)其實(shí)就是在處理一個 FiberNode 對象,然后又生成下一個任務(wù)需要處理的 FiberNode匈睁。順便提一嘴监透,這里提到的FiberNode 是一種數(shù)據(jù)格式,下面是它沒有開美顏的樣子:

class FiberNode {

constructor(tag, pendingProps, key, mode) {

// 實(shí)例屬性

this.tag = tag;// 標(biāo)記不同組件類型航唆,如函數(shù)組件胀蛮、類組件、文本糯钙、原生組件...

this.key = key;// react 元素上的 key 就是 jsx 上寫的那個 key 粪狼,也就是最終 ReactElement 上的

this.elementType =null;// createElement的第一個參數(shù),ReactElement 上的 type

this.type =null;// 表示fiber的真實(shí)類型 任岸,elementType 基本一樣再榄,在使用了懶加載之類的功能時可能會不一樣

this.stateNode =null;// 實(shí)例對象,比如 class 組件 new 完后就掛載在這個屬性上面享潜,如果是RootFiber困鸥,那么它上面掛的是 FiberRoot,如果是原生節(jié)點(diǎn)就是 dom 對象

// fiber

this.return =null;// 父節(jié)點(diǎn),指向上一個 fiber

this.child =null;// 子節(jié)點(diǎn)剑按,指向自身下面的第一個 fiber

this.sibling =null;// 兄弟組件, 指向一個兄弟節(jié)點(diǎn)

this.index =0;//? 一般如果沒有兄弟節(jié)點(diǎn)的話是0 當(dāng)某個父節(jié)點(diǎn)下的子節(jié)點(diǎn)是數(shù)組類型的時候會給每個子節(jié)點(diǎn)一個 index疾就,index 和 key 要一起做 diff

this.ref =null;// reactElement 上的 ref 屬性

this.pendingProps = pendingProps;// 新的 props

this.memoizedProps =null;// 舊的 props

this.updateQueue =null;// fiber 上的更新隊列執(zhí)行一次 setState 就會往這個屬性上掛一個新的更新, 每條更新最終會形成一個鏈表結(jié)構(gòu)澜术,最后做批量更新

this.memoizedState =null;// 對應(yīng)? memoizedProps,上次渲染的 state猬腰,相當(dāng)于當(dāng)前的 state瘪板,理解成 prev 和 next 的關(guān)系

this.mode = mode;// 表示當(dāng)前組件下的子組件的渲染方式

// effects

this.effectTag = NoEffect;// 表示當(dāng)前 fiber 要進(jìn)行何種更新

this.nextEffect =null;// 指向下個需要更新的fiber

this.firstEffect =null;// 指向所有子節(jié)點(diǎn)里,需要更新的 fiber 里的第一個

this.lastEffect =null;// 指向所有子節(jié)點(diǎn)中需要更新的 fiber 的最后一個

this.expirationTime = NoWork;// 過期時間漆诽,代表任務(wù)在未來的哪個時間點(diǎn)應(yīng)該被完成

this.childExpirationTime = NoWork;// child 過期時間

this.alternate =null;// current 樹和 workInprogress 樹之間的相互引用

? }

}

額…看著好像有點(diǎn)上頭侮攀,這是開了美顏的樣子:

是不是好看多了?在每次循環(huán)的時候厢拭,找到下一個執(zhí)行需要處理的節(jié)點(diǎn)兰英。

function performUnitWork(currentFiber){

//beginWork(currentFiber) //找到兒子,并通過鏈表的方式掛到currentFiber上供鸠,每一偶兒子就找后面那個兄弟

//有兒子就返回兒子

if(currentFiber.child){

returncurrentFiber.child;

? }

//如果沒有兒子畦贸,則找弟弟

while(currentFiber){//一直往上找

//completeUnitWork(currentFiber);//將自己的副作用掛到父節(jié)點(diǎn)去

if(currentFiber.sibling){

returncurrentFiber.sibling

? ? }

? ? currentFiber = currentFiber.return;

? }

}

在一次任務(wù)結(jié)束后返回該處理節(jié)點(diǎn)的子節(jié)點(diǎn)或兄弟節(jié)點(diǎn)或父節(jié)點(diǎn)。只要有節(jié)點(diǎn)返回楞捂,說明還有下一個任務(wù)薄坏,下一個任務(wù)的處理對象就是返回的節(jié)點(diǎn)。通過一個全局變量記住當(dāng)前任務(wù)節(jié)點(diǎn)寨闹,當(dāng)瀏覽器再次空閑的時候胶坠,通過這個全局變量,找到它的下一個任務(wù)需要處理的節(jié)點(diǎn)恢復(fù)執(zhí)行繁堡。就這樣一直循環(huán)下去沈善,直到?jīng)]有需要處理的節(jié)點(diǎn)返回,代表所有任務(wù)執(zhí)行完成椭蹄。最后大家手拉手闻牡,就形成了一顆 Fiber 樹。

終止

其實(shí)并不是每次更新都會走到提交階段绳矩。當(dāng)在調(diào)和過程中觸發(fā)了新的更新罩润,在執(zhí)行下一個任務(wù)的時候,判斷是否有優(yōu)先級更高的執(zhí)行任務(wù)翼馆,如果有就終止原來將要執(zhí)行的任務(wù)割以,開始新的 workInProgressFiber 樹構(gòu)建過程,開始新的更新流程写妥。這樣可以避免重復(fù)更新操作拳球。這也是在 React 16 以后生命周期函數(shù) componentWillMount 有可能會執(zhí)行多次的原因审姓。

3. 任務(wù)具備優(yōu)先級

React Fiber 除了通過掛起珍特,恢復(fù)和終止來控制更新外,還給每個任務(wù)分配了優(yōu)先級魔吐。具體點(diǎn)就是在創(chuàng)建或者更新 FiberNode 的時候扎筒,通過算法給每個任務(wù)分配一個到期時間(expirationTime)莱找。在每個任務(wù)執(zhí)行的時候除了判斷剩余時間,如果當(dāng)前處理節(jié)點(diǎn)已經(jīng)過期嗜桌,那么無論現(xiàn)在是否有空閑時間都必須執(zhí)行改任務(wù)奥溺。

同時過期時間的大小還代表著任務(wù)的優(yōu)先級。

任務(wù)在執(zhí)行過程中順便收集了每個 FiberNode 的副作用骨宠,將有副作用的節(jié)點(diǎn)通過 firstEffect浮定、lastEffect、nextEffect 形成一條副作用單鏈表 AI(TEXT)-B1(TEXT)-C1(TEXT)-C1-C2(TEXT)-C2-B1-B2(TEXT)-B2-A层亿。

其實(shí)最終都是為了收集到這條副作用鏈表桦卒,有了它,在接下來的渲染階段就通過遍歷副作用鏈完成 DOM 更新匿又。這里需要注意方灾,更新真實(shí) DOM 的這個動作是一氣呵成的,不能中斷碌更,不然會造成視覺上的不連貫裕偿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痛单,隨后出現(xiàn)的幾起案子嘿棘,更是在濱河造成了極大的恐慌,老刑警劉巖旭绒,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔫巩,死亡現(xiàn)場離奇詭異,居然都是意外死亡快压,警方通過查閱死者的電腦和手機(jī)圆仔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔫劣,“玉大人坪郭,你說我怎么就攤上這事÷龃保” “怎么了歪沃?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫌松。 經(jīng)常有香客問我沪曙,道長,這世上最難降的妖魔是什么萎羔? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任液走,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缘眶。我一直安慰自己嘱根,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布巷懈。 她就那樣靜靜地躺著该抒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顶燕。 梳的紋絲不亂的頭發(fā)上凑保,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音涌攻,去河邊找鬼愉适。 笑死,一個胖子當(dāng)著我的面吹牛癣漆,可吹牛的內(nèi)容都是我干的维咸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼惠爽,長吁一口氣:“原來是場噩夢啊……” “哼癌蓖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婚肆,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤租副,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后较性,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體用僧,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年赞咙,在試婚紗的時候發(fā)現(xiàn)自己被綠了责循。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡攀操,死狀恐怖院仿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情速和,我是刑警寧澤歹垫,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站颠放,受9級特大地震影響排惨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碰凶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一暮芭、第九天 我趴在偏房一處隱蔽的房頂上張望鹿驼。 院中可真熱鬧,春花似錦谴麦、人聲如沸蠢沿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至恤磷,卻和暖如春面哼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扫步。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工魔策, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人河胎。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓闯袒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親游岳。 傳聞我的和親對象是個殘疾皇子政敢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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