RN 入門(二)—基礎知識

生命週期

在這裏插入圖片描述

組件的生命週期分成三個狀態:

Mounting

裝載已插入真實 DOM

Updating

正在被重新渲染

Unmounting

已移出真實 DOM

Mounting-裝載

1.1 constructor

構造函數,在組件掛載之前調用一次。返回值將會作爲 this.state 的初始值。

  1. 第一條語句必須是super(props)
  2. constructor將在任意一個RN組件被加載之前優先調用,並且只會調用一次。
  3. 該函數最大的作用是定義該組件當中需要使用的狀態機變量以及函數bind操作 。
constructor(props: props, context: any) {
        super(props, context);
        //初始化狀態變量
        this.state = {
            name: '張三',
        };
        //函數綁定
        this.login = this.login.bind(this);
    }
1.2 UNSAFE_componentWillMount

準備加載組件,這個函數調用時機是在組件創建,並初始化了狀態之後,在第一次繪製 render() 之前。可以在這裏做一些業務初始化操作,也可以設置組件狀態。這個函數在整個生命週期中只被調用一次。

  1. 函數整個過程中只執行一次。
  2. 在初始渲染前執行,即在render被調用之前調用。
  3. 子組件中同樣擁有該方法,並會在父組件的componentWillMount函數之後被調用。
  4. 該函數適合於需要在本地讀取一些數據用於顯示,那麼在render執行前調用是一個很好的時機。
UNSAFE_componentWillMount(): void {
        //加載本地數據
        AsyncStorage.getItem('data')
            .then((listData) => {
                //TODO...
            }).catch((error) => {
            //TODO...
        });
    }
1.3 render

開始渲染

  1. 調用該方法,先對狀態機變量與屬性進行檢查。

  2. 如果開發者不想渲染界面的話,可以在此處返回null或者false。

  3. 該方法適用於進行界面的JSX代碼編寫,因此不適合在此處對狀態機變量進行修改或者訪問服務器。

render() {
        return (
            <View>
                <Text>
                    姓名:{this.props.content}
                </Text>
            </View>
        );
    }
1.4 componentDidMount

函數原型:void componentDidMount();

這個函數調用的時候,其虛擬 DOM 已經構建完成,可以在這個函數開始獲取其中的元素或者子組件了。

從這個函數開始,就可以和 JS 其他框架交互了,例如設置計時 setTimeout 或者 setInterval,或者發起網絡請求。這個函數也是隻被調用一次。這個函數之後,就進入了穩定運行狀態,等待事件觸發。

  1. 函數整個過程只會調用一次。
  2. 在初始渲染完成之後調用,即在render被調用之後調用。
  3. 子組件中同樣擁有該方法,並會在父組件的componentDidMount函數之前被調用。
  4. 一般情況在這個方法中請求網絡是一個不錯的選擇。
componentDidMount(): void {
        //發起網絡請求
        fetch(url)
            .then((response) => response.json())
            .then((responseData) => {
                //TODO...
            })
            .catch((error) => {
                //TODO...
            });
    }

在這裏插入圖片描述

Updating-更新

2.1 UNSAFE_componentWillReceiveProps

函數原型:UNSAFE_componentWillReceiveProps(nextProps);

當屬性發生改變或接收到一個新的屬性時候,該函數被調用。並接受一個輸入參數,類型爲Object,存放新的props,原先舊的props仍然可以通過this.props訪問。

  1. 接受一個Object參數,存放新的props,舊的props仍然可通過this.props訪問。
  2. 該函數在RN初次渲染時不會被調用。
  3. 如果在該函數當中對狀態機變量進行了修改,RN不會立即渲染頁面,而是會等待該方法執行完畢後一起渲染。
2.2 UNSAFE_componentWillUpdate

函數原型:boolean UNSAFE_componentWillUpdate(nextProps,nextState);

組件是否需要更新,接受兩個參數。根據返回的布爾值來決定是否需要對頁面進行重新渲染,如果不進行渲染,那麼該方法後續的componentWillUpdate與componentDidUpdate都不會被執行。

  1. 接受兩個參數,根據返回值決定是否需要重新渲染。如果不進行渲染後續的componentWillUpdatecomponentDidUpdate都不會執行。
  2. 該函數默認會返回true。
  3. 可以在該函數中編寫一些邏輯來判斷渲染類型,來阻值一些沒有必要的重新渲染,達到提升應用運行效率的目的。
2.3 componentWillUpdate

函數原型:componentWillUpdate(object nextProps, object nextState),當shouldComponentUpdate返回true後立即調用,

  1. 初始渲染完成之後,重新渲染前會調用這個函數.但是這個函數不能通過this.setSatte再次改變狀態機變量的值。
  2. 該函數無返回值。
  3. 在該方法中,不應該對狀態機變量進行修改。
2.4 componentDidUpdate

函數原型:componentDidUpdate(object prevProps, object prevState),在組件的更新已經同步到 DOM 中之後立刻被調用。

  1. 該函數會在重新渲染render之後調用,參數是渲染前的props和state,傳入上個方法必須的兩個參數即可。

Unmounting-(卸載)

3.1 componentWillUnmount

該方法會在RN卸載之前調用,無參無返回值,在該方法中,需要對該組件當中申請或者訂閱的某些資源與消息進行釋放。

在該方法中執行任何必要的清理,比如無效的定時器,或者清除在 componentDidMount 中創建的 DOM 元素。

RN 變量

State變量

state或props任何一個變化都會引起render重新執行渲染。

state表示一個組件內部自身狀態,只能在自身組件中存在。

由於state任何屬性的改變都會導致UI重繪,而UI重繪會消耗系統資源,所以在封裝可複用的組件時,儘量不用或少用state,而是通過props將數據傳遞到組件內部(props在組件內部是不可變的,不會導致UI重繪)。

Props變量

props是父組件中指定,傳遞給自組件的數據流,且一經指定,在被指定的組件的生命週期中不可更改。

在 React 中信息是單向的。我們維護着組件層次,在其中每個組件都僅依賴於它父組件和自己的狀態。通過屬性(props)我們將信息從上而下的從父組件傳遞到子元素。如果一個祖先組件需要自己子孫的狀態,推薦的方法是傳遞一個回調函數給對應的子元素。

成員變量

在RN中如果使用狀態機變量存儲於UI無關的變量,會導致不必要的判斷是否需要重新渲染,從而導致應用性能下降,正確的做法是保存在組件的成員變量中(在構造函數中定義成員變量是一個不錯的做法,可保證成員變量有初始值)。

定義

//構造函數中定義成員變量
constructor(props) {
        super(props);
        //成員變量
        this.inputContent = '';
    }

使用

<TextInput
     placeholder={'登陸賬號 '}
     onChangeText={(content) => {
        //將輸入內容賦值給當前的局部變量
        this.inputContent = content;
     }}
/>

<Button
    title="點擊2"
    onPress={() => {
      //輸出局部變量
      console.log('輸入的賬號爲:' + this.inputContent);
     }}
/>

靜態變量

React Native 允許組件有靜態變量、靜態成員函數。它們的作用與 C++,Java 中的類靜態變量、類靜態成員函數基本一樣。

定義

export default class RegisterComponent extends Component {
    //定義類的靜態成員變量
    static loginName = '';
    static loginPwd = '';
    //定義類的靜態成員函數
    static login() {
        console.log('loginName:' + this.loginName);
        console.log('loginPwd:' + this.loginPwd);
    }
}

使用

訪問方式類名.變量名類名.函數名,不能以this調用。

export default class LoginComponent extends Component {
   render() {
        return (
            <View>
                <TouchableOpacity
                    style={loginStyle.loginButtonTouchable}
                    activeOpactiv="0"
                    onPress={() => {
                        //直接類名.靜態變量/靜態函數名調用
                        RegisterComponent.loginName = 'zcmain';
                        RegisterComponent.loginPwd = '99999';
                        RegisterComponent.login();
                         }}>
                </TouchableOpacity>
            </View>
           );
}

RN混合開發

1. RN調用原生方法

Android端實現

步驟

  1. 創建繼承ReactContextBaseJavaModule的Module類。

    1.1 實現getName方法,返回一個字符串,該字符串是標記導出的原生模塊,供RN端調用

    ​ (不要使用ToastAndroid作爲導出名字,會與RN內置的衝突)。

    1.2 定義並實現使用ReactMethod註解的Java方法,且方法返回值只能是void類型(RN跨語言訪問是異

    ​ 步的,所以RN想要返回值唯一辦法是使用回掉函數或者發送事件

    ​ 參見:《Android原生調用RN方法實現》。

  2. 創建實現ReactPackage接口的RN包管理器類,並將前一步創建的Module類註冊到該包管理器的createNativeModules方法中。

  3. 將創建的RN包管理器類添加到Application中的getPackages方法中。

實現

  1. 原生創建一個類繼承ReactContextBaseJavaModule類,實現getName方法,用於提供給RN側調用原生代碼的接口名稱。並使用@ReactMethod註解定義給RN側調用的函數

    /**
     *1.創建繼承ReactContextBaseJavaModule的Module類
     */
    public class AndroidNativeModule extends ReactContextBaseJavaModule {
        ReactApplicationContext context;
        public AndroidNativeModule(@NonNull ReactApplicationContext reactContext) {
            super(reactContext);
            this.context = reactContext;
        }
    
        //1.1 實現getName方法,返回一個字符串(該字符串是標記導出的原生模塊)
        @NonNull
        @Override
        public String getName() {
            return "AndroidNativeModule";
        }
        
        /**
         *1.2 定義並實現使用ReactMethod註解的Java方法,且方法返回值只能是void類型。
         */
        //提供給RN調用的方法(無參)
        @ReactMethod
        public void callAndroidMethod() {
            Log.d("AndroidNativeModule","from the RN call...");
        }
    
        //提供給RN調用的方法(有參)
        @ReactMethod
        public void callAndroidParamsMethod(String name, int age) {
            Log.d("AndroidNativeModule", "from the RN call name:" + name + "\tage:" + age);
        }
    }
    
  2. 原生創建一個類實現ReactPackage包管理器接口。實現該接口的createNativeModules函數,將原生代碼創建的AndroidNativeModule添加到RN包管理器中。

    /**
     *2.創建類實現ReactPackage接口,實現createNativeModules方法將之前創建的 AndroidNativeModule
     *  添加到該包管理器中。
     */
    public class AndroidNativePackage implements ReactPackage {
        
        @NonNull
        @Override
        public List<NativeModule> createNativeModules(@NonNull 
            ReactApplicationContext reactContext) {
            //將之前創建的 AndroidNativeModule 添加到該包管理器中
            List<NativeModule> nativeModules = new ArrayList<>();
            nativeModules.add(new AndroidNativeModule(reactContext));
            return nativeModules;
        }
    
        @NonNull
        @Override
        public List<ViewManager> createViewManagers(@NonNull 
            ReactApplicationContext reactContext) {
            //返回空集合
            return Collections.emptyList();
        }
    }
    
  3. 將原生創建的AndroidNativePackage包管理器實例添加到在Application中的getPackages函數中。

     @Override
    protected List<ReactPackage> getPackages() {
         List<ReactPackage> packages = new PackageList(this).getPackages();
         //將創建原生的包管理器添加到ReactPackage列表中
         packages.add(new AndroidNativePackage());
         return packages;
     }
    
  4. RN側調用

    //1.導入NativeModules組件
    import { NativeModules } from 'react-native';
    
    //2.使用 NativeModules.原生導出模塊.函數名稱。
    invokerNative() {
        //調用原生無參函數
        NativeModules.AndroidNativeModule.callAndroidMethod();
        //調用原生有參函數
        NativeModules.AndroidNativeModule.callAndroidParamsMethod('Android',30);
    }
    

IOS端實現

步驟

  1. 創建頭文件(*.h)引入RCTBridgeModule.h頭文件,來使用RCTBridgeModule協議。

  2. 創建實現文件(*.m):

    2.1 使用RCT_EXPORT_MODULE(js_name)宏,標記導出的原生模塊。如果你不指定名稱,默認

    ​ 就會使用這個 Objective-C 類的名字。如果類名以 RCT 開頭,則 JavaScript 端引入的模塊名會自動

    ​ 移除這個前綴。

    2.2 使用RCT_EXPORT_METHOD()宏,導出實現的原生函數,且函數返回值只能是void(RN跨語言訪問是

    ​ 異步的,所以RN想要返回值唯一辦法是使用回掉函數或者發送事件

    ​ 參見《IOS原生調用RN方法實現》。

    PS:React Native 還定義了一個RCT_REMAP_METHOD()宏,它可以指定 JavaScript 方法名。所以當原生

    ​ 端存在重載方法時,可以使用這個宏來避免在 JavaScript 端的名字衝突。

實現

  1. 創建原生頭文件(.h)

    //1.創建頭文件
    #import <Foundation/Foundation.h>
    //導入RCTBridgeModule.h文件
    #import "RCTBridgeModule.h"
    @interface Hello : NSObject <RCTBridgeModule>
    @end
    
  2. 創建頭文件實現文件(.m)

    //2.創建實現文件
    #import <Foundation/Foundation.h>
    #import "Hello.h"
    
    @implementation Hello
        
    //2.1 使用RCT_EXPORT_MODULE(js_name)宏,標記導出的原生模塊。
    RCT_EXPORT_MODULE(IOSNativeModule);
    
    /**
     *2.2 使用RCT_EXPORT_METHOD()宏,導出實現的原生函數,且函數返回值只能是void
     */
    //導出供RN調用方法(無參)
    RCT_EXPORT_METHOD(callIosMethod){
      NSLog(@"from the RN call...");
    }
    
    //導出供RN調用方法(有參)
    RCT_EXPORT_METHOD(callIosParamsMethod:(NSString *)name age:(int)age)
    {
      NSLog(@"from the RN call name%@ age%d",name,age);
    }
    @end
    
  3. RN側實現調用

    //導入 NativeModules 組件
    import { NativeModules } from 'react-native';
    
    //使用 NativeModules.原生導出模塊.函數名
    invokerNative() {
        //調用原生無參函數
        NativeModules.IosNativeModule.callIosMethod();
        //調用原生有參函數
        NativeModules.IosNativeModule.callIosParamsMethod('IOS', 26);
    }
    

2. 原生調用RN方法

  • 被動調用:原生模塊還支持一種特殊的參數——回調函數。它提供了一個函數來把返回值傳回給 JavaScript,即RN先調用原生函數,然後原生通過回掉函數返回數據至RN。後引申爲Promise機制實現。
  • 主動調用:原生模塊主動向 JavaScript 發送事件通知。最好的方法是繼承RCTEventEmitter,實現suppportEvents方法並調用self sendEventWithName:

Android端實現

被動調用(Promise機制)

Androd端通過Promise機制被動發消息到RN,實現上與RN調用Android端方法實現相似,唯一不同點是如果橋接原生方法的最後一個參數爲Promise對象,則對應的JS方就會返回一個Promise對象。

步驟

  1. 相同步驟參考《RN調用Android原生方法實現

  2. 不同點原生的Module中:

2.1 使用ReactMethod註解的Java方法,最後一個參數爲Promise對象。

實現

  1. 創建原生Module、包管理器、添加到Application中步驟略(參考:《RN調用Android原生方法實現》)。

  2. 原生Module中ReactMethod註解的方法,最後一個參數爲Promise對象

    //提供給RN調用的方法,且通過Promise對象返回數據到RN
    @ReactMethod
    public void callAndroidParamsMethodAcceptBackMsg(String fromRnMsg, Promise promise) {
        Log.d("AndroidNativeModule", "RN:" + fromRnMsg);
        String toRnMsg = "Android:Hello RN Nice to meet you! !";
        if (!TextUtils.isEmpty(toRnMsg)) {
            //通過promise的resolve返回正常數據
            promise.resolve("\n\n" + fromRnMsg + "\n" + toRnMsg);
        } else {
            //通過promise的reject返回異常
            promise.reject("11", "msg cannot be empty!");
        }
    }
    
  3. RN測調用

    //NativeModules.原生導出模塊.原生註解方法名稱
    NativeModules.AndroidNativeModule
              .callAndroidParamsMethodAcceptBackMsg('RN:Hello Android!')
              .then((response) => {
                 	 Alert.alert('Title', response);
           	   }).catch((error) => {
                 	 Alert.alert('Title', error.message);
               });
    

主動調用(RCTEventEmitter事件)

即使沒有使用Promise機制被RN被動調用,原生模塊也可以主動給RN發送事件通知。最簡單辦法是通過RCTDeviceEventEmitter,這可以通過ReactContext來獲得對應的引用。然後調用其ReactContext.getJSMethod(RCTDeviceEventEmitter.class).emit(事件名稱,data參數)來發送數據即可。

注意:使用emit發送的消息數據需要包裝成WritableMap對象,否則會出現異常

使用emit發送的消息數據需要包裝成WritableMap對象,否則會出現異常

使用emit發送的消息數據需要包裝成WritableMap對象,否則會出現異常

步驟

  1. 創建原生Module、包管理器、添加到Application步驟略(參考:《RN調用Android原生方法實現》)。

  2. 我們通常封裝成一個工具類

    1.1 內部持有一個ReactContext對象的引用。

    1.2 實現原生向RN發送消息的方法

  3. 然後在原生創建的RN包管理器的createNativeModules方法中初始化該工具類。

  4. RN端配置:

    4.1 導入NativeEventEmitterNativeModules組件

    4.2 通過NativeModules.原生導出模塊實例化NativeEventEmitter對象

    4.3 調用NativeEventEmitteraddlistener(事件名,回調函數)來監聽事件

    ​ (推薦在UNSAFE_componentWillMount中開啓事件訂閱監聽)。

    4.4 別忘記最後取消訂閱(推薦在componentWillUnmount中取消訂閱)。

實現

  1. 創建原生Module、包管理器、添加到Application步驟略(參考:《RN調用Android原生方法實現》)。

  2. 創建通信工具類

    //1.創建原生向RN主動發送消息工具類
    public class DeviceEventEmitterUtil {
        //1.1內部持有ReactContext對象引用
        private static ReactContext reactContext;
        
        public DeviceEventEmitterUtil(ReactContext reactContext) {
            this.reactContext = reactContext;
        }
    
        /**
         * 1.2原生向RN發送消息
         * @param eventName 事件名稱
         * @param params    參數(注意包裝成WritableMap對象)
         */
        public static void sendMsgToRn(String eventName, WritableMap params) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                        .emit(eventName, params);
        }
    }
    

    模擬客戶端調用該方法

    void sendMsgToRn() {
      try {
           //模擬訂單信息(需要包裝成WritableMap對象)
           WritableMap orderParams = Arguments.createMap();
           orderParams.putString("orderTime", "20191219154030");
           orderParams.putString("orderAmount", "18888.09");
           orderParams.putString("orderName", "MacBook Pro");
           //發送提交訂單消息到RN
           DeviceEventEmitterUtil.sendMsgToRn("order", orderParams);
    
           //模擬支付信息(需要包裝成WritableMap對象)
           WritableMap payParams = Arguments.createMap();
           payParams.putString("orderId", "90888878789");
           //發送支付消息到RN
           DeviceEventEmitterUtil.sendMsgToRn("pay", payParams);
        } catch (Exception e) {
           Log.e("error", e.getMessage());
        }
    }
    
  3. 在原生創建的包管理器的createNativeModules方法中實例化(註冊)該工具類。
    在這裏插入圖片描述

  4. RN側實現

    //4.1 導入NativeEventEmitter、NativeModules組件
    import {
        NativeModules,
        NativeEventEmitter,
    } from 'react-native';
    
    //4.2 通過【NativeModules.原生導出模塊】實例化NativeEventEmitter對象
      const androidEventEmitter = new  NativeEventEmitter(
            						   NativeModules.AndroidNativeModule);
    
    /**
     * 4.3 監聽原生主動發送過來的消息
     */
    listenerNativeMsg() {
      //監聽原生的order消息事件  
      this.subScriptOrder = androidEventEmitter.addListener('order', (data) => {
            Alert.alert('order:', JSON.stringify(data));
      });
      //監聽原生的pay消息事件
      this.subScriptPay = androidEventEmitter.addListener('pay', (data) => {
           Alert.alert('pay:', JSON.stringify(data));
      });
    }
    
    //4.4 開啓事件訂閱(建議在UNSAFE_componentWillMount中)
    UNSAFE_componentWillMount(): void {
       console.log('開啓訂閱');
       this.listenerNativeMsg();
    }
    
    //4.5取消事件訂閱(建議在componentWillUnmount中)
    componentWillUnmount(): void {
        console.log('取消訂閱');
        this.subScriptOrder.remove();
        this.subScriptPay.remove();
    }
    

IOS端實現

被動調用(Promise機制)

IOS端通過Promise機制被動發送消息到RN,實現上與RN調用IOS端方法實現相似,唯一不同點是參數最後兩個分別是Promise的RCTPromiseResolveBlockRCTPromiseRejectBlock類型。

步驟

  1. 相同步驟參考《RN調用IOS原生方法

  2. 不同點實現文件中:

    2.1 使用RCT_EXPORT_METHOD宏,導出的函數最後兩個參數爲RCTPromiseResolveBlock

    RCTPromiseRejectBlock

實現

  1. 創建頭文件略(參考《RN調用IOS原生方法》)

  2. 創建頭文件實現文件(.m)

    //2.創建實現文件
    #import <Foundation/Foundation.h>
    #import "Hello.h"
    
    @implementation Hello
        
    //使用RCT_EXPORT_MODULE(js_name)宏,標記導出的原生模塊。
    RCT_EXPORT_MODULE(IOSNativeModule);
    
    //2.1 導出供RN調用的方法(有參且原生通過Promise機制返回數據到RN)
    RCT_EXPORT_METHOD(callIosParamsMethodAcceptBackMsg:(NSString *)msg resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
       NSString *toRnMsg = @"IOS:Hello RN Nice to meet you! !";
      if (![msg isEqualToString:@""]) {
        NSString *response =[NSString stringWithFormat:@"\n\n%@\n%@",msg,toRnMsg];
        resolve(response);
      } else {
        reject(@"warning", @"msg cannot be empty!", nil);
      }
    }
    @end
    
  3. RN側調用

    //1.導入NativeModules組件
    import {NativeModules} from 'react-native';
    //2.使用NativeModules.原生導出模塊.方法名
     NativeModules.IosNativeModule
         	.callIosParamsMethodAcceptBackMsg('RN:Hello IOS !')
         	.then((response) => {
                  Alert.alert('Title', response);
             }).catch((error) => {
                  Alert.alert('Title', error.message);
             });
    

主動調用(RCTEventEmitter事件)

即使沒有使用Promise機制被RN被動調用,原生模塊也可以主動給RN發送事件通知,最好的辦法是繼承RCTEventEmitter,實現supportEvents方法並調用self sendEventWithName:實現。

步驟

  1. 創建頭文件(.h)繼承RCTEventEmitter(需要導入RCTEventEmitter.hRCTBridgeModule.h

  2. 創建實現文件(.m):

    2.1 使用RCT_EXPORT_MODULE(js_name)宏,導出原生模塊。

    2.2 實現supportedEvents方法並且返回發送事件的名稱(後續原生通過該事件名稱發送消息到RN,

    ​ RN端通過該事件名稱接收消息)。

    2.3 原生調用[self sendEventWithName:事件名稱,body:]來發送消息到RN端。

  3. RN端使用:

    3.1 導入NativeEventEmitterNativeModules組件。

    3.2 使用解構賦值原生導出模塊實例化出NativeEventEmitter對象。

    3.3 構造函數中聲明事件訂閱的局部變量。

    3.4 使用NativeEventEmitter實例對象調用addListener(evnetName,listenerFunaction)指定原

    ​ 生supportedEvents函數中聲明的事件名稱,以及回掉函數即可。

    3.5 在需要地方進行事件訂閱(推薦在UNSAFE_componentWillMount中進行訂閱)。

    3.6 在需要地方取消訂閱(componentWillUnmount中取消訂閱)。

實現

  1. 創建頭文件(.h)

    //1.創建頭文件
    #import <Foundation/Foundation.h>
    //導入RCTBridgeMOdule和RCTEventEmitter頭文件
    #import <React/RCTBridgeModule.h>
    #import <React/RCTEventEmitter.h>
    //繼承RCTEventEmitter父類
    @interface SendMsgToRn :RCTEventEmitter<RCTBridgeModule>
    @end
    
  2. 創建實現文件(.m)

    //2.創建頭文件
    #import "SendMsgToRn.h"
    @implementation SendMsgToRn
    
    //2.1使用RCT_EXPORT_MODULE(js_name)宏導出模塊
    RCT_EXPORT_MODULE(IosNativeModule)
    
    //2.2實現RCTEventEmitter類中的supportedEvents方法,返回事件名稱(可以定義多個事件名稱)
    - (NSArray<NSString *> *)supportedEvents{
      return @[@"order",@"pay"];
    }
    
    //2.3原生主動向RN發送事件方法封裝
    -(void)sendMsgToRn:(NSString *)eventName eventData:(NSObject *)params{
      NSLog(@"發送事件名稱%@,發送事件參數%@",eventName,params);
       //最終調用父類(RCTEventEmitter)的sendEventWithName方法指定事件名稱來發送消息
      [self sendEventWithName:eventName body:params];
    }
    @end   
    

    模擬客戶端調用該類方法向RN發送消息

    -void)sendMsgToRn{
       NSObject *order = @{@"orderTime":@"20191218125942",
                           @"orderAmount":@"14888.88",
                           @"orderName":@"MacBook Pro"};
       //調用提交訂單方法
       [self sendMsgToRn:@"order" eventData:order];
        
       //調用支付方法
       NSObject *payInfo = @{@"orderId":[NSNumber numberWithInteger:9876563541]};
       [self sendMsgToRn:@"pay" eventData:payInfo];
    }
    
  3. RN側實現

    //3.1 導入NativeModules、NativeEventEmitter組件
    import {
        NativeModules,
        NativeEventEmitter,
    } from 'react-native';
    
    //3.2 通過【NativeModules.原生導出模塊】實例化NativeEventEmitter對象
    const iosEventEmitter = new NativeEventEmitter(NativeModules.IosNativeModule);
    
    export default class MyCommonent extends Component{
        //3.3 構造方法中定義局部訂閱變量
        constructor(props) {
      		super(props);
      		let subscribeOrder;
      		let subscribePay;
    	}
        
        //3.4 使用實例化後的NativeEventEmitter對象調用addListener方法(指定事件名稱和回調函數)
    	listenerNativeMsg(){
      		//監聽原生的order事件
      		this.subscribeOrder = iosEventEmitter.addListener('order',(data)=>{
           		Alert.alert('order:', JSON.stringify(data));
      		});
      		//監聽原生的pay事件
       		this.subscribePay = iosEventEmitter.addListener('pay',(data)=>{
           		Alert.alert('order:', JSON.stringify(data));
      		});
    	}
        
        //3.5 UNSAFE_componentWillMount中開啓訂閱
    	UNSAFE_componentWillMount(): void {
      		console.log('開啓訂閱');
      		this.listenerNativeMsg();
    	}
        
        //3.6 別忘記在componentWillUnmount聲明週期方法中取消訂閱
    	componentWillUnmount(): void {
       		console.log('取消訂閱');
       		this.subscribeOrder.remove();
       		this.subscribePay.remove();
    	}
    }
    

RN組件創建方式

ES6方式創建

//ES6方式創建並導組件
    export default class ComponentA extends Component{
      render(){
        return(
             <Text>ES6方式創建組件</Text>
        );
      }
    }

ES5方式創建(該方法最新版本已經廢棄會報錯)

  1. Es5創建的組件,其成員函數會自動綁定this,也就是說,在任何時候,我們通過this拿到的對象,都是指向當前的組件類的;
  2. 配置組件屬性類型propTypes及其默認props屬性defaultProps ,Es5創建的組件,其propTypes及其默認props屬性defaultProps會作爲組件實例的屬性來進行配置,其中defaultProps是通過組件的getDefaultProps方法來獲取的;
  3. 配置組件的初始狀態state ,Es5創建的組件,其初始狀態state是通過getInitialState方法來進行配置的
//ES5創建組件
    //注意:React.createClass從0.48開始被刪除,可以使用
    //create-react-class 包中的 createReactClass 方法替代
    var Hellocommpents=createReactClass({
     render(){
        return
         <text style="{{fontSize:15,backgroundColor:'green'"> Hellocommpents</text>
    
     }
    } )
    module.exports=Hellocommpents;

函數式創建

  1. 組件不會被實例化,整體渲染性能得到提升;
  2. 組件不能訪問this對象;
  3. 組件無法訪問生命週期的方法;
  4. 組件只能訪問輸入的props;
//函數方式定義組件並導出
    function Hellocommpents() {
      //注意結尾需要帶 ";"
      return  <text style="{{fontSize:15,backgroundColor:'green'"> Hellocommpents</text>;
    }
    module.exports=Hellocommpents;

或者

 //或者 函數方式定義組件並導出
    const Hellocommpents = ({title,onClick})=>(
      <View>
      	<Text>標題{title}</Text>
       	<Button
              title={'確認'} 
              onPress={onClick}/>
      </View>
    )
    
    //定義屬性約束
    Hellocommpents.propTypes = {
      onClick: PropTypes.func.isRequired,
      text: PropTypes.string.isRequired
    }
    //導出組件
    export default Hellocommpents;

RN中PropTypes屬性確認使用

什麼是屬性確認?

使用React-native創建的組件是可以複用的,所以我們封裝的組件可以用在其他項目或給項目組其他人使用。但是別人可能對這個組件不熟悉,經常忘記使用某些屬性,或者某些屬性傳遞的數據類型有誤。因此我們可以在開發React Native自定義組件時,可以通過PropTypes屬性確認來聲明這個組件需要哪些屬性。這樣,如果在調用這個自定義組件時沒有提供相應的屬性,則會在手機與調試工具中彈出警告信息,告知開發者該組件需要哪些屬性。

簡言之使用Prop-Types屬性確認優點:

  • 可以實現類型檢查,當傳入錯誤的屬性值,會報警告,但是不會報錯;
  • 用PropTypes定義屬性,外界使用的時候會有提示;

注意:

  1. 爲了保證React Native代碼高效運行,屬性確認僅在開發環境中有效。也就是說,正式發佈的App運行時是不會進行檢查的。
  2. PropTypes必須要用static聲明,否則無效果(僅在組件是通過ES6創建時候使用,如果通過函數創建則屬性確認通過組件名稱.propTypes={}來聲明,具體可參考《RN通過函數創建組件》)
  3. PropTypes只能用於React框架的自定義組件,默認JS是沒有的,因爲它是React框架中的。

安裝屬性確認prop-types組件

  1. 安裝prop-types
    npm install --save prop-types
    #或者
    yarn add prop-types
  2. 使用(需要使用的js文件中引入)
    import PropTypes from ‘prop-types’;

屬性確認prop-types中語法

  1. 要求屬性是指定的JavaScript基本類型

    屬性名: PropTypes.array,    //指定屬性爲數組類型
    屬性名: PropTypes.bool,     //指定屬性爲布爾類型
    屬性名: PropTypes.func,     //指定屬性爲函數類型
    屬性名: PropTypes.number,   //指定屬性爲數值類型
    屬性名: PropTypes.object,   //指定屬性爲Object類型
    屬性名: PropTypes.string,   //指定屬性爲字符串類型
    
  2. 要求屬性是可渲染的節點

    屬性名:PropTypes.node,
    
  3. 要求屬性是某個React元素

    屬性名:PropTypes.element,
    
  4. 要求屬性是某個指定類的實例

    屬性名:PropTypes.instanceOf(NameOfAClass),
    
  5. 要求屬性取值爲特定的幾個值

    屬性名:PropTypes.oneOf(['value1','value2']),
    
  6. 要求屬性爲指定類型中的一個

    屬性名:PropTypes.oneOfType([
             PropTypes.bool,
             PropTypes.number,
             PropTypes.instanceOf(NameOfAClass),
           ])
    
  7. 要求屬性爲指定類型的數組

    屬性名: PropTypes.arrayOf(PropTypes.number),
    
  8. 要求屬性是一個有特定成員變量的對象

    屬性名: PropTypes.objectOf(PropTypes.number),
    
  9. 要求屬性是一個指定構成方式的對象

    屬性名: PropTypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number,
      }),
    
  10. 屬性可以是任意類型

    屬性名: PropTypes.any
    

PS:上述十種語法中都可以通過在後面加上isRequired 聲明它是必需的。

屬性名: PropTypes.array.isRequired,
屬性名: PropTypes.any.isRequired,
屬性名: PropTypes.instanceOf(NameOfAClass).isRequired,

給自定義屬性設置初始值

  • 如果想要給自定義屬性添加默認初始值,需要使用defaultProps
  • 注意:也是需要用static修飾
 static defaultProps = {
        name: 'scottDefault',
        age: 12
    }

示例

   import React,{Component} from 'react';
    import {...} from 'react-native';
    //定義組件並導出
    export default class CompontentA extends Component{
    //定義常量
    const NAV_BAR_HEIGHT_ANDROID=50;   //Android的NavigationBar高度
    const NAV_BAR_HEIGHT_IOS = 44;     //iOs的NavigationBar高度
    const STATUS_BAR_HEIGHT=20;        //狀態欄高度
    const StatusBarShape={             //狀態欄形狀的約束,用來屬性確認
      backgroundColor:PropTypes.string,
      barStyle:PropTypes.oneOf('default' , 'light-content' , 'dark-content'),
      hidden:PropTypes.bool,
    }
    
    //定義屬性約束使用 static 修飾
    static propTypes = {
        style: PropTypes.object,        //NavigationBar樣式約束
        title: PropTypes.string,        //標題約束:文本類標題
        titleView:PropTypes.element,    //標題的樣式約束
        hide:PropTypes.bool,            //是否隱藏NavigationBar
        leftButton:PropTypes.element,   //NavigationBar左側按鈕
        rightButton:PropTypes.element,  //NavigationBar右側按鈕
        staturBar: PropTypes.shape(StatusBarShape),  //狀態欄
      };
    
    //給組件設置默認值使用static修飾
    static defaultProps ={
        staturBar:{
          barStyle: 'light-content',
          hidden:false,
        }
      }
    
    render(
      //獲取用戶設置的狀態欄的樣式,用於下面的取出
      let statusView = <View 
      					style={[styles.staturBar , this.props.staturBar]}>  
                        //取出用戶設置的狀態欄的樣式
                        <StatusBar {...this.props.staturBar}/>
                  	   </View>
    
      let titleView = this.props.titleView ? 
          			  this.props.titleView : 
      				  <Text style={styles.title}>{this.props.title}</Text>;
    
      let contentView = <View style={styles.navBar}>
                      			{this.props.leftButton}
                      			<View style={styles.titleViewContainer}>
                        				{statusView}
                        				{titleView}
                      			</View>
                      			{this.props.rightButton}
                    		</View>;
        return(
            <View style={styles.container}>
              {contentView}
            </View>
        );
       }
    }

RN組件引用

構建完你的組件之後,你可能會想要去尋求一個辦法,來直接調用你在render()返回的組件的實例的方法。在大部分情況下,這應該不是必須的,因爲在響應式數據流中,你要輸出一些數據,你應該在render()中給子組件傳遞最新的屬性。不過,在某些特殊情況下,直接操作組件實例的方法還是必要或者有利的。所以React提供了一個打破限制的辦法,這就是refsrefs(reference,引用)在以下時候特別有用:當你需要直接操作一個組件渲染的DOM標記(譬如要調整它的絕對位置),或者在一個大型的非React應用中使用一個React組件,或者是把你已有的代碼庫在React中複用。

讓我們來看看怎麼獲取一個ref

render() {
    return(
        //ES6箭頭函數表示
        <TextInput ref={(c)=>{this._input=c}}/>
    );
  }

//使用
componentDidMount() {
    this._inputText.focus();
}

RN父子組件傳值

例如:A組件是登陸頁面,B組件爲自定義的輸入框屬於A組件的一個子組件,那麼我們在B組件中的輸入值在父組件A中如何獲取呢?可通過props方式通過回調函數將子組件的數據回傳到父組件中。

實現原理:

  1. 父組件通過屬性props傳遞一個回調函數到子組件中。
  2. 子組件的TextInput輸入組件onTextChange函數中綁定父組件傳遞過來的回調函數即可。
    //自定義子組件
    import React, {Component} from 'react';
    import {
        View,
        Text,
        TextInput,
    } from 'react-native';
    export default class InPutComponent extends Component {
        render() {
            return (
                <View style={style.rootView}>
                    <Text 
                        style={{backgroundColor: '#999', textAlign: 'center', flex: 1}}>
                        {this.props.title}
                    </Text>
                    <TextInput 
                         style={{flex: 3}} 
                         placeholder={this.props.placeholder} 
                         //onChangeText綁定父組件傳遞過來的回調函數,將數據返回父組件
                         onChangeText={(data)=>{this.props.onChangeTextCallBack(data)}}/>
                </View>
            );
        }
    }
    //父組件
    import React, {Component} from 'react';
    import{View} from 'react-native';
    import InPutText from '../../common/InPutComponent';
    export default class LoginComponent extends Component {
        render() {
            return (
                <View>
                     {/*父組件中使用子組件*/}
                    <InPutText
                        title='賬號'
                        placeholder='請輸入賬號'
                        {/*通過props傳遞一個回調函數給子組件*/}
                        onChangeTextCallBack={(data,index) => {
                            console.log('index:' + index);
                        }}/>
                </View>   
    }

這樣當子組件輸入框中的內容發生改變後會通過回調函數將數據返回到父組件的回調函數中。

擴展

ES6 Promise異步機制與ES7 async/await機制

Promise介紹

Promise是什麼?

Promise是ES6中新增的特性。在以往 JavaScript 中,所有代碼都是單線程的,也就是同步執行的。而 Promise 就爲異步編程提供了一種解決方案。

基本用法

Promise 對象是由關鍵字 new 及其構造函數來創建的。

const promise = new Promise(((resolve, reject))=>{
    // do something here ...
    if (success) {
        resolve(value); // fulfilled成功
    } else {
        reject(error); // rejected失敗
    }
});

由上述代碼我們可知:

  • 該構造函數接收兩個函數作爲參數,分別是resolvereject
  • 當異步操作執行成功後,會將異步操作結果作爲參數傳入resolve函數並執行,此時 Promise對象狀態從pending變爲fulfilled
  • 失敗則會將異步操作的錯誤作爲參數傳入reject函數並執行,此時 Promise對象狀態從pending變爲rejected

接下來我們通過該對象的then方法,分別指定resolved狀態和rejected狀態的回調函數

promise.then(function(value) {
      // success
  }, function(error) {
      // failure
  });

//或者
promise.then((value) => {
     //success
}, (error)=> {
    //failure
});

then方法可以接收兩個回調函數作爲參數,第一個回調函數就是fulfilled成功狀態時調用;第二個回調函數就是rejected失敗時調用。這邊的第二個參數是可選的,不一定要提供。

Promise 主要API方法

  • Promise.all(iterable)

    • 參數
      iterable 必須是一個可迭代對象,如 Array 或 String。

    • 返回值
      一個新的Promise實例

    • Promise.all 的使用

      如果傳入的參數中存在不是Promise實例,則會先調用Promise.resolve,將其轉爲Promise實例,再進一步處理。

      var p1 = Promise.resolve(3);
      var p2 = 1337;    //p2不是Promise對象,會通過Promise.resolve轉換
      var p3 = new Promise((resolve, reject) => {
        setTimeout(resolve, 100, 'foo');
      }); 
      
      Promise.all([p1, p2, p3]).then(values => { 
        console.log(values); 
      });
      
      //  [3, 1337, "foo"] 
      
  • Promise.resolve(value)

    • 參數

      • 如果參數是一個Promise實例,則返回值是原封不動的返回該實例;

        //通過resolve指定一個字符串爲參數,返回一個Promise實例
        var original = Promise.resolve('我在第二行');
        
        //通過resolve指定一個Promise實例爲參數,會將original Promise實例原封不動的返回;
        var cast = Promise.resolve(original);
        
        //比較兩個Promise是否是同一個實例
        console.log('original === cast ? ' + (original === cast));
        
        cast.then((value)=>{
          console.log('value: ' + value);
        });
        
        // "original === cast ? true"
        // "value: 我在第二行"
        
      • 如果參數是普通數據:[String|Array|Object|Number],直接將傳入參數當最終結果並返回一個新的Promise

        //通過resolve指定Number返回Promise實例
        let p = Promsie.resolve(123);
        //通過then方法指定回調函數
        p.then((num)=>{
           console.log(num);
        });
        
        // 123
        
      • 如果不指定參數,直接返回一個resolved狀態的Promise對象

        //通過resovle不指定參數返回一個Promise實例
        let p = Promsie.resovle();
        
        //通過then方法回調函數接受的是無
        p.then(()=>{
          // do something here...
        })
        
  • Promise.reject(reason)

    • 參數:表示被拒絕的原因;

      傳入的參數會原封不動的作爲 reject 函數的理由,並不會因爲傳入的參數 Promise 或者是 thenable 對象而有所不同;

    • 返回值:一個含有reason的狀態爲rejectedPromise

async/await介紹

async/await是什麼?

async/await是ES7提出的一種異步解決方案,相比較Promise 對象then 函數的嵌套Async/Await 可以讓你輕鬆寫出同步風格的代碼同時又擁有異步機制,更加簡潔,邏輯更加清晰。

async/await特性

  • 自動將常規函數轉換成Promise,返回值也是一個Promise對象;
  • 只有async函數內部的異步操作執行完,纔會執行then方法指定的回調函數;
  • await只能在async函數內部使用,用在普通函數裏就會報錯;
  • 在async函數裏,無論是Promise reject的數據還是邏輯報錯,都會被默默吞掉,所以最好把await放入try{}catch{}中,catch能夠捕捉到Promise對象rejected的數據或者拋出的異常;

使用

  • 注意
    • await 表示在這裏等待promise返回結果了,再繼續執行。
    • await 後面跟着的應該是一個promise對象(當然,其他返回值也沒關係,只是會立即執行,不過那樣就沒有意義了…)
//定義一個使用async修飾的函數,返回時一個Promise對象
const fetchPostTest = async (url, params) => {
    console.log('url:' + url);
    console.log('params:' + JSON.stringify(params));
    //模擬請求返回數據(await後面跟着一個Promise對象,否則setTimeout無效會立即返回)
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('返回數據了');
        }, 5000);
    });
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章