Angular 中的響應(yīng)式編程 -- 淺淡 Rx 的流式思維

Rx--隱藏在Angular 2.x中利劍 一文中我們已經(jīng)初步的了解了 Rx 和 Rx 在 Angular 的應(yīng)用韩肝。 今天我們一起通過(guò)一個(gè)具體的例子來(lái)理解響應(yīng)式編程設(shè)計(jì)的思路。最后會(huì)看看剛剛發(fā)布的 Angular 4 的新特性給響應(yīng)式編程帶來(lái)了什么新鮮的元素迷守。

為什么要做響應(yīng)式編程?

我給出的答案很簡(jiǎn)單:響應(yīng)式編程可以讓你把程序邏輯想的很清楚檩禾。為什么這么說(shuō)呢挂签?讓我們先來(lái)看一個(gè)小例子,比如我們有這樣一個(gè)需求盼产,在生日的控件之前添加一個(gè)年齡的選擇饵婆,用以輔助生日的輸入。雖然很變態(tài)戏售,其實(shí)直接輸入趕腳比這種方式快啊侨核,但真的有客戶提出過(guò)這種需求,不管怎樣我們來(lái)看一下好了灌灾。

有年齡和單位選擇的日期輸入
有年齡和單位選擇的日期輸入

首先分析一下需求:

  • 年齡可以按歲芹关、月、天為單位紧卒。
  • 其中如果年齡小于等于3個(gè)月侥衬,按天為單位,如果小于等于2歲按月為單位跑芳,其余情況按歲為單位轴总。其實(shí)就是考慮幼兒的情況啦。
  • 填年齡時(shí)博个,出生日期隨之變化怀樟,因?yàn)闊o(wú)法精確,所以只需精確到選擇的單位即可盆佣。

如果按傳統(tǒng)方式編程的話往堡,我們可能需要在年齡和年齡單位的兩個(gè)處理輸入改變的 event handler 去對(duì)數(shù)據(jù)進(jìn)行處理,具體我們就不展開了共耍。我們來(lái)看一下用響應(yīng)式編程如何處理這個(gè)邏輯虑灰。

理解 Rx 的關(guān)鍵是要把任何變化想象成數(shù)據(jù)流,數(shù)據(jù)流分為幾種:

  1. 永遠(yuǎn)不會(huì)結(jié)束的
  2. 有限次的痹兜,比如執(zhí)行若干次結(jié)束的(包括只發(fā)生一次的)
  3. 當(dāng)然還有一些特殊的穆咐,比如永遠(yuǎn)不會(huì)發(fā)生的(這個(gè)是為了解決某些特定場(chǎng)景問(wèn)題存在的)

這么說(shuō)好像比較抽象,那么還是回到例子來(lái)看這個(gè)問(wèn)題字旭。就這個(gè)需求來(lái)看的話对湃,年齡和年齡單位這兩個(gè)數(shù)據(jù)要一起來(lái)考慮,

數(shù)據(jù)流的合并
數(shù)據(jù)流的合并

上圖中(由于太懶遗淳,后面的合并虛線就沒(méi)有畫了)拍柒,上面兩個(gè)流為原始數(shù)據(jù)流,一個(gè)是年齡的數(shù)據(jù)流屈暗,每次更改年齡數(shù)時(shí)拆讯,這個(gè)數(shù)據(jù)流就產(chǎn)生一個(gè)數(shù)據(jù):比如一開始初始值為 33剧包,我們刪掉個(gè)位數(shù)的 3,這時(shí)由于其變化往果,產(chǎn)生第二個(gè)值 3 (原十位的3)疆液,然后我們添加了5,新值變成35陕贮,因此流中的第三個(gè)數(shù)據(jù)是35堕油,以此類推。另一個(gè)數(shù)據(jù)流反映了年齡單位的變化肮之,按照“歲-月-歲-天”的次序產(chǎn)生新的數(shù)據(jù)掉缺。一個(gè)人的最終的年齡是通過(guò)年齡值和年齡單位聯(lián)合確定的,這也就是說(shuō)我們需要對(duì)這兩個(gè)流做合并計(jì)算戈擒。

那么選擇什么樣的合并方式呢眶明?其實(shí)我們需要的是任何一個(gè)流的值變化的時(shí)候,新的合并流都應(yīng)該有一個(gè)對(duì)應(yīng)數(shù)據(jù)筐高,這個(gè)數(shù)據(jù)包括剛剛變化的那個(gè)值和另一個(gè)流中最新的值搜囱。比如:如果年齡數(shù)據(jù)從 33 刪掉個(gè)位變成 3,此時(shí)我們沒(méi)有改變年齡單位柑土,合并流中的新數(shù)據(jù)應(yīng)該是 3歲 蜀肘。接下來(lái)我們改變單位為 ,那這時(shí)候年齡數(shù)據(jù)的最新值仍然是 3 稽屏,所以新流的數(shù)據(jù)應(yīng)為 3月等等以此類推扮宠。

這樣的一種合并方式在 Rx 中專門有一個(gè)操作符來(lái)處理,那就是 combineLatest狐榔。如果我們使用 age$ 代表年齡數(shù)據(jù)流(那個(gè) $ 代表 Stream -- 流的意思坛增,約定俗成的寫法,不強(qiáng)制要求)薄腻,用 ageUnit$ 代表年齡單位數(shù)據(jù)流的話收捣,我們可以寫出如下的合并邏輯,為了簡(jiǎn)化問(wèn)題被廓,我們這里合并后都使用 作為單位:

// 這里前面兩個(gè)參數(shù)都是參與合并的數(shù)據(jù)流坏晦,第三個(gè)是個(gè)處理函數(shù)
// 這個(gè)處理函數(shù)接受兩個(gè)流中的最新數(shù)據(jù)萝玷,然后經(jīng)過(guò)運(yùn)算輸出新值
this.computed$ = Observable.combineLatest(age$, ageUnit$, (a, u)=>{
      // 非法數(shù)字就都按初始值處理嫁乘,這里就簡(jiǎn)單粗暴了
      if(a === undefined || a <= 0 ) return initialAge;
      // 全部轉(zhuǎn)化為天數(shù)
      switch (parseInt(u)) {
        case AgeUnit.Day.valueOf():
          return a;
        case AgeUnit.Month.valueOf():
          return a * 30;
        case AgeUnit.Year.valueOf():
        default:
          // 別問(wèn)我閏年大小月啥的,只是個(gè)例子而已
          return a * 365; 
      }
    })

合并之后呢球碉,由于我們最終需要向生日那個(gè)輸入框中寫入一個(gè)日期蜓斧,而我們合并之后的流給出的是按天數(shù)計(jì)算的年齡,所以這里顯然需要一個(gè)轉(zhuǎn)換睁冬。

在 Rx 中這種數(shù)據(jù)的轉(zhuǎn)換再容易不過(guò)了挎春,最常用的一個(gè)就是 map 轉(zhuǎn)換操作符看疙,接著上面的代碼繼續(xù)來(lái)一個(gè) map 函數(shù),這里使用了 momentjs 的按當(dāng)前日期減去剛剛的以天數(shù)為單位的年齡值直奋,就得到一個(gè)大概估算的出生日期能庆。

.map(a => {
      const date = moment().subtract(a, 'days').format('YYYY-MM-DD');
      return date;
    });

但是到這里,你會(huì)發(fā)現(xiàn)我們還沒(méi)有定義兩個(gè)原始數(shù)據(jù)流呢脚线,別急搁胆,留到后面是為了引出 Angular 對(duì)于 Rx 的良好支持。

響應(yīng)式表單中的 Rx

Angular 的表單處理非常強(qiáng)大邮绿,有模版驅(qū)動(dòng)的表單和響應(yīng)式表單兩類渠旁,兩種表單各有千秋,在不同場(chǎng)合可以分別使用船逮,甚至混合使用顾腊,但這里就不展開了。我們這里使用了響應(yīng)式表單挖胃,也非常簡(jiǎn)單杂靶,就是一個(gè) form 里面 3 個(gè)控件,這里我采用了官方的 Material 控件酱鸭,如果你覺(jué)得不爽伪煤,可以直接用基礎(chǔ)的 HTML 控件搭配樣式即可。

<form 
[formGroup]="form" 
(ngSubmit)="onSubmit()">
  <md-input-container align="end">
      <input mdInput 
        formControlName="age" 
        type="number" 
        placeholder="年齡" 
        max="200" 
        min="1" />
  </md-input-container>
  <md-button-toggle-group formControlName="ageUnit">
    <md-button-toggle value="0" >歲</md-button-toggle>
    <md-button-toggle value="1" >月</md-button-toggle>
    <md-button-toggle value="2" >天</md-button-toggle>
  </md-button-toggle-group>
  <md-input-container>
      <input mdInput 
        formControlName="dateOfBirth" 
        type="date" 
        placeholder="出生日期" 
        max="2100-12-31" 
        min="1900-01-01"
        [value]="computed$ | async"
        />
      <md-hint align="start">YYYY/MM/DD格式輸入</md-hint>
  </md-input-container>
</form>

Angular 中處理響應(yīng)式表單只有 3 個(gè)步驟:

  1. 在組件的 HTML 模版中給要處理的控件加上 formControlName="blablabla"
  2. form 標(biāo)簽中添加 [formGroup]="xxx" 指令凛辣,這個(gè) xxx 就是你在組件中聲明的 FormGroup 類型的成員變量:比如下面代碼中的 form: FormGroup;
  3. 在組件的構(gòu)造函數(shù)中取得 FormBuilder 后(比如下面代碼中的 constructor(private fb: FormBuilder) { })抱既,用 FormBuilder 構(gòu)造表單控件數(shù)組并賦值給剛才的類型為 FormGroup 的成員變量。
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { AgeUnit } from '../../domain/entities.interface';
import * as moment from 'moment/moment';

@Component({
  selector: 'app-reactive',
  templateUrl: './reactive.component.html',
  styleUrls: ['./reactive.component.scss']
})
export class ReactiveComponent implements OnInit {
  form: FormGroup;
  computed$: Observable<string>;
  ageSub: Subscription;
  dateOfBirth$: Observable<string>;
  dateOfBirthSub: Subscription;
  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      age: ['', Validators.required],
      ageUnit: ['', Validators.required],
      dateOfBirth: ['', Validators.compose([Validators.required, this.validateDate])]
    });

    const initialAge = 33;
    const initialAgeUnit = AgeUnit.Year;
    this.form.controls['age'].setValue(initialAge);
    this.form.controls['ageUnit'].setValue(initialAgeUnit);
  }

  validateDate(c: FormControl): {[key: string]: any}{
    const result = moment(c.value).isValid 
        && moment(c.value).isBefore()
        && moment(c.value).year()> 1900;
    return {
      "valid": result
    }
  }

  onSubmit() {
    if(!this.form.valid) return;
  }
}

現(xiàn)在這個(gè)表單就建立好了扁誓,但你可能會(huì)問(wèn)防泵,這也沒(méi)看出來(lái)響應(yīng)式啊,別急蝗敢,接下來(lái)我們就要看看它的響應(yīng)式支持了捷泞。我們?cè)倩氐揭婚_始的小題目,我們的兩個(gè)原始數(shù)據(jù)流:age$ageUnit$ 怎么構(gòu)建寿谴?這兩個(gè)數(shù)據(jù)流其實(shí)是來(lái)自于兩個(gè)控件的值的變化锁右,而響應(yīng)式表單獲取值的變化是非常簡(jiǎn)單的就一行:

this.form.controls['age'].valueChanges

上面這行代碼的意思是從表單的控件數(shù)組中取得 formControlNameage 的這個(gè)控件然后監(jiān)聽(tīng)其值的變化。這個(gè) valueChanges 返回的其實(shí)就是一個(gè) Observable 讶泰,見(jiàn)下面的 TypeScript 定義:

/**
 * Emits an event every time the value of the control changes, in
 * the UI or programmatically.
 */
readonly valueChanges: Observable<any>;

既然我們得到了這個(gè)原始數(shù)據(jù)流咏瑟,剩下的工作就比較簡(jiǎn)單了。但我們可能需要對(duì)這個(gè)原始數(shù)據(jù)流再做點(diǎn)處理痪署。首先码泞,我們并不希望每次改這個(gè)值都去監(jiān)聽(tīng),因?yàn)檩斎胧且粋€(gè)連續(xù)事件狼犯,每一次按鍵都監(jiān)聽(tīng)是不太劃算的余寥。這就需要一個(gè)濾波器的處理 .debounceTime(500)领铐,我們不去處理 500 毫秒內(nèi)的變化,而是等待其輸入停頓時(shí)再發(fā)送數(shù)據(jù)宋舷。第二绪撵,如果用戶采用了拷貝粘貼的方式,我們希望同樣的數(shù)據(jù)不重復(fù)發(fā)送祝蝠,所以濾掉相同的數(shù)據(jù)莲兢。最后,我們采用 startWith 給這個(gè)流一個(gè)初始值续膳,這是由于如果一開始我們什么都不做改艇,兩個(gè)流就都沒(méi)有數(shù)據(jù);或者只改變其中一個(gè)坟岔,另一個(gè)由于一直沒(méi)有變就不會(huì)產(chǎn)生數(shù)據(jù)谒兄,這樣的話,合并流也不會(huì)有數(shù)據(jù)社付。

// 省略其它引入
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
// 省略其它部分
const age$ = this.form.controls['age'].valueChanges
      .debounceTime(500)
      .distinctUntilChanged()
      .startWith(initialAge);
const ageUnit$ = this.form.controls['ageUnit'].valueChanges
      .distinctUntilChanged()
      .startWith(initialAgeUnit);

Async 管道

到目前為止承疲,我們還沒(méi)有進(jìn)行對(duì) Observable 的訂閱,如果不訂閱的話鸥咖,寫的再漂亮的語(yǔ)句也不會(huì)執(zhí)行的燕鸽。按常規(guī)套路來(lái)講伊磺,我們得聲明 Subscription 對(duì)象昙篙,因?yàn)?Observable 是一直監(jiān)聽(tīng)的柳弄,即使頁(yè)面銷毀矛渴,它也還在,這會(huì)造成內(nèi)存泄漏绢彤。所以描馅,我們需要再頁(yè)面銷毀(ngOnDestroy 中)的適合取消訂閱营勤。 需要訂閱的 Observable 少的時(shí)候還好富弦,一旦多起來(lái)沟娱,處理時(shí)也挺麻煩,像下面的代碼那樣腕柜。

// 省略其它引入
import { Subscription } from 'rxjs/Subscription';
// 省略其它部分
ageSub: Subscription;
// 省略其它部分
this.ageSub = this.computed$.subscribe(date => this.form.controls['dateOfBirth'].setValue(date));
// 省略其它部分
onNgDestroy(){
  if(this.ageSub !== undefined || !this.ageSub.closed)
    this.ageSub.unsubscribe();
}

所幸的是济似,Angular 提供了對(duì)于響應(yīng)式編程非常友好的設(shè)計(jì),我們完全可以不在代碼中做訂閱或取消訂閱的動(dòng)作盏缤。那么問(wèn)題來(lái)了砰蠢,不訂閱的話,值怎么獲得呢蛾找?答案是 Async 管道娩脾。Async 會(huì)在組件初始化時(shí)自動(dòng)的訂閱以及在組件銷毀時(shí)自動(dòng)取消訂閱,太爽了打毛。因此柿赊,我們可以刪掉上面的代碼了,然后在組件模版中給生日的那個(gè) input 添加一個(gè)指令 [value]="computed$ | async"幻枉,這就是說(shuō)該 input 的 value 就是 computed$ 訂閱后的值碰声,那么 | async 是說(shuō) computed$ 是一個(gè) Observable,請(qǐng)對(duì)他采用異步處理熬甫,即初始化時(shí)自動(dòng)的訂閱以及在組件銷毀時(shí)自動(dòng)取消訂閱胰挑。

<input mdInput 
        formControlName="dateOfBirth" 
        // 省略其它屬性
        [value]="computed$ | async"
        />

對(duì)于響應(yīng)式編程方式的思考

上面的例子,我不知道大家發(fā)現(xiàn)沒(méi)有椿肩,當(dāng)然 Rx 提供了好多方便的操作符瞻颂。但更重要的是,寫 Rx 的時(shí)候郑象,我們需要對(duì)流程理解的足夠清晰贡这,或者說(shuō) Rx 逼著我們對(duì)流程反復(fù)梳理。其實(shí)有的時(shí)候厂榛,寫 Rx 不一定很快盖矫,但一旦業(yè)務(wù)梳理清楚了,接下來(lái)就是幾行代碼的事情击奶。如果你有時(shí)候覺(jué)得用現(xiàn)有的 Rx 操作符寫不出辈双,那多半是你的對(duì)需求中涉及的數(shù)據(jù)流的關(guān)系沒(méi)有弄清楚。

Angular 4 中的 NgIf 的改進(jìn)

Angular 4 中的 ngIf 現(xiàn)在可以攜帶 else 了柜砾,如果你曾經(jīng)使用過(guò) Angular 就知道湃望,原來(lái)我們是得寫兩個(gè) ngIf 來(lái)完成類似的功能的。這個(gè) else 可以攜帶一個(gè)模版的引用痰驱。比如下面例子中:如果用戶登錄成功顯示用戶名喜爷,否則顯示登錄鏈接。

<span *ngIf="auth$ else login">
  <a routerLink="/profile">{{(auth$|async).user.name}}</a>
  <a routerLink="/blablabla">{{(auth$|async).visits}}</a>
</span>
<ng-template #login>
  <a routerLink="/login">登錄</a>
</ng-template>

另一個(gè)改進(jìn)是 ngIf 中現(xiàn)在可以將評(píng)估表達(dá)式的結(jié)果賦值給一個(gè)變量萄唇,好處是什么呢檩帐?可以讓你少寫很多 (auth$|async)

<span *ngIf="auth$ | async as auth else login">
  <a routerLink="/profile">{{auth.user.name}}</a>
  <a routerLink="/blablabla">{{auth.visits}}</a>
</span>
<ng-template #login>
  <a routerLink="/login">登錄</a>
</ng-template>

有問(wèn)題的童鞋可以加入我的小密圈討論: http://t.xiaomiquan.com/jayRnaQ (該鏈接7天內(nèi)(5月14日前)有效)

好久沒(méi)寫 Angular 了,希望后面會(huì)有時(shí)間多寫一些另萤。另外湃密,我的 《Angular 從零到一》出版了,本文出自第 8 章部分內(nèi)容四敞,下面是書籍的內(nèi)容簡(jiǎn)介:

本書系統(tǒng)介紹Angular的基礎(chǔ)知識(shí)與開發(fā)技巧泛源,可幫助前端開發(fā)者快速入門。共有9章忿危,第1章介紹Angular的基本概念达箍,第2~7章從零開始搭建一個(gè)待辦事項(xiàng)應(yīng)用,然后逐步增加功能铺厨,如增加登錄驗(yàn)證缎玫、將應(yīng)用模塊化硬纤、多用戶版本的實(shí)現(xiàn)、使用第三方樣式庫(kù)赃磨、動(dòng)態(tài)效果制作等筝家。第8章介紹響應(yīng)式編程的概念和Rx在Angular中的應(yīng)用。第9章介紹在React中非常流行的Redux狀態(tài)管理機(jī)制邻辉,這種機(jī)制的引入可以讓代碼和邏輯隔離得更好溪王,在團(tuán)隊(duì)工作中強(qiáng)烈建議采用這種方案。本書不僅講解Angular的基本概念和最佳實(shí)踐值骇,而且分享了作者解決問(wèn)題的過(guò)程和邏輯莹菱,講解細(xì)膩,風(fēng)趣幽默吱瘩,適合有面向?qū)ο缶幊袒A(chǔ)的讀者閱讀道伟。

歡迎大家圍觀、訂購(gòu)搅裙、提出寶貴意見(jiàn)皱卓。

京東鏈接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市部逮,隨后出現(xiàn)的幾起案子娜汁,更是在濱河造成了極大的恐慌,老刑警劉巖兄朋,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掐禁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颅和,警方通過(guò)查閱死者的電腦和手機(jī)傅事,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)峡扩,“玉大人蹭越,你說(shuō)我怎么就攤上這事〗探欤” “怎么了响鹃?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)案训。 經(jīng)常有香客問(wèn)我买置,道長(zhǎng),這世上最難降的妖魔是什么强霎? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任忿项,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轩触。我一直安慰自己寞酿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布怕膛。 她就那樣靜靜地躺著熟嫩,像睡著了一般秦踪。 火紅的嫁衣襯著肌膚如雪褐捻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天椅邓,我揣著相機(jī)與錄音柠逞,去河邊找鬼。 笑死景馁,一個(gè)胖子當(dāng)著我的面吹牛板壮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播合住,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绰精,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了透葛?” 一聲冷哼從身側(cè)響起笨使,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎僚害,沒(méi)想到半個(gè)月后硫椰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萨蚕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年靶草,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岳遥。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奕翔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浩蓉,到底是詐尸還是另有隱情派继,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布妻往,位于F島的核電站互艾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏讯泣。R本人自食惡果不足惜纫普,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昨稼,春花似錦节视、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至匾荆,卻和暖如春拌蜘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牙丽。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工简卧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烤芦。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓举娩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親构罗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铜涉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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