官網(wǎng)沒說的那些React-native 小心得

上個(gè)迭代用React-native做了一個(gè)一期新的需求,在開的過程中舉步維艱,如履薄冰啊,做過react的同學(xué)也知道,現(xiàn)在React-native 版本才到0.43,還不算一個(gè)成熟的技術(shù),開發(fā)的過程中就是不確定性,開發(fā)一個(gè)新需求用著一個(gè)心里沒底的技術(shù),還是真是有點(diǎn)小壓力,不過還好,問題歸問題,但都不是什么大問題,想必一個(gè)只有0.43 的版本到現(xiàn)在 其實(shí)已經(jīng)不錯(cuò)了,我相信React-native 以后會(huì)有很大的潛力的,廢話不多說,說一下自己的心得,供大家參考,如果有什么不對(duì)的,還請(qǐng)輕噴,然后咱們?cè)俟餐逃懀黄饘W(xué)習(xí)。

一、調(diào)試

1.我開發(fā)Rn喜歡打log,項(xiàng)目里每個(gè)方法,每個(gè)生命周期 數(shù)據(jù)回調(diào)之類的通過log去看,執(zhí)行的順序一幕了然,我不用Debug 模式調(diào)試是一因?yàn)閐ebug 速度慢,然后還得一層一層打開去找源碼打斷點(diǎn),二是因?yàn)?Debug是個(gè)終極武器似的,現(xiàn)在的程度還用不到打斷點(diǎn)的方式,除非出什么詭異的問題你看不到log ,這個(gè)時(shí)候Debug也是個(gè)很好的工具。關(guān)于打log 請(qǐng)看我的這篇文章React-native調(diào)試小技巧,在Logcat輸出console的log,不用搖晃也能彈出Debug 彈窗

2.用數(shù)據(jù)線連接著真機(jī)去調(diào)試,而不是用無線。有人問 既然有無線了為什么還用數(shù)據(jù)線,這您有所不知,在我們公司,不知怎么得,公司的公共WiFi(集團(tuán)級(jí)的無線網(wǎng)絡(luò))不知道封了什么東西,導(dǎo)致在同一個(gè)局域網(wǎng)下邊的機(jī)器找不到另一個(gè)機(jī)器端口8081下的bundle包,Charles也不管用了,坑爹的WiFi啊,我們采取了用網(wǎng)線分一個(gè)自己的無線熱點(diǎn),然后這樣就可以了,不過這兩個(gè)各有優(yōu)點(diǎn),無線調(diào)試就是方便,不用限定在自己的工位,自由拿著手機(jī)隨便跑,但是每次都得去debug彈窗輸入ip:port,累覺不愛,但是基于自己開發(fā)的情況,在工位開發(fā)不用隨便亂跑,然后數(shù)據(jù)線開發(fā)在終端輸入一條命令就搞定
adb reverse tcp:8081 tcp:8081不過這個(gè)也有缺點(diǎn),就是拔掉了數(shù)據(jù)線 然后再重新按上的時(shí)候還得重新adb reverse tcp:8081 tcp:8081,插入別的iPhone手機(jī)也會(huì)導(dǎo)致 必須重新reverse,總之要不是因?yàn)榫W(wǎng)絡(luò)的原因 我也不會(huì)選擇用數(shù)據(jù)線調(diào)試。

二、繼承。

個(gè)人認(rèn)為js也是面向?qū)ο笮偷木幊陶Z言,只不過不用提前編譯運(yùn)行。所以就大膽的想出了繼承的想法。在js中 繼承的目的主要是為了復(fù)用UI,并且邏輯性不大的UI,純粹只是為了展示,并且UI差異不大的話,可以用繼承的方式去減少代碼的編寫(ps:減少的那些寫代碼的時(shí)間還不夠出問題調(diào)試的時(shí)間呢,唉),比如 我有個(gè)詳情頁的展示,只不過有個(gè)按鈕的展示的ui和邏輯是不同的,大部分是可以復(fù)用的,所以父類的寫法應(yīng)該是

export default class BaseDetailPage extends Component {
    // 構(gòu)造
    constructor(props) {
        super(props);
        // 初始狀態(tài)
        this.state = {};
    }

    componentDidMount() {
        this.getData();//去獲取詳情頁數(shù)據(jù),每個(gè)詳情頁的接口是不一樣的,所以這個(gè)方法應(yīng)該是在子類里面
    }

    componentWillUnmount() {

    }

    /**
     * 數(shù)據(jù)成功的回調(diào)
     * @param response
     */
    handleAppDetailSuccess(response) {
        this.setState({
            appDetail: response.data,
        });
    }

    /**
     *  數(shù)據(jù)獲取失敗的回調(diào)
     * @param response
     */
    handleAppDetailFailed(response) {
//做些失敗的處理
    }

    render() {

        return (
            <View style={styles.container}>
                {this.renderLogo()}
                {this.renderSizeInfo()}
                {this.renderButton()}//這一塊是調(diào)用的子類的方法

            </View>)
    }

    /**
     * 自己去寫Ui
     */
    renderLogo() {


    }

    /**
     * 自己去寫Ui
     */
    renderSizeInfo() {

    }
}

子類A的代碼:

export default class DetailA extends BaseDetailPage {
    // 構(gòu)造
    constructor(props) {
        super(props);
        // 初始狀態(tài)
        this.state = {};
    }

    /**
     * 如果子類也要用這個(gè)生命周期,必須先調(diào)用父類的
     * 不然就不會(huì)執(zhí)行了
     */
    componentDidMount() {
        super.componentDidMount();
    }


    /**
     * 子類獲取詳情頁信息,但是最終的數(shù)據(jù)還是要回傳給父類,所以調(diào)用父類的回調(diào)方法
     * 比如
     */
    getData() {
        NetUtil.get("http://www.baidu.com", this.handleAppDetailSuccess.bind(this),
            this.handleAppDetailFailed.bind(this))
    }

    /**

     * 每個(gè)子類必須重寫這個(gè)方法,因?yàn)楦割悤?huì)調(diào)用
     * 重寫這個(gè)空方法表明這個(gè)子類什么都不做
     * @returns {XML}
     */
    renderButton() {
        return <TouchableHighlight onPress={()=> {

        }
        }>
            <Text> 我是子類A 的button</Text>

        </TouchableHighlight>
    }
}

子類B的實(shí)現(xiàn):

export default class DetailB extends BaseDetailPage {
    // 構(gòu)造
    constructor(props) {
        super(props);
        // 初始狀態(tài)
        this.state = {};
    }

    /**
     * 如果子類也要用這個(gè)生命周期,必須先調(diào)用父類的
     * 不然就不會(huì)執(zhí)行了
     */
    componentDidMount() {
        super.componentDidMount();
    }
    
    /**
     * 子類獲取詳情頁信息,但是最終的數(shù)據(jù)還是要回傳給父類,所以調(diào)用父類的回調(diào)方法
     * 比如
     */
    getData() {
        NetUtil.get("http://www.baidu.com", this.handleAppDetailSuccess.bind(this),
            this.handleAppDetailFailed.bind(this))
    }

    /**

     * 每個(gè)子類必須重寫這個(gè)方法,因?yàn)楦割悤?huì)調(diào)用
     * 重寫這個(gè)空方法表明這個(gè)子類什么都不做
     * @returns {XML}
     */
    renderButton() {

        return <TouchableHighlight onPress={()=> {

        }
        }>
            <View>
                <Text> 我是子類B 的button</Text>
                <Text> 我比A多了好幾個(gè)view</Text>
                <Text> 我們都復(fù)用這個(gè)方法</Text>
            </View>
        </TouchableHighlight>
    }
}

總結(jié):

1.注釋大體說了一下子類父類之間的關(guān)系,其實(shí)這樣寫還是有很對(duì)優(yōu)點(diǎn)的,比如在一個(gè)很復(fù)雜的頁面中,復(fù)用父類是很簡單的一個(gè)事情,每塊代碼邏輯簡單明了,沒有業(yè)務(wù)的耦合,不用標(biāo)志位字段判斷 。
2.復(fù)用父類會(huì)出現(xiàn)一些問題,比如那個(gè)生命周期的復(fù)用,必須調(diào)用父類。其實(shí)和android中的生命周期有點(diǎn)類似。特別說明一些state,子類和父類的state都是相互可見的,但是必須先定義才能調(diào)用,比如父類定義this.setState({appDetail:null}),子類如果想用這個(gè)父類的字段,必須先定義這個(gè)字段。賦值的事情就交給父類就行了。
3.本人親測,在用父類的情況下,Rn的hot-loading 貌似失效了,修改了子類只會(huì)彈出toast,然后頁面沒有變化,必須重新退出之后然后在進(jìn)入,才能看到。如果修改了父類會(huì)報(bào)錯(cuò)

undefined is not an object (evaluating ‘internallnstance._pendingForceUpdate’)

解決辦法 重新reload就行了,這個(gè)我不能解釋。

三、DeviceEventEmitter的正確用法

說DeviceEventEmitter 這個(gè)類之前先介紹一些這個(gè)類是干嘛的。DeviceEventEmitter類似于android的廣播,只要注冊(cè)了要監(jiān)聽的東西,并且有相應(yīng)的發(fā)送消息的地方,就會(huì)收到這個(gè)廣播。
主要用在兩個(gè)地方:

  • native 像js發(fā)送信息
  • js 向js發(fā)送消息
    一般我們會(huì)在js初始化的函數(shù)componenDidMount()去注冊(cè)監(jiān)聽
//第一步 
 componentDidMount() {
        this.addProgressListener();
    }
//第二步
  addProgressListener() {
        this.progerss = DeviceEventEmitter.addListener('onProgress', this.progressListener.bind(this));
    }
//第三步
  progressListener(params) {
      
    }

當(dāng)然你也可以一氣呵成的去完成這個(gè)注冊(cè)監(jiān)聽的方法,看方法名也知道這個(gè)是個(gè)下載的過程,我把下載的任務(wù)放到了 native層面去做,需要rn實(shí)時(shí)的去展示,
看第三部的param,這個(gè)數(shù)據(jù)結(jié)構(gòu)是jsonObject還是jsonOBjectArray 還是要看native怎么拼裝的。

 public void sendProgress(String s, int progress) {
   
    // 發(fā)送參數(shù)
    WritableMap map = Arguments.createMap();
    map.putInt(PARAM_PROGRESS, progress);
    map.putString(APP_ID, s);
//{"appId":124,"progerss":30%} 這是一個(gè)jsonObject 當(dāng)然你可以根據(jù)業(yè)務(wù)拼接 //Arguments.createArray()
    getReactApplicationContext()
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
        .emit("onProgress", map);//這個(gè)消息發(fā)送只會(huì)發(fā)送到監(jiān)聽`onProgress `這個(gè)字段的地方

  }

個(gè)人感覺Rn的這個(gè)消息機(jī)制還是不錯(cuò)的,但是如果你感到滿足了而不繼續(xù)學(xué)習(xí),接下來會(huì)讓你很痛苦的。
為什么呢?因?yàn)槟銓W(xué)會(huì)了注冊(cè),但是沒學(xué)會(huì)移除監(jiān)聽,會(huì)引發(fā)很多的問題。

比如這個(gè)

setState(…): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op

其實(shí) 這個(gè)不算問題,只能算一個(gè)warning,但是有warning 了那么離error還遠(yuǎn)嗎。其實(shí)這個(gè)warning是因?yàn)槟沩撁娑夹遁d了,但是這個(gè)監(jiān)聽還存在著,就和android的內(nèi)存泄露一樣,一直刷著沒有mount的組件。那么只需要正確的移除就行了,樓主當(dāng)你就是沒有用到正確的移除方法,導(dǎo)致遇到了一個(gè)問題查了兩天,竟然沒查到,最后才想到是移除監(jiān)聽的方法寫錯(cuò)了。

正確的移除方式

this.progress.remove();//這個(gè)值請(qǐng)看第二步

也可以這樣

 DeviceEventEmitter.removeAllListeners();// 如果這個(gè)有back的監(jiān)聽,
//也會(huì)把 back健的監(jiān)聽也除去,不建議用這個(gè)方法

四、小知識(shí)

1.項(xiàng)目中用了一個(gè)第三方庫react-native-progress這個(gè)庫的star有700多

progress
progress

但是這個(gè)項(xiàng)目有個(gè)問題 ,就是在debug的模式下 會(huì)使用indeterminate屬性為true會(huì)崩潰,所以 就考慮到了有沒有一個(gè)全局的變量能判斷是在release模式還是在debug模式。其實(shí)是有的 這個(gè)值就是__DEV__

  if(__DEV__){
//Debug 模式
  }else{
//Release模式
  }

2.如果在一個(gè)方法中調(diào)用this.setState()兩次,那么整個(gè)頁面也會(huì)render兩次。

五、工具

作為開發(fā),各種工具必須的玩的666的,如果我作為面試官,對(duì)于工具的使用必須會(huì)在我的面試范圍之內(nèi),一個(gè)不愛瞎折騰,一個(gè)不愿接受新事物的人談什么創(chuàng)造力。
1.Charles 一個(gè)很牛逼的抓包神器。這次主要用了他的抓Https、模擬慢網(wǎng)速、

模擬網(wǎng)速
Paste_Image.png

1、入口:Proxy--Throttle Settings
2、可在“Throttle Preset”下選擇 預(yù)置的網(wǎng)絡(luò)配置(28.8~256kpbs、3G等)
3、可調(diào)節(jié)帶寬、利用%比、延遲,來模擬網(wǎng)絡(luò)環(huán)境。
備注:用的多的就是可用Throttle Preset設(shè)置2G 3G 4G,模擬不同網(wǎng)速的移動(dòng)網(wǎng)絡(luò)下app運(yùn)行情況; 設(shè)置延遲,查看高延遲情況下,app提示及處理是否正常。

Https抓包
  1. charles的設(shè)置
    1.1 選擇 help | Install Charles CA SSL Certificate
Paste_Image.png

1.2 然后會(huì)密碼輸入框,然后輸入點(diǎn)擊確定,Charles會(huì)把證書寫進(jìn)你的鑰匙串了

Paste_Image.png

1.3 選擇Proxy | Proxy Settings,彈出proxy設(shè)置選項(xiàng)卡,勾選Enabling transparent HTTP proxying

Paste_Image.png

1.4 選擇ssl,勾選Enable SSL Proxying,在Location部份選擇add,按如下圖添加,抓取任意站點(diǎn)、443端口的數(shù)據(jù)

Paste_Image.png

1.手機(jī)安裝證書。
下載證書 http://www.charlesproxy.com/documentation/additional/legacy-ssl-proxying/

Paste_Image.png

2.安裝證書

2.1、Android:把證書放到儲(chǔ)存設(shè)備上,然后 設(shè)置-安全-從SD卡安裝

怎么把證書放到存儲(chǔ)設(shè)備上,這個(gè)是我要說的,看著測試沒還要打開文件存儲(chǔ),然后復(fù)制粘貼真是費(fèi)勁

發(fā)送文件

adb push charles-proxy-ssl-proxying-certificate.crt /sdcard/   

[100%] /sdcard/charles-proxy-ssl-proxying-certificate.crt

支持重命名

拷貝文件

adb pull /sdcard/charles-proxy-ssl-proxying-certificate.crt

[100%] /sdcard/charles-proxy-ssl-proxying-certificate.crt

2.2、ios:先用郵件把證書發(fā)到IPHONE上的郵箱,用iFile打開安裝

其實(shí)Charles還有很多強(qiáng)大的功能,比如Map、Rewrite等功能,其實(shí)還都用過,但是在這就不多說了,要說的話就要另開新帖了,這兒重點(diǎn)是講rn

最后提醒大家一定要仔細(xì),往往稍微一不留神就會(huì)造成一個(gè)很小的bug但是你不得不花費(fèi)時(shí)間去調(diào)試bug,到時(shí)候回過頭來你會(huì)恨自己為什么當(dāng)初不仔細(xì)一點(diǎn),這也證明了很多bug就是因?yàn)殚_發(fā)的時(shí)候不仔細(xì)造成的。

今天是五一假期啊,我卻在這碼字~祝大家有個(gè)好的假期。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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