ReactNative之原生模塊開發(fā)并發(fā)布--iOS篇

準(zhǔn)備工作:

創(chuàng)建ReactNative工程

我們需要先創(chuàng)建一個(gè)ReactNative工程隔箍,使用如下命令創(chuàng)建箭启。

react native init TestProject

創(chuàng)建好工程之后蜜氨,我們使用xcode打開TestProject/ios/下的iOS工程炎滞。

創(chuàng)建靜態(tài)庫(kù),并將這個(gè)靜態(tài)庫(kù)手動(dòng)鏈接到工程中

首先舵匾,我們?cè)谇懊鎰?chuàng)建的ReactNative工程下的node_modules創(chuàng)建一個(gè)文件夾react-native-BGNativeModuleExample俊抵,然后我們?cè)谛聞?chuàng)建的文件夾下再創(chuàng)建一個(gè)ios文件夾。

$ cd TestProject/node_modules

$ mkdir react-native-BGNativeModuleExample

$ cd react-native-BGNativeModuleExample

$ mkdir ios

然后坐梯,由于ReactNative的組件都是一個(gè)個(gè)靜態(tài)庫(kù)徽诲,我們發(fā)布到npm給別人使用的話,也需要建立靜態(tài)庫(kù)。我們使用Xcode建立靜態(tài)庫(kù)谎替,取名為BGNativeModuleExample轩拨。建立之后,我們將創(chuàng)建的靜態(tài)庫(kù)中的文件全部copy到node_modules/react-native-BGNativeModuleExample/ios目錄下院喜。

iOS文件目錄如下:

|____BGNativeModuleExample

| |____BGNativeModuleExample.h

| |____BGNativeModuleExample.m

|____BGNativeModuleExample.xcodeproj

最后,我們需要手動(dòng)將這個(gè)靜態(tài)庫(kù)鏈接到工程中晕翠。

1喷舀、使用xcode打開創(chuàng)建的靜態(tài)庫(kù),添加一行Header Search Paths淋肾,值為$(SRCROOT)/../../react-native/React硫麻,并設(shè)置為recursive。

2樊卓、將BGNativeModuleExample靜態(tài)庫(kù)工程拖動(dòng)到工程中的Library中拿愧。

3、選中 TARGETS => TestProject => Build Settings => Link Binary With Libraries碌尔,添加libBGNativeModuleExample.a這個(gè)靜態(tài)庫(kù)

到此浇辜,我們準(zhǔn)備工作完成了。我們這里這么準(zhǔn)備是有用意的唾戚,那就是模擬npm鏈接的過(guò)程柳洋,建立好了環(huán)境,避免了發(fā)布到npm上后別人使用找不到靜態(tài)庫(kù)的問(wèn)題叹坦。

一熊镣、編寫原生模塊代碼

1、創(chuàng)建原生模塊

選中我們創(chuàng)建的BGNativeModuleExample靜態(tài)庫(kù)募书,然后在BGNativeModuleExample.h文件中導(dǎo)入RCTBridgeModule.h绪囱,讓BGNativeModuleExample類遵循RCTBridgeModule協(xié)議。

//BGNativeModuleExample.h文件的內(nèi)容如下

#import? "RCTBridgeModule.h"

@interface BGNativeModuleExample : NSObject

@end

在BGNativeModuleExample.m文件中莹捡,我們需要實(shí)現(xiàn)RCTBridgeModule協(xié)議鬼吵。為了實(shí)現(xiàn)RCTBridgeModule協(xié)議,我們的類需要包含RCT_EXPORT_MODULE()宏道盏。這個(gè)宏也可以添加一個(gè)參數(shù)用來(lái)指定在Javascript中訪問(wèn)這個(gè)模塊的名字而柑。如果不指定,默認(rèn)會(huì)使用這個(gè)類的名字荷逞。

在這里媒咳,我們指定了模塊的名字為BGNativeModuleExample。

RCT_EXPORT_MODULE(BGNativeModuleExample);

實(shí)現(xiàn)了RCTBridgeModule協(xié)議之后种远,我們就可以在js中如下獲取到我們創(chuàng)建的原生模塊涩澡。

import { NativeModules } from 'react-native';

var BGNativeModuleExample = NativeModules.BGNativeModuleExample;

需要注意的是,RCT_EXPORT_MODULE宏傳遞的參數(shù)不能是OC中的字符串坠敷。如果傳遞@“BGNativeModuleExample"妙同,那么我們導(dǎo)出給JS的模塊名字其實(shí)是@"BGNativeModuleExample"射富,使用BGNativeModuleExample就找不到了。在這里粥帚,我們其實(shí)可以通過(guò)打印NativeModules來(lái)查找到我們創(chuàng)建的原生模塊胰耗。

2、為原生模塊添加方法

我們需要明確的聲明要給JS導(dǎo)出的方法芒涡,否則ReactNative不會(huì)導(dǎo)出任何方法柴灯。聲明通過(guò)RCT_EXPORT_METHOD()宏來(lái)實(shí)現(xiàn):

RCT_EXPORT_METHOD(testPrint:(NSString *)name info:(NSDictionary *)info) {

RCTLogInfo(@"%@: %@", name, info);

}

在JS中,我們可以這樣調(diào)用這個(gè)方法:

BGNativeModuleExample.testPrint("Jack", {

height: '1.78m',

weight: '7kg'

});

3费尽、參數(shù)類型

RCT_EXPORT_METHOD()支持所有標(biāo)準(zhǔn)的JSON類型赠群,包括:

string (NSString)

number (NSInteger, float, double, CGFloat, NSNumber)

boolean (BOOL, NSNumber)

array (NSArray) 包含本列表中任意類型

map (NSDictionary) 包含string類型的鍵和本列表中任意類型的值

function (RCTResponseSenderBlock)

除此以外,任何RCTConvert類支持的的類型也都可以使用(參見RCTConvert了解更多信息)旱幼。RCTConvert還提供了一系列輔助函數(shù)查描,用來(lái)接收一個(gè)JSON值并轉(zhuǎn)換到原生Objective-C類型或類。

了解更多請(qǐng)點(diǎn)擊原生模塊柏卤。

4冬三、回調(diào)函數(shù)

警告:本章節(jié)內(nèi)容目前還處在實(shí)驗(yàn)階段,因?yàn)槲覀冞€并沒(méi)有太多的實(shí)踐經(jīng)驗(yàn)來(lái)處理回調(diào)函數(shù)闷旧。

回調(diào)函數(shù)长豁,在官方的文檔中是有上面的一個(gè)警告,不過(guò)在使用過(guò)程暫時(shí)未發(fā)現(xiàn)問(wèn)題忙灼。在OC中匠襟,我們添加一個(gè)getNativeClass方法,將當(dāng)前模塊的類名回調(diào)給JS该园。

RCT_EXPORT_METHOD(getNativeClass:(RCTResponseSenderBlock)callback) {

callback(@[NSStringFromClass([self class])]);

}

在JS中酸舍,我們通過(guò)以下方式獲取到原生模塊的類名

BGNativeModuleExample.getNativeClass(name => {

console.log("nativeClass: ", name);

});

原生模塊通常只應(yīng)調(diào)用回調(diào)函數(shù)一次。但是里初,它們可以保存callback并在將來(lái)調(diào)用啃勉。這在封裝那些通過(guò)“委托函數(shù)”來(lái)獲得返回值的iOS API時(shí)最常見。

5双妨、Promises

原生模塊還可以使用promise來(lái)簡(jiǎn)化代碼淮阐,搭配ES2016(ES7)標(biāo)準(zhǔn)的async/await語(yǔ)法則效果更佳。如果橋接原生方法的最后兩個(gè)參數(shù)是RCTPromiseResolveBlock和RCTPromiseRejectBlock刁品,則對(duì)應(yīng)的JS方法就會(huì)返回一個(gè)Promise對(duì)象泣特。

我們通過(guò)Promises來(lái)實(shí)現(xiàn)原生模塊是否會(huì)響應(yīng)方法,響應(yīng)則返回YES挑随,不響應(yīng)則返回一個(gè)錯(cuò)誤信息状您,代碼如下:

RCT_REMAP_METHOD(testRespondMethod,

name:(NSString *)name

resolver:(RCTPromiseResolveBlock)resolve

rejecter:(RCTPromiseRejectBlock)reject) {

if([self respondsToSelector:NSSelectorFromString(name)]) {

resolve(@YES);

}

else {

reject(@"-1001", @"not respond this method", nil);

}

}

在JS中,我們有兩種方式調(diào)用,第一種是通過(guò)then....catch的方式:

BGNativeModuleExample.testRespondMethod("dealloc")

.then(result => {

console.log("result is ", result);

})

.catch(error => {

console.log(error);

});

第二種是通過(guò)try...catch來(lái)調(diào)用膏孟,與第一種相比眯分,第二種會(huì)報(bào)警告”Possible Unhandled Promiss Rejection (id:0)“。

async testRespond() {

try {

var result = BGNativeModuleExample.testRespondMethod("hell");

if(result) {

console.log("respond this method");

}

} catch (e) {

console.log(e);

}

}

注意: 如果使用Promiss我們不需要參數(shù)柒桑,則在OC去掉name那一行就行了弊决;如果需要多個(gè)參數(shù),在name下面多加一行就行了魁淳,注意它們之間不需要添加逗號(hào)丢氢。

6、多線程

我們這里操作的模塊沒(méi)有涉及到UI先改,所以專門建立一個(gè)串行的隊(duì)列給它使用,如下:

return dispatch_queue_create("com.liuchungui.demo", DISPATCH_QUEUE_SERIAL);

注意: 在模塊之間共享分發(fā)隊(duì)列

methodQueue方法會(huì)在模塊被初始化的時(shí)候被執(zhí)行一次蒸走,然后會(huì)被React Native的橋接機(jī)制保存下來(lái)仇奶,所以你不需要自己保存隊(duì)列的引用,除非你希望在模塊的其它地方使用它比驻。但是该溯,如果你希望在若干個(gè)模塊中共享同一個(gè)隊(duì)列,則需要自己保存并返回相同的隊(duì)列實(shí)例别惦;僅僅是返回相同名字的隊(duì)列是不行的狈茉。

更多線程的操作細(xì)節(jié)可以參考:http://reactnative.cn/docs/0.24/native-modules-ios.html#content

7、導(dǎo)出常量

原生模塊可以導(dǎo)出一些常量掸掸,這些常量在JavaScript端隨時(shí)都可以訪問(wèn)氯庆。用這種方法來(lái)傳遞一些靜態(tài)數(shù)據(jù),可以避免通過(guò)bridge進(jìn)行一次來(lái)回交互扰付。

OC中堤撵,我們實(shí)現(xiàn)constantsToExport方法,如下:

- (NSDictionary *)constantsToExport {

return @{ @"BGModuleName" : @"BGNativeModuleExample",

TestEventName: TestEventName

};

}

JS中羽莺,我們打印一下這個(gè)常量

console.log("BGModuleName value is ", BGNativeModuleExample.BGModuleName);

但是注意這個(gè)常量?jī)H僅在初始化的時(shí)候?qū)С隽艘淮问底颍约词鼓阍谶\(yùn)行期間改變constantToExport返回的值,也不會(huì)影響到JavaScript環(huán)境下所得到的結(jié)果盐固。

8荒给、給JS發(fā)送事件

即使沒(méi)有被JS調(diào)用,本地模塊也可以給JS發(fā)送事件通知刁卜。最直接的方式是使用eventDispatcher志电。

在這里,我們?yōu)榱四軌蚪邮盏绞录ば铮覀冮_一個(gè)定時(shí)器溪北,每一秒發(fā)送一次事件。

#import "BGNativeModuleExample.h"

#import "RCTEventDispatcher.h"

@implementation BGNativeModuleExample

@synthesize bridge = _bridge;

- (instancetype)init {

if(self = [super init]) {

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendEventToJS) userInfo:nil repeats:YES];

}

return self;

}

- (void)receiveNotification:(NSNotification *)notification {

[self.bridge.eventDispatcher sendAppEventWithName:TestEventName body:@{@"name": @"Jack"}];

}

@end

在JS中,我們這樣接收事件

NativeAppEventEmitter.addListener(BGNativeModuleExample.TestEventName, info => {

console.log(info);

});

注意: 編寫OC代碼時(shí)之拨,需要添加@synthesize bridge = _bridge;茉继,否則接收事件的時(shí)候就會(huì)報(bào)Exception -[BGNativeModuleExample brige]; unrecognized selector sent to instance的錯(cuò)誤。

上面原生代碼就編寫好了蚀乔,主要以代碼實(shí)踐為主烁竭,彌補(bǔ)官方文檔中的一些不足,如果要需要了解更多的原生模塊封裝的知識(shí)吉挣,可以參考原生模塊派撕,也可以參考官方的源代碼。

二睬魂、發(fā)布上線

我們按照上面步驟編寫好原生模塊之后终吼,接下來(lái)將我們寫的原生模塊發(fā)布到npm。

1氯哮、我們需要?jiǎng)?chuàng)建github倉(cāng)庫(kù)

在github上創(chuàng)建一個(gè)倉(cāng)庫(kù)react-native-BGNativeModuleExample际跪,然后關(guān)聯(lián)到我們前面創(chuàng)建的react-native-BGNativeModuleExample目錄

$ cd TestProject/node_modules/react-native-BGNativeModuleExample

$ git init .

$ git remote add origin https://github.com/liuchungui/react-native-BGNativeModuleExample.git

2、我們需要?jiǎng)?chuàng)建原生模塊的入口文件

我們需要在react-native-BGNativeModuleExample目錄下創(chuàng)建一個(gè)index.js喉钢,它是整個(gè)原生模塊的入口姆打,我們這里只是將原生進(jìn)行導(dǎo)出。

//index.js

import React, { NativeModules } from 'react-native';

module.exports = NativeModules.BGNativeModuleExample;

3肠虽、發(fā)布到npm

在發(fā)布到npm之前幔戏,我們需要?jiǎng)?chuàng)建一個(gè)package.json文件,這個(gè)文件包含了module的所有信息税课,比如名稱闲延、版本、描述韩玩、依賴慨代、作者、license等啸如。 我們?cè)趓eact-native-BGNativeModuleExample根目錄下使用npm init命令來(lái)創(chuàng)建package.json侍匙,系統(tǒng)會(huì)提示我們輸入所需的信息,不想輸入的直接按下Enter跳過(guò)叮雳。

$ npm init

This utility will walk you through creating a package.json file.

It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields

and exactly what they do.

Use `npm install? --save` afterwards to install a package and

save it as a dependency in the package.json file.

Press ^C at any time to quit.

name: (react-native-BGNativeModuleExample)

輸入完成之后想暗,系統(tǒng)會(huì)要我們確認(rèn)文件的內(nèi)容是否有誤,如果沒(méi)有問(wèn)題直接輸入yes帘不,那么package.json就創(chuàng)建好了说莫。 我這里創(chuàng)建的package.json文件如下:

{

"name": "react-native-nativemodule-example",

"version": "1.0.0",

"description": "",

"main": "index.js",

"scripts": {

"test": "echo \\"Error: no test specified\\" && exit 1"

},

"repository": {

"type": "git",

"url": "git+[https://github.com/liuchungui/react-native-BGNativeModuleExample.git](https://github.com/liuchungui/react-native-BGNativeModuleExample.git)"

},

"author": "",

"license": "ISC",

"bugs": {

"url": "[https://github.com/liuchungui/react-native-BGNativeModuleExample/issues](https://github.com/liuchungui/react-native-BGNativeModuleExample/issues)"

},

"homepage": "[https://github.com/liuchungui/react-native-BGNativeModuleExample](https://github.com/liuchungui/react-native-BGNativeModuleExample)#readme"

}

如果我們編寫的原生模塊依賴于其他的原生模塊,我們需要在package.json添加依賴關(guān)系寞焙,我們這里由于沒(méi)有相關(guān)依賴储狭,所以不需要添加:

"dependencies": {

}

初始化完package.json互婿,我們就可以發(fā)布到npm上面了。

如果沒(méi)有npm的賬號(hào)辽狈,我們需要注冊(cè)一個(gè)賬號(hào)慈参,這個(gè)賬號(hào)會(huì)被添加到npm本地的配置中,用來(lái)發(fā)布module用刮萌。

$ npm adduser

Username: your name

Password: your password

Email: yourmail@gmail.com

成功之后驮配,npm會(huì)把認(rèn)證信息存儲(chǔ)在~/.npmrc中,并且可以通過(guò)以下命令查看npm當(dāng)前使用的用戶:

$ npm whoami

以上完成之后着茸,我們就可以進(jìn)行發(fā)布了壮锻。

$npm publish

+ react-native-nativemodule-example@1.0.0

到這里,我們已經(jīng)成功把module發(fā)布到了npmjs.org涮阔。當(dāng)然猜绣,我們也別忘記將我們的代碼發(fā)布到github。

$ git pull origin master

$ git add .

$ git commit -m 'add Project'

$ git push origin master

有時(shí)候敬特,有些文件沒(méi)必要發(fā)布途事,例如Example文件,我們就可以通過(guò).npmignore忽略它擅羞。例如我這里.npmignore文件內(nèi)容如下:

Example/

.git

.gitignore

.idea

這樣的話,我們npm進(jìn)行發(fā)布的時(shí)候义图,就不會(huì)將Example發(fā)布到npm上了减俏。

4、添加Example碱工,測(cè)試是否可用娃承,添加README

我們?cè)趓eact-native-BGNativeModuleExample目錄下創(chuàng)建一個(gè)Example的ReactNative工程,并且通過(guò)rnpm install react-native-nativemodule-example命令安裝我們發(fā)布的react-native-nativemodule-example模塊怕篷。

$ rnpm install react-native-nativemodule-example

TestProject@0.0.1 /Users/user/github/TestProject

└── react-native-nativemodule-example@1.0.0

rnpm-link info Linking react-native-nativemodule-example ios dependency

rnpm-link info iOS module react-native-nativemodule-example has been successfully linked

rnpm-link info Module react-native-nativemodule-example has been successfully installed & linked

上面提示安裝并且link成功历筝,我們就可以在js中進(jìn)行使用了。

import BGNativeModuleExample from 'react-native-nativemodule-example';

BGNativeModuleExample.testPrint("Jack", {

height: '1.78m',

weight: '7kg'

});

5廊谓、我們?cè)诎l(fā)布上線之后還需要編寫README文件

README文件是非常重要的梳猪,如果沒(méi)有README文件,別人看到我們的原生組件蒸痹,根本就不知道我們這個(gè)組件是用來(lái)干啥的春弥。所以,我們很有必要添加一個(gè)README文件叠荠,這個(gè)文件需要告訴別人我們這個(gè)原生組件是干什么的匿沛、如何安裝、API榛鼎、使用手冊(cè)等等逃呼。

6鳖孤、原生模塊升級(jí),發(fā)布新版本

當(dāng)我們添加新代碼或者修復(fù)bug后抡笼,需要發(fā)布新的版本苏揣,我們只需要修改package.json文件中的version的值就行了,然后使用npm publish進(jìn)行發(fā)布蔫缸。

總結(jié)

本篇文章主要分成兩個(gè)部分腿准,一是講述了編寫原生模塊的知識(shí),二是將我們編寫的內(nèi)容發(fā)布到npm上拾碌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吐葱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子校翔,更是在濱河造成了極大的恐慌弟跑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件防症,死亡現(xiàn)場(chǎng)離奇詭異孟辑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蔫敲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門饲嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人奈嘿,你說(shuō)我怎么就攤上這事貌虾。” “怎么了裙犹?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵尽狠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我叶圃,道長(zhǎng)袄膏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任掺冠,我火速辦了婚禮沉馆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘德崭。我一直安慰自己悍及,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布接癌。 她就那樣靜靜地躺著心赶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缺猛。 梳的紋絲不亂的頭發(fā)上缨叫,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天椭符,我揣著相機(jī)與錄音,去河邊找鬼耻姥。 笑死销钝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琐簇。 我是一名探鬼主播蒸健,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婉商!你這毒婦竟也來(lái)了似忧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丈秩,失蹤者是張志新(化名)和其女友劉穎盯捌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蘑秽,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饺著,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肠牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幼衰。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缀雳,靈堂內(nèi)的尸體忽然破棺而出渡嚣,到底是詐尸還是另有隱情,我是刑警寧澤俏险,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站扬绪,受9級(jí)特大地震影響竖独,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挤牛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一莹痢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墓赴,春花似錦竞膳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至章办,卻和暖如春锉走,著一層夾襖步出監(jiān)牢的瞬間滨彻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工挪蹭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留择葡,地道東北人脊框。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畦幢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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