項(xiàng)目接入使用React Native

有一段時(shí)間沒有寫東西了,因?yàn)樽罱?xiàng)目開始嘗試使用React Native(以下簡(jiǎn)稱RN)來開發(fā),所以這段時(shí)間一直在研究,目前為止開發(fā)的內(nèi)容不多,所以使用過的東西也不算多,這里也只是做個(gè)簡(jiǎn)單的記錄
這里我打算從以下幾個(gè)方面來講:
1.背景介紹
2.環(huán)境的配置
3.RN所需要知道的知識(shí)
4.RN與原生的交互
5.本地調(diào)試與本地打包調(diào)試
6.遠(yuǎn)程熱更新
7.踩坑記錄
8.相關(guān)資料


0x00 背景介紹

RN是Facebook在React.js 2015大會(huì)上公布開源的,它是基于開源框架React.js來實(shí)現(xiàn)的,它支持了iOS和Android兩大平臺(tái),解決開發(fā)者們編寫重復(fù)代碼的痛點(diǎn),實(shí)現(xiàn)了所謂的跨平臺(tái)開發(fā),Write Once , Run Anywhere,這是目前很多開發(fā)者所追求的,特別是一些獨(dú)立開發(fā)者或者項(xiàng)目快速迭代的團(tuán)隊(duì),可以嘗試使用RN來開發(fā),另外包括方便的npm管理,快速的調(diào)試等等
那么既然優(yōu)點(diǎn)這么明顯,為什么大部分的團(tuán)隊(duì)還是采用傳統(tǒng)的iOS、Android開發(fā)呢,踩過坑的同學(xué)都知道,首先在支持上還做得不夠完善,在使用組件時(shí),RN原有提供的組件往往不能很好的支持,與原生組件多少存在著差異,而且在使用第三方組件時(shí),又會(huì)因?yàn)殚L(zhǎng)期不更新的原因,存在很多坑,對(duì)于新手來說,根本不知道坑在哪,完全無(wú)從下手。另外RN的性能也不能和原生的相提并論,特別是列表組件在渲染大量數(shù)據(jù)時(shí),流暢性方面還是原生更加優(yōu)越,而且并非所以代碼iOS和Android都能公用,如果某個(gè)組件只支持某一個(gè)平臺(tái),那你必須分開編寫代碼,實(shí)際上還是存在重復(fù)代碼,除此之外學(xué)習(xí)的成本以及團(tuán)隊(duì)RN推廣等等原因都需要考量,但是我相信,跨平臺(tái)開發(fā)始終是一個(gè)趨勢(shì),RN整個(gè)社區(qū)也在不斷的發(fā)展,相信未來我們會(huì)實(shí)現(xiàn)真正意義上的跨平臺(tái)開發(fā)~

0x01 環(huán)境配置

相對(duì)于Android的環(huán)境配置過程來說,iOS可以說是簡(jiǎn)單輕松…出現(xiàn)的問題要少很多
首先我們需要安裝Homebrew

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

然后安裝nodewatchman(用于監(jiān)測(cè)文件系統(tǒng)的變更)

brew install node
brew install watchman

RN的命令行工具react-native-cli

npm install -g react-native-cli

如果遇到權(quán)限問題,只要前面加個(gè)sudo即可

sudo npm install -g react-native-cli

yeah~that's all~我在配置的過程中,基本沒有報(bào)錯(cuò),如果有出現(xiàn)配置問題的話,請(qǐng)自行Google一下,看看大家的解決方法

0x02 RN所需要知道的知識(shí)

RN的運(yùn)行機(jī)制

在開始寫代碼之前,我們需要了解RN的運(yùn)行機(jī)制是怎么樣的,這樣寫起來思路會(huì)更加清晰
首先,程序需要有個(gè)入口,我們可以創(chuàng)建很多的組件,但是有且只有一個(gè)組件用來做為程序的入口,RN的入口則類似于iOS的main.m,在iOS里我們會(huì)在main函數(shù)里設(shè)置應(yīng)用程序類的代理類

return UIApplicationMain(argc, argv, nil, NSStringFromClass([KDAppDelegate class]));

同樣,RN里我們需要注冊(cè)入口的名稱,并且這個(gè)名稱要和原生的初始化RN界面時(shí)的入口名稱保持一致

// 引用navigation使用的組件

import React, { Component } from 'react';
import {
 AppRegistry,
 ...
} from 'react-native';
?
// 創(chuàng)建navigation類
class navigation extends Component {
 // set compnent
}
?
// 注冊(cè)navigation為程序的入口
AppRegistry.registerComponent('navigation', () => navigation);

在iOS原生這邊需要用到RN的地方,我們需要初始化它

NSURL *jsCodeLocation =

[[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];
//       [NSURL URLWithString:@"http://172.17.9.188:8081/index.ios.bundle?platform=ios"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                   moduleName:@"navigation"
                                           initialProperties:nil
                                                 launchOptions:nil];
self.view = rootView;

Tip:

jsCodeLocation 是RN資源加載的路徑,我們有兩種方式去加載,一種是加載本地的js文件及其他資源文件,一種是我們將其打包成bundle文件,前者的優(yōu)勢(shì)在于方便調(diào)試,后者是用來打包發(fā)布上線用

moduleName
是對(duì)應(yīng)于RN的入口名字,且這個(gè)是唯一的,那我們?nèi)绻卸鄠€(gè)入口需要初始化不同的RN界面,那該怎么辦呢?這就用到了initialProperties
,它是字典類型,我們可以將入口作為路由,在initialProperties
里傳入我們需要初始化的界面名稱,入口獲取到名稱之后,渲染對(duì)應(yīng)的界面即可

RN組件的生命周期

在RN里面,所謂的界面應(yīng)該稱作類或者組件更為合適并且組件也有它的生命周期,和iOS里的viewWillAppear、viewDidDisappear等等很像,下面生命周期內(nèi)容取自于http://www.race604.com/react-native-component-lifecycle/


我們可以把組件生命周期大致分為三個(gè)階段:
第一階段:是組件第一次繪制階段,如圖中的上面虛線框內(nèi),在這里完成了組件的加載和初始化;

第二階段:是組件在運(yùn)行和交互階段,如圖中左下角虛線框,這個(gè)階段組件可以處理用戶交互,或者接收事件更新界面;

第三階段:是組件卸載消亡的階段,如圖中右下角的虛線框中,這里做一些組件的清理工作。

下面來詳細(xì)介紹生命周期中的各回調(diào)函數(shù)。

getDefaultProps

在組件創(chuàng)建之前,會(huì)先調(diào)用 getDefaultProps(),這是全局調(diào)用一次,嚴(yán)格地來說,這不是組件的生命周期的一部分。在組件被創(chuàng)建并加載候,首先調(diào)用 getInitialState(),來初始化組件的狀態(tài)。

componentWillMount

然后,準(zhǔn)備加載組件,會(huì)調(diào)用componentWillMount(),其原型如下:

void componentWillMount() 

這個(gè)函數(shù)調(diào)用時(shí)機(jī)是在組件創(chuàng)建,并初始化了狀態(tài)之后,在第一次繪制 render() 之前。可以在這里做一些業(yè)務(wù)初始化操作,也可以設(shè)置組件狀態(tài)。這個(gè)函數(shù)在整個(gè)生命周期中只被調(diào)用一次。

componentDidMount

在組件第一次繪制之后,會(huì)調(diào)用 componentDidMount(),通知組件已經(jīng)加載完成。函數(shù)原型如下:

void componentDidMount() 

這個(gè)函數(shù)調(diào)用的時(shí)候,其虛擬 DOM 已經(jīng)構(gòu)建完成,你可以在這個(gè)函數(shù)開始獲取其中的元素或者子組件了。需要注意的是,RN 框架是先調(diào)用子組件的 componentDidMount(),然后調(diào)用父組件的函數(shù)。從這個(gè)函數(shù)開始,就可以和 JS 其他框架交互了,例如設(shè)置計(jì)時(shí) setTimeout或者 setInterval,或者發(fā)起網(wǎng)絡(luò)請(qǐng)求。這個(gè)函數(shù)也是只被調(diào)用一次。這個(gè)函數(shù)之后,就進(jìn)入了穩(wěn)定運(yùn)行狀態(tài),等待事件觸發(fā)。

componentWillReceiveProps

如果組件收到新的屬性(props),就會(huì)調(diào)用componentWillReceiveProps()
,其原型如下:

void componentWillReceiveProps( 

 object nextProps
)

輸入?yún)?shù) nextProps是即將被設(shè)置的屬性,舊的屬性還是可以通過 this.props 來獲取。在這個(gè)回調(diào)函數(shù)里面,你可以根據(jù)屬性的變化,通過調(diào)用 this.setState()來更新你的組件狀態(tài),這里調(diào)用更新狀態(tài)是安全的,并不會(huì)觸發(fā)額外的 render()調(diào)用。如下:

componentWillReceiveProps: function(nextProps) { 

 this.setState({
   likesIncreasing: nextProps.likeCount > this.props.likeCount
 });
}

shouldComponentUpdate

當(dāng)組件接收到新的屬性和狀態(tài)改變的話,都會(huì)觸發(fā)調(diào)用shouldComponentUpdate(...)
,函數(shù)原型如下:

boolean shouldComponentUpdate( 

 object nextProps, object nextState
)

輸入?yún)?shù)nextProps 和上面的 componentWillReceiveProps函數(shù)一樣,nextState表示組件即將更新的狀態(tài)值。這個(gè)函數(shù)的返回值決定是否需要更新組件,如果 true表示需要更新,繼續(xù)走后面的更新流程。否者,則不更新,直接進(jìn)入等待狀態(tài)。
默認(rèn)情況下,這個(gè)函數(shù)永遠(yuǎn)返回 true用來保證數(shù)據(jù)變化的時(shí)候 UI 能夠同步更新。在大型項(xiàng)目中,你可以自己重載這個(gè)函數(shù),通過檢查變化前后屬性和狀態(tài),來決定 UI 是否需要更新,能有效提高應(yīng)用性能。

componentWillUpdate

如果組件狀態(tài)或者屬性改變,并且上面的 shouldComponentUpdate(...)返回為 true,就會(huì)開始準(zhǔn)更新組件,并調(diào)用 componentWillUpdate(),其函數(shù)原型如下:

void componentWillUpdate( 

 object nextProps, object nextState
)

輸入?yún)?shù)與 shouldComponentUpdate 一樣,在這個(gè)回調(diào)中,可以做一些在更新界面之前要做的事情。需要特別注意的是,在這個(gè)函數(shù)里面,你就不能使用 this.setState來修改狀態(tài)。這個(gè)函數(shù)調(diào)用之后,就會(huì)把 nextPropsnextState 分別設(shè)置到 this.propsthis.state中。緊接著這個(gè)函數(shù),就會(huì)調(diào)用 render()來更新界面了。

componentDidUpdate

調(diào)用了 render() 更新完成界面之后,會(huì)調(diào)用 componentDidUpdate()來得到通知,其函數(shù)原型如下:

void componentDidUpdate( 

 object prevProps, object prevState
)

因?yàn)榈竭@里已經(jīng)完成了屬性和狀態(tài)的更新了,此函數(shù)的輸入?yún)?shù)變成了 prevPropsprevState

componentWillUnmount

當(dāng)組件要被從界面上移除的時(shí)候,就會(huì)調(diào)用 componentWillUnmount(),其函數(shù)原型如下:

void componentWillUnmount() 

在這個(gè)函數(shù)中,可以做一些組件相關(guān)的清理工作,例如取消計(jì)時(shí)器、網(wǎng)絡(luò)請(qǐng)求等。
下表是生命周期函數(shù)的調(diào)用次數(shù),以及能否使用setSate():

RN的設(shè)計(jì)模式

目前設(shè)計(jì)模式也非常多,如Flux,Reflux,Redux,Relay,Marty,不過以上都不是很了解,可以參考ReactNative的組件架構(gòu)設(shè)計(jì)學(xué)習(xí)了解一下,由于做客戶端的同學(xué)接觸的最多的是MVC,MVVM、MVCS等等,所以我覺得選用類似MVCS的模式可能更加適合新手的學(xué)習(xí),比如寫組件時(shí),通常我們會(huì)創(chuàng)建一個(gè)組件,里面會(huì)包含數(shù)據(jù)的處理,頁(yè)面的渲染,樣式的設(shè)置,網(wǎng)絡(luò)請(qǐng)求,當(dāng)這些內(nèi)容過多時(shí),組件就會(huì)顯得特別臃腫,所以我們需要將其拆分開為數(shù)據(jù)模型(Model),頁(yè)面渲染,樣式設(shè)置,網(wǎng)路請(qǐng)求(Service),這里的頁(yè)面渲染和樣式設(shè)置,不能算是稱作為iOS里的Controller和View,應(yīng)該跟前端一樣,在html文件里面寫布局,css文件里面寫樣式,感覺像是MVCS和前端的融合

0x03 RN與原生的交互

在寫RN時(shí)不免會(huì)遇到與原生交互,下面我分JS調(diào)用原生、原生調(diào)用JS來講

JS調(diào)用原生

在調(diào)用原生時(shí),我們需要實(shí)現(xiàn)RCTBridgeModuleRCT_EXPORT_MODULE();
RCT_EXPORT_MODULE();則是一個(gè)宏定義,返回moduleName,并且調(diào)用+ load方法注冊(cè)

#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

例如我們?cè)黾右粋€(gè)bridge方法,獲取版本號(hào),getVersion為方法名,callback是原生回調(diào)給JS的內(nèi)容

RCT_EXPORT_METHOD(getVersion : (RCTResponseSenderBlock)callback) {
   NSString *version =
   [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
   callback(@[[NSNull null], @[version]]);
}

然后返回方法的隊(duì)列為主隊(duì)列

- (dispatch_queue_t)methodQueue {
   return dispatch_get_main_queue();
}

在JS文件里,我們可以定義一個(gè)全局變量

var ZanIntentModule = NativeModules.ZanIntentModule;

然后在使用的時(shí)候調(diào)用我們?cè)谠鷷r(shí)定義方法

ZanIntentModule.getVersion(
(callback) => {
   // do some thing
})

原生調(diào)用JS

老版本的調(diào)用方式為,但是接口被標(biāo)記為deprecated:__deprecated_msg("Subclass RCTEventEmitter instead");

[self.bridge.eventDispatcher sendAppEventWithName:kGiftReloadData body:nil];

新版本的調(diào)用方式為

ZanEventEmitter *emitter = [[ZanEventEmitter alloc] init];
emitter.bridge = self.bridge;
[emitter sendEventWithName:kGiftReloadData body:nil];

但是新版本坑的是,直接這樣調(diào)用時(shí)bridge居然是nil,網(wǎng)上說用單例,但是也不行...所以我還是用老版本的調(diào)用方法,有哪個(gè)大神知道怎么用新版本接口調(diào)用的正確姿勢(shì),請(qǐng)留言交流哈
然后在實(shí)現(xiàn)RCTBridgeDelegate

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
   return [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];
//   return [NSURL URLWithString:@"http://172.17.9.94:8081/index.ios.bundle?platform=ios"];
}

在對(duì)應(yīng)的組件里,需要在componentWillMount增加監(jiān)聽

componentWillMount() {
  this.eventEmitter = NativeAppEventEmitter.addListener(
   'GiftReloadData',
   () => this._reloadData()
 );
}

對(duì)應(yīng)的也需要移除掉監(jiān)聽

componentWillUnmount() {
  subscription.remove();
}

然后原生發(fā)送action之后,會(huì)觸發(fā)我們?cè)O(shè)定好的reloadData()方法

0x04 本地調(diào)試與打包調(diào)試

在編寫的過程中,也需要進(jìn)行調(diào)試,調(diào)試有兩種方法:一種是本地調(diào)試,一種是打包調(diào)試

本地調(diào)試

我們?cè)诩虞dbundle時(shí),需要替換成你的ip地址,端口號(hào)不要變

[NSURL URLWithString:@"http://172.17.9.94:8081/index.ios.bundle?platform=ios"]

如果你是在真機(jī)上調(diào)試,你需要開啟HTTP代理,填寫你的ip地址和端口號(hào)
在終端上,先進(jìn)入到你的項(xiàng)目目錄(與node_modules目錄同級(jí)),然后開啟服務(wù)

yzydeMacBook-Pro:shangjiaban-ios yzy$ npm start

你修改了某處之后,在模擬器上點(diǎn)擊Shake Gesture或者快捷鍵,在真機(jī)上只要搖一搖就可以


在模擬器彈出框里選擇Roload,這樣就會(huì)重新加載你本地的JS文件

如果你想查看JS里面的log日志,你可以選擇Start Remote JS Debugging,在chrome瀏覽器里就能看到輸出的日志了

打包調(diào)試

另外一種就是打包調(diào)試,但是比較麻煩,首先我們要講bundle加載方式改為

[[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];

然后在終端里面,輸入

yzydeMacBook-Pro:shangjiaban-ios yzy$ react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ./xxx/bundle/index.ios.jsbundle --assets-dest ./xxx/bundle

--bundle-output ./xxx/bundle/index.ios.jsbundle指的是輸出的bundle文件路徑

[20:54:43] <START> Building Dependency Graph

[20:54:43] <START> Crawling File System
[20:54:43] <START> find dependencies
[20:54:48] <END>   Crawling File System (4712ms)
[20:54:48] <START> Building in-memory fs for JavaScript
[20:54:48] <END>   Building in-memory fs for JavaScript (230ms)
[20:54:48] <START> Building in-memory fs for Assets
[20:54:48] <END>   Building in-memory fs for Assets (154ms)
[20:54:48] <START> Building Haste Map
[20:54:48] <START> Building (deprecated) Asset Map
[20:54:48] <END>   Building (deprecated) Asset Map (66ms)
[20:54:48] <END>   Building Haste Map (154ms)
[20:54:48] <END>   Building Dependency Graph (5261ms)
transformed 372/372 (100%)
[20:54:49] <END>   find dependencies (6402ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: ./Koudaitong/bundle/index.ios.jsbundle
bundle: Copying 5 asset files
bundle: Done writing bundle output
bundle: Done copying assets

當(dāng)看到這樣的信息的時(shí)候,說明已經(jīng)打包成功了,再將生成的bundle文件夾以Create folder references形式加到工程里,然后就可以run了
Tip:

在真機(jī)調(diào)試時(shí),需要在Edit Scheme里在Run模式里,將Build Configuration改為Release模式

0x05 遠(yuǎn)程熱更新

這塊網(wǎng)上的方案大同小異,因?yàn)槟壳拔覀冞€是采取本地打包加載的方式,還未上熱更新,所以在這不好多做說明,等上了熱更新之后,我再來補(bǔ)充~

0x06 踩坑記錄

踩坑最多是應(yīng)該是使用上的
1.RN系統(tǒng)的組件并不是所有都是共用的,比如segment支持iOS,不支持Android,Alert分為iOS和Android等等,所以還是要寫重復(fù)的代碼
2.ListView不支持iOS原生的滑動(dòng)操作,需要使用第三方庫(kù),但是第三方庫(kù)不能控制只編輯一個(gè)Cell
3.由于原先iOS和Android的代碼倉(cāng)庫(kù)是分開的,所以接入RN時(shí),JS文件也是跟著倉(cāng)庫(kù)走的,這樣iOS和Android會(huì)存在重復(fù)代碼,并且目前兩個(gè)人分別接iOS和Android,寫JS時(shí),有時(shí)并不共享,容易代碼寫著寫著就有差異了,偏離了Write Once , Run Anywhere的初衷

0x07 相關(guān)資料

React Native
React Native 中文網(wǎng)
匯集了各類react-native學(xué)習(xí)資源、開源App和組件
寫給 iOS 開發(fā)者的 React Native 學(xué)習(xí)路線
江清清的技術(shù)專欄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

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