%02d reactnative_reactnative 高度100% - CSDN
  • React Native BLE蓝牙通信

    2020-07-24 11:48:50
    由于项目需要,基于React Native 开发的App要跟BLE蓝牙设备通信。 在js.coach上搜索React Native BLE蓝牙组件,只找到三个组件: react-native-ble-manager:文档清晰明了,简单易懂,基本每个月都有更新,遇到...

    由于项目需要,基于React Native 开发的App要跟BLE蓝牙设备通信。
    js.coach上搜索React Native BLE蓝牙组件,只找到三个组件:

    1. react-native-ble-manager文档清晰明了,简单易懂,基本每个月都有更新,遇到问题提交issue作者也能及时回复,不确保能兼容所有BLE蓝牙设备。(本文使用这个库:demo源码地址)

    2. react-native-ble-plx文档阅读起来有点难度,但API很丰富,相比react-native-ble-manager 显的比较专业。(我也试用了这个库 : demo源码地址)

    3. react-native-ble:由Node.js BLE移植而成,而且久未更新,能不能适配最新React Native版本还是个问题,没有深入研究。

    综上分析,我当初选择的是react-native-ble-manager,组件的安装、配置看官方文档即可。
    PS:由于react-native-ble-manager更新比较频繁,本教程最初是基于3.2.1版本编写,由于版本跨度比较大,导致demo出现了一些问题,现已将代码和文章教程全部更新到6.2.4版本,如后面的版本没有及时更新适配,自己也可以根据官方文档作出相对应的更改,但通信原理和步骤是不变的。

    如有疑问,可在评论区留言,或者加我QQ(190525238)咨询讨论。

    友情提示:

    react-native-ble-manager只适用于BLE蓝牙通信,不适用于经典蓝牙通信,接入前请确保你的设备是BLE(低功耗)蓝牙,以免前功尽弃。android请使用BlueLoupe软件查看当前蓝牙是否是低功耗的,当有Bluetooth Low Energy 值时,表示当前蓝牙是BLE蓝牙。
    BlueLoupe
    ios平台可以下载LightBlue软件测试蓝牙连接和通信,但貌似无法区分BLE和经典蓝牙

    根据多数网友反馈总结:

    1. 如果连接的是蓝牙打印机(大部分为小票打印机)的话, 使用react-native-ble-managerreact-native-ble-plx,如果可以正常搜索连接的话,那通信应该是没问题的,但有可能会出现android可以连接而ios不行,或者ios可以连接而android不行,有可能是库不支持你的蓝牙设备,或者你的设备不是BLE蓝牙。
    2. 如果BLE蓝牙通信不行,那也可以尝试使用经典蓝牙通信react-native-bluetooth-serial实现打印。
    3. 如果你是用react-native-ble-plx实现小票打印机的话,如果某些蓝牙协议需要特殊的结束码才能打印的话,那就需要修改下源码才能正确识别结束标志[0x0D, 0x0A],修改后的源码地址

    数据通信

    实现蓝牙数据通信需要初始化、搜索、连接、获取Service和Characteristic、通知监听、读数据、写数据、解析蓝牙指令

    初始化

    import BleManager from 'react-native-ble-manager';
    const BleManagerModule = NativeModules.BleManager;
    const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);
    
    BleManager.start({showAlert: false})
      .then( ()=>{
    	   //检查蓝牙打开状态,初始化蓝牙后检查当前蓝牙有没有打开
           BleManager.checkState();
           console.log('Init the module success.');                
       }).catch(error=>{
           console.log('Init the module fail.');
       });
    

    添加相应的监听器

    //蓝牙状态改变监听
    bleManagerEmitter.addListener('BleManagerDidUpdateState', (args) => {
    	console.log('BleManagerDidUpdateStatea:', args);
    	if(args.state == 'on' ){  //蓝牙已打开
    		
    	}
    });
    

    搜索

    第一次使用react-native-ble-manager这个组件时,发现搜索不到附近的蓝牙设备(手机,电脑),于是就向作者提交了一个issue,问题是:Can’t discovery to some devices,然后才明白该组件只能搜索到标准的BLE蓝牙设备。

    蓝牙4.0标准包含两个蓝牙标准,准确的说,是一个双模的标准,它包含传统蓝牙部分(也有称之为经典蓝牙Classic Bluetooth)和低功耗蓝牙部分(Bluetooth Low Energy)。
    经典蓝牙可以用数据量比较大的传输,如:图像、视频、音乐等。低功耗蓝牙的数据传输用于实时性要求比较高但数据速率比较低的产品,如智能穿戴设备、遥控类的,鼠标,键盘,遥控鼠标(Air Mouse),还有传感设备的数据发送,如心跳带,血压计,温度传感器等等、其应用的行业和方向也比较广泛。
    详情可查看:什么是蓝牙双模标准

    所以,即使是蓝牙4.0的设备,例如手机蓝牙、电脑蓝牙,使用该组件也搜索不到蓝牙,要看它是不是低功耗标准的蓝牙。
    在android平台下,可以直接搜索到蓝牙设备的Mac地址,而ios需要通过广播0x18才能获取得到蓝牙的Mac地址(需要修改蓝牙固件将Mac地址加入到广播中,普通蓝牙设备一般没有)。

    注意:从Android 6.0之后,想要扫描低功率蓝牙设备,应用需要拥有访问设备位置的权限。这是因为Bluetooth beacons蓝牙信标,可用于确定手机和用户的位置。此外,在申请位置权限后,还需要打开定位服务(GPS)才能扫描到BLE设备。在小米手机上,如果没有在代码中手动申请定位权限,需要在应用权限管理中将定位改为允许才可以。Android这样的做法无疑保护了用户的安全,但也给开发者带来了小小的麻烦。

    //扫描可用设备,5秒后结束 
    BleManager.scan([], 5, true)
    	.then(() => {
    		console.log('Scan started');
    	})
    	.catch( (err)=>{
            console.log('Scan started fail');
        });
      
    //停止扫描
    BleManager.stopScan()
        .then(() => {
    	    console.log('Scan stopped');
        })
        .catch( (err)=>{
    		console.log('Scan stopped fail',err);
        });
    

    添加相应的监听器

    //搜索到一个新设备监听
    bleManagerEmitter.addListener('BleManagerDiscoverPeripheral', (data) => {
    	console.log('BleManagerDiscoverPeripheral:', data);
    	let id;  //蓝牙连接id
    	let macAddress;  //蓝牙Mac地址            
    	if(Platform.OS == 'android'){
    	    macAddress = data.id;
    	    id = macAddress;
    	}else{  
    	    //ios连接时不需要用到Mac地址,但跨平台识别是否是同一设备时需要Mac地址
            //如果广播携带有Mac地址,ios可通过广播0x18获取蓝牙Mac地址,
    	    macAddress = getMacAddressFromIOS(data);
    	    id = data.id;
        }            
    });
    
    //搜索结束监听
    bleManagerEmitter.addListener('BleManagerStopScan', () => {
    	 console.log('BleManagerStopScan:','Scanning is stopped');		
        //搜索结束后,获取搜索到的蓝牙设备列表,如监听了BleManagerDiscoverPeripheral,可省去这个步骤
        BleManager.getDiscoveredPeripherals([])
           .then((peripheralsArray) => {
               console.log('Discovered peripherals: ', peripheralsArray);
           });
    });
    
    /** ios系统从蓝牙广播信息中获取蓝牙MAC地址 */
    getMacAddressFromIOS(data){
    	let macAddressInAdvertising = data.advertising.kCBAdvDataManufacturerMacAddress;
    	//为undefined代表此蓝牙广播信息里不包括Mac地址
    	if(!macAddressInAdvertising){  
            return;
        }
    	macAddressInAdvertising = macAddressInAdvertising.replace("<","").replace(">","").replace(" ","");
    	if(macAddressInAdvertising != undefined && macAddressInAdvertising != null && macAddressInAdvertising != '') {
    	macAddressInAdvertising = swapEndianWithColon(macAddressInAdvertising);
        }
        return macAddressInAdvertising;
    }
    
    /**
    * ios从广播中获取的mac地址进行大小端格式互换,并加上冒号:
    * @param str         010000CAEA80
    * @returns string    80:EA:CA:00:00:01
    */
    swapEndianWithColon(str){
    	let format = '';
    	let len = str.length;
    	for(let j = 2; j <= len; j = j + 2){
    		format += str.substring(len-j, len-(j-2));
    		if(j != len) {
    			format += ":";
    		}
    	}
        return format.toUpperCase();
    }
    

    连接

    android使用Mac地址与蓝牙连接,ios使用UUID与蓝牙连接。

    //连接蓝牙
    BleManager.connect(id)
       .then(() => {
    	   console.log('Connected');
       })
       .catch((error) => {
    	   console.log('Connected error:',error);
       });
       
    //断开蓝牙连接
    BleManager.disconnect(id)
        .then( () => {
    	    console.log('Disconnected');
        })
        .catch( (error) => {
    	    console.log('Disconnected error:',error);
        });
    

    添加相应的监听器

    //蓝牙设备已连接监听
    bleManagerEmitter.addListener('BleManagerConnectPeripheral', (args) => {
    	log('BleManagerConnectPeripheral:', args);
    });
             
    //蓝牙设备已断开连接监听
    bleManagerEmitter.addListener('BleManagerDisconnectPeripheral', (args) => {
    	console.log('BleManagerDisconnectPeripheral:', args);
    });
    

    蓝牙连接后会显示该设备的具体信息,android平台下连接成功后返回的数据如下:

    { characteristics:
      [ { properties: { Read: 'Read' },
           characteristic: '2a00',
           service: '1800' },
         { properties: { Read: 'Read' },
           characteristic: '2a01',
           service: '1800' },
         { properties: { Write: 'Write', Read: 'Read' },
           characteristic: '2a02',
           service: '1800' },
         { properties: { Read: 'Read' },
           characteristic: '2a04',
           service: '1800' },
         { descriptors: [ { value: null, uuid: '2902' } ],
           properties: { Indicate: 'Indicate', Read: 'Read' },
           characteristic: '2a05',
           service: '1801' },
         { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],
           properties: { Notify: 'Notify' },
           characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb8',
           service: '0783b03e-8535-b5a0-7140-a304d2495cb7' },
         { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],
           properties: { WriteWithoutResponse: 'WriteWithoutResponse' },
           characteristic: '0783b03e-8535-b5a0-7140-a304d2495cba',
           service: '0783b03e-8535-b5a0-7140-a304d2495cb7' },
          { descriptors: [ { value: null, uuid: '2902' }, { value: null, uuid: '2901' } ],
            properties:
            { Notify: 'Notify',
               WriteWithoutResponse: 'WriteWithoutResponse',
               Read: 'Read' },
            characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb9',
            service: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],
      services:
      [ { uuid: '1800' },
        { uuid: '1801' },
        { uuid: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],
      rssi: -46,
      advertising:{ data: 'AgEGEQe3XEnSBKNAcaC1NYU+sIMHCQlQRVAtUEVOLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',CDVType: 'ArrayBuffer' },
      id: '00:CD:FF:00:22:2D',
      name: 'PEP-HC001' 
    }
    

    ios平台下连接成功后返回的数据如下:

    { name: 'PEP-HC001',
      id: '64319987-E97B-46C0-91AE-261E93EADBFD',
      advertising: 
       { kCBAdvDataLocalName: 'PEP-HC001',
         kCBAdvDataIsConnectable: true,
         kCBAdvDataServiceUUIDs: [ '0783' ],
         kCBAdvDataManufacturerMacAddress: '<472200ff cd00>',
         kCBAdvDataManufacturerData: { CDVType: 'ArrayBuffer', data: 'RyIA/80A' } },
      services: [ '0783B03E-8535-B5A0-7140-A304D2495CB7' ],
      characteristics: 
       [ { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',
           isNotifying: false,
           characteristic: '0783B03E-8535-B5A0-7140-A304D2495CB8',
           properties: [ 'Notify' ] },
         { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',
           isNotifying: false,
           characteristic: '0783B03E-8535-B5A0-7140-A304D2495CBA',
           properties: [ 'WriteWithoutResponse' ] },
         { service: '0783B03E-8535-B5A0-7140-A304D2495CB7',
           isNotifying: false,
           characteristic: '0783B03E-8535-B5A0-7140-A304D2495CB9',
           properties: [ 'Read', 'WriteWithoutResponse', 'Notify' ] } ],
      rssi: -35 }
    

    获取Service和Characteristic

    BLE分为三部分Service(服务)、Characteristic(特征)、Descriptor(描述符),这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有跟权限相关的字段,如Property,Property有读写等各种属性,如Notify、Read、Write、WriteWithoutResponse。(引自:Android BLE开发之Android手机与BLE终端通信)

    Service

    一个低功耗蓝牙设备可以定义多个Service, Service可以理解为一个功能的集合。设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
    0x0000xxxx-0000-1000-8000-00805F9B34FB
    为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的"x"部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
    0x00002A37-0000-1000-8000-00805F9B34FB(引自:Android BLE 蓝牙开发入门

    注意:除了标准的UUID,蓝牙硬件工程师也可以自定义一些标准之外的UUID以满足一些特殊通信要求,要是你的蓝牙使用的是自定义的UUID,那写数据的时候得传128位的UUID才能正常通信,要是你用的是标准的UUID通信,那写数据的时候传16位UUID就可以了。

    Characteristic

    在 Service 下面,又包括了许多的独立数据项,我们把这些独立的数据项称作 Characteristic。同样的,每一个 Characteristic 也有一个唯一的 UUID 作为标识符。建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。(引自:Android BLE 蓝牙开发入门

    蓝牙连接成功后,需要调用retrieveServices方法获取NotifyReadWriteserviceUUIDcharacteristicUUID作为参数来跟蓝牙进一步通信

    //获取蓝牙Service和Characteristics
    BleManager.retrieveServices(peripheralId)
        .then((peripheralInfo) => {
    	    this.getUUID();
            console.log('Peripheral info:', peripheralInfo);
        });  
    

    peripheralInfo下的characteristics字段值是一个特征数组,每一项代表一个特征通道,找到properties中包含有NotifyReadWriteWriteWithoutResponse属性的那一项,其servicecharacteristic即是我们需要的参数。
    PS:serviceUUIDcharacteristicUUID标准格式为XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX的128bit的UUID。所以需要将获到的’XXXX’格式的UUID转换为标准的128bit的UUID格式才可能进行通信。
    不同的蓝牙设备,可能有多个特征通道包含NotifyReadWriteWriteWithoutResponse属性值,那每个通道属性的功能可能会不一样,应根据具体的蓝牙设备选择符合我们要求的特征通道。有些可能不包含NotifyReadWriteWriteWithoutResponse中的一个或多个属性,具体跟蓝牙硬件有关系,一般有NotifyWrite两个属性就可以满足通信的要求了。

    //获取Notify、Read、Write、WriteWithoutResponse的serviceUUID和characteristicUUID
    getUUID(peripheralInfo){       
        this.readServiceUUID = [];
        this.readCharacteristicUUID = [];   
        this.writeWithResponseServiceUUID = [];
        this.writeWithResponseCharacteristicUUID = [];
        this.writeWithoutResponseServiceUUID = [];
        this.writeWithoutResponseCharacteristicUUID = [];
        this.nofityServiceUUID = [];
        this.nofityCharacteristicUUID = [];  
    	for(let item of peripheralInfo.characteristics){  
    		//请根据具体需要是否转成128位的,ios有些设备需要传16位的才能正常通信
    		//item.service = this.fullUUID(item.service);
    	    item.characteristic = this.fullUUID(item.characteristic); 
            if(Platform.OS == 'android'){  
                 if(item.properties.Notify == 'Notify'){	
    		         this.nofityServiceUUID.push(item.service);     
    		         this.nofityCharacteristicUUID.push(item.characteristic);
                 }
                 if(item.properties.Read == 'Read'){
                     this.readServiceUUID.push(item.service);
                     this.readCharacteristicUUID.push(item.characteristic);
                 }
                 if(item.properties.Write == 'Write'){
                     this.writeWithResponseServiceUUID.push(item.service);
                     this.writeWithResponseCharacteristicUUID.push(item.characteristic);
                 }
                 if(item.properties.Write == 'WriteWithoutResponse'){
                     this.writeWithoutResponseServiceUUID.push(item.service);
                     this.writeWithoutResponseCharacteristicUUID.push(item.characteristic);
                 }	             
             }else{  //ios
                 for(let property of item.properties){
                     if(property == 'Notify'){
                         this.nofityServiceUUID.push(item.service);
                         this.nofityCharacteristicUUID.push(item.characteristic);
                     }
                     if(property == 'Read'){
                         this.readServiceUUID.push(item.service);
                         this.readCharacteristicUUID.push(item.characteristic);
                     }
                     if(property == 'Write'){
                         this.writeWithResponseServiceUUID.push(item.service);
                         this.writeWithResponseCharacteristicUUID.push(item.characteristic);
                     }
                     if(property == 'WriteWithoutResponse'){
                         this.writeWithoutResponseServiceUUID.push(item.service);
                         this.writeWithoutResponseCharacteristicUUID.push(item.characteristic);
                     }	                
                 }
             }
    	 }
    }
    
    /**
     * 请根据具体需要是否转成128位的,ios有些设备需要传16位的才能正常通信
     * Converts UUID to full 128bit.
     * 
     * @param {UUID} uuid 16bit, 32bit or 128bit UUID.
     * @returns {UUID} 128bit UUID.
     */
     fullUUID(uuid) {
         if (uuid.length === 4){
             return '0000' + uuid.toUpperCase() + '-0000-1000-8000-00805F9B34FB'
          }             
         if (uuid.length === 8) {
             return uuid.toUpperCase() + '-0000-1000-8000-00805F9B34FB'
          }            
          return uuid.toUpperCase()
      }  
    

    注意:

    • 如果android和ios使用的是同一个ServiceUUID和CharacteristicUUID,有可能会导致ios通信正常,android通信失败(或者android通信正常,ios通信失败),这时,就需要针对ios和android传入不同的UUID,这样才能正常通信;
    • 也可能是另一种情况,ios需要传入16位的UUID(如FEE0FEE1等),而android需要传入128位的UUID(如0000FEE0-0000-1000-8000-00805F9B34FB);
    • 总之,android和ios有一方通信失败,大多是传入UUID的问题。

    通知监听

    蓝牙连接成功,当我们向设备写入数据成功并且指令也正确的话,我们就会得到设备通过蓝牙发送给APP的响应数据,实现这一响应的前提是需要开启通知监听,这样就能在回调中监听到数据返回了。

    //打开通知
    BleManager.startNotification(peripheralId, this.nofityServiceUUID[0], this.nofityCharacteristicUUID[0])
        .then(() => {
    	    console.log('Notification started');
        })
        .catch((error) => {
    	    console.log('Notification error:',error);
        });
      
    //关闭通知
    BleManager.stopNotification(peripheralId, this.nofityServiceUUID[0], this.nofityCharacteristicUUID[0])
       .then(() => {
            console.log('stopNotification success!');
        })
        .catch((error) => {
            console.log('stopNotification error:',error);
        });
        
    
    

    读数据

    通过read读取的数据,一般是固定的数据,如设备名,固件版本等等,返回的数据是byte数组,需要转换

    //读取蓝牙数据
    BleManager.read(peripheralId, this.readServiceUUID[0], this.readCharacteristicUUID[0])
         .then((data) => {
         	const str = byteToString(data);
            console.log('Read: ', str);                    
         })
         .catch((error) => {
             console.log(error);
         });
    
    // byte数组转换成字符串
     byteToString(arr) {  
         if(typeof arr === 'string') {  
             return arr;  
         }  
         var str = '',  
             _arr = arr;  
         for(var i = 0; i < _arr.length; i++) {  
             var one = _arr[i].toString(2),  
                 v = one.match(/^1+?(?=0)/);  
             if(v && one.length == 8) {  
                 var bytesLength = v[0].length;  
                 var store = _arr[i].toString(2).slice(7 - bytesLength);  
                 for(var st = 1; st < bytesLength; st++) {  
                     store += _arr[st + i].toString(2).slice(2);  
                 }  
                 str += String.fromCharCode(parseInt(store, 2));  
                 i += bytesLength - 1;  
             } else {  
                 str += String.fromCharCode(_arr[i]);  
             }  
         }  
         return str;  
     }  
    

    写数据

    写数据有两个方法,分别为
    write:Write with response to the specified characteristic
    writeWithoutResponse:Write without response to the specified characteristic
    具体选用哪个方法需要看characteristics下的properties支持哪种写入方法。
    如下面蓝牙连接返回的数据所示:

    • 如果你要在service1800characteristic2a02上写指令就只能用 write方法;
    • 如果你要在service0783b03e-8535-b5a0-7140-a304d2495cb7characteristic0783b03e-8535-b5a0-7140-a304d2495cb9上写指令就只能用 writeWithoutResponse方法;
    • service1800characteristic2a00上是没有写属性的,所以调用哪个方法都会写入失败;
    • 同样硬件规格的蓝牙,servicecharacteristic的属性都是固定的,有些硬件设备商也会直接告诉你具体使用哪个servicecharacteristic来进行蓝牙通信。
    { characteristics:
      [ { properties: { Read: 'Read' },
           characteristic: '2a00',
           service: '1800' }, 
         { properties: { Write: 'Write', Read: 'Read' },
           characteristic: '2a02',
           service: '1800' },     
         { properties: { 
    	     Notify: 'Notify', 
    	     Read: 'Read', 
             WriteWithoutResponse: 'WriteWithoutResponse' 
           },
           characteristic: '0783b03e-8535-b5a0-7140-a304d2495cb9',
           service: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],
      services:
      [ { uuid: '1800' },
        { uuid: '1801' },
        { uuid: '0783b03e-8535-b5a0-7140-a304d2495cb7' } ],
      rssi: -46,
      advertising:{ data: 'AgEGEQe3XEnSBKNAcaC1NYU+sIMHCQlQRVAtUEVOLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',CDVType: 'ArrayBuffer' },
      id: '00:CD:FF:00:22:2D',
      name: 'PEP-HC001' 
    }
    

    写数据注意事项:

    BLE蓝牙传输速率比经典蓝牙慢的多,而且GATT底层需要封装7个字节的额外协议数据, 即一次最多只能传输20字节,所以一般选用16进制数据来提高单次数据传输量。而且如果发送的数据大于20字节的话要分包发送,例如要发送30个字节,可以先write(前20个字节),等这次写入成功后(或者开启线程sleep几十毫秒后),再write(后面10个字节)。

    发送的时候需要先将其装载到byte[]数组中,例如要发送0xFE 0xFD 0x01 0x0A 0xFC 0xFB这个指令,需要把它转化为
    new byte[] { (byte) 0xFE, (byte) 0xFD, (byte) 0x01, (byte) 0x0A ,(byte) 0xFC, (byte) 0xFB }
    这样去发送。

    这是官方最新的例子

    import { stringToBytes } from 'convert-string';
    //发送给蓝牙的指令
    let command = 'FEFD010AFCFB'; 
    //将字符串转换成字节数组传送,stringToByte方法将每个16进制的字符转换成指定位置的字符的 Unicode编码,这个返回值是 0 - 65535 之间的整数
    let bytes = stringToBytes(command);  
    // 转换后为:[ 70, 69, 70, 68, 48, 49, 48, 65, 70, 67, 70, 66 ]
    
    

    5.0.1之前的版本写数据是需要经过base64编码转换后发送的,5.0.1之后的版本虽然能发送byte[],但却是通过stringToBytes将其转化为Unicode编码位置的byte[],然而蓝牙那边只能接收16进制的byte[]数据。带着这个疑问,我提了一个issue给作者,问题是:Can I send hexadecimal data to BLE instead of base64 format?然而作者却没有给我一个满意的解决办法。所以,自己动手对源码做一些小小的修改以符合我们的实际需求吧。

    android源码修改

    修改的源文件只有一个:在react-native-ble-manager\android\src\main\java\it\innove目录下的BleManager.java文件。
    点击跳转到修改后的BleManager.java文件

    BleManager.java文件

    • 增加的方法:
    /** 16进制字符串转换成16进制byte数组,每两位转换 */
    public static byte[] strToHexByteArray(String str){
    	byte[] hexByte = new byte[str.length()/2];
    	for(int i = 0,j = 0; i < str.length(); i = i + 2,j++){
    		hexByte[j] = (byte)Integer.parseInt(str.substring(i,i+2), 16);
    	}
    	return hexByte;
    }
    
    • 修改write方法:
      write android
    @ReactMethod
    public void write(String deviceUUID, String serviceUUID, String characteristicUUID, String message, Integer maxByteSize, Callback callback) {
    	Log.d(LOG_TAG, "Write to: " + deviceUUID);
    
    	Peripheral peripheral = peripherals.get(deviceUUID);
    	if (peripheral != null){
    		// byte[] decoded = new byte[message.size()];
    		// for (int i = 0; i < message.size(); i++) {
    		// 	decoded[i] = new Integer(message.getInt(i)).byteValue();
    		// 	Log.d(LOG_TAG, "decoded: " + decoded[i]);
    		// }
    		// Log.d(LOG_TAG, "Message(" + decoded.length + "): " + bytesToHex(decoded));
    
    		//message由原来的ReadableArray类型改为String类型,再将16进制字符串转化成16进制byte[]数组
    		byte [] decoded = strToHexByteArray(message);
    		Log.d(LOG_TAG, "decoded: " + Arrays.toString(decoded));
    
    		peripheral.write(UUIDHelper.uuidFromString(serviceUUID), UUIDHelper.uuidFromString(characteristicUUID), decoded, maxByteSize, null, callback, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
    	} else
    		callback.invoke("Peripheral not found");
    }
    
    • 修改writeWithoutResponse方法(修改的地方同write方法一样):
      android writeWithoutResponse
    @ReactMethod
    public void writeWithoutResponse(String deviceUUID, String serviceUUID, String characteristicUUID, String message, Integer maxByteSize, Integer queueSleepTime, Callback callback) {
    	Log.d(LOG_TAG, "Write without response to: " + deviceUUID);
    
    	Peripheral peripheral = peripherals.get(deviceUUID);
    	if (peripheral != null){
    		// byte[] decoded = new byte[message.size()];
    		// for (int i = 0; i < message.size(); i++) {
    		// 	decoded[i] = new Integer(message.getInt(i)).byteValue();
    		// }
    		// Log.d(LOG_TAG, "Message(" + decoded.length + "): " + bytesToHex(decoded));
    		
    		//message由原来的ReadableArray类型改为String类型,再将16进制字符串转化成16进制byte[]数组
    		byte [] decoded = strToHexByteArray(message);
    		Log.d(LOG_TAG, "decoded: " + Arrays.toString(decoded));
    		peripheral.write(UUIDHelper.uuidFromString(serviceUUID), UUIDHelper.uuidFromString(characteristicUUID), decoded, maxByteSize, queueSleepTime, callback, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
    	} else
    		callback.invoke("Peripheral not found");
    }
    

    ios源码修改

    看了下Object-C的语法,参考了iOS蓝牙中的进制转换,总算完成了ios的源码修改。

    修改的源文件只有一个:在react-native-ble-manager/ios目录下的BleManager.m文件。
    点击跳转到修改后的BleManager.m文件

    BleManager.m文件

    • 增加的方法:
    //16进制字符串转换成16进制byte数组,每两位转换
    - (NSData *)hexToBytes:(NSString *)str{
        NSMutableData* data = [NSMutableData data];
        int idx;
        for (idx = 0; idx+2 <= str.length; idx+=2) {
            NSRange range = NSMakeRange(idx, 2);
            NSString* hexStr = [str substringWithRange:range];
            NSScanner* scanner = [NSScanner scannerWithString:hexStr];
            unsigned int intValue;
            [scanner scanHexInt:&intValue];
            [data appendBytes:&intValue length:1];
        }
        return data;
    }
    
    • 修改write方法(方法太长,只截修改的部分):
      ios write
    • 修改writeWithoutResponse方法(修改的地方同write方法一样):
      ios writeWithoutResponse

    修改源码后写数据示例

    修改后直接发送16进制字符串就可以进行通信了。
    例如要发送0xFE 0xFD 0x01 0x0A 0xFC 0xFB这个指令,需要把它转化为FEFD010AFCFB这样去发送。

    //发送给蓝牙的数据
    let data = 'FEFD010AFCFB'; 
    //写数据到蓝牙
    BleManager.write(peripheralId, this.writeWithResponseServiceUUID[0], this.writeWithResponseCharacteristicUUID[0], data)
    	.then(() => {
    		console.log('write success: ',data);
    	})
    	.catch((error) => {
    		console.log('write  failed: ',data);
    	});
    	
    //写数据到蓝牙,没有响应 	
    BleManager.writeWithoutResponse(peripheralId, this.writeWithoutResponseServiceUUID[0], this.writeWithoutResponseCharacteristicUUID[0], data)
    	.then(() => {
    		console.log('writeWithoutResponse success: ',data);
    	})
    	.catch((error) => {
    		console.log('writeWithoutResponse  failed: ',data);
    	});
    

    我fork了一个分支,并将源码修改的版本上传到了github,如有需要,可直接安装我修改后的版本

    • yarn安装
      yarn add https://github.com/zhanguangao/react-native-ble-manager.git

    • npm安装
      npm install git+https://git@github.com/zhanguangao/react-native-ble-manager.git

    解析蓝牙指令

    当通知成功开启后,就可以添加相应的监听器,并对接收到的数据进行解析

    const BleProtocol = new BleProtocol();
    //接收到新数据监听,开启通知成功后,该监听才可接收到数据
    bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', (data) => {
        //ios接收到的是小写的16进制,android接收的是大写的16进制,统一转化为大写16进制
        let value = data.value.toUpperCase();				
    	console.log('BluetoothUpdateValue', value);
    	BluetoothProtocol.parseData(value);
    });
    
    const BLE_HEAD = "FEFD";
    const BLE_TRAIL = "FCFB";
    
    /**
     * 2个16进制字符串代表8位二进制即 1 byte的数据,即FE = 11111110
     * 包数据长度 = 包命令 + Data的byte长度,1个
     * 返回数据:FEFD048D010203FCFB(16进制)
     * 分段解析:包头(FEFD) + 包数据长度(04) + 包命令(8D) + Data(010203) + 包尾(FCFB) *
     */
    export default class BleProtocol {
      constructor() {
        this.trailStatus = true; //接收到包头后,接收包尾的状态,一个完整数据包的接收状态
        this.receiveData = []; //接收蓝牙数据缓存
      }
    
      /** 解析蓝牙返回的数据 */
      parseData(data) {
        this.receiveData.push(data);
        let packet = this.receiveData.join(""); //将数组转换为字符串解析
        let command; //包命令
    
        if (isHead(packet)) {
          this.trailStatus = false; //接收到包头,等待接收包尾
          this.packetLen = this.getPacketByteLen(packet); //包数据长度
        }
    
        //接收到包尾
        if (isTrail(packet)) {
          //校验数据长度:包数据长度 = 实际接收到的包长度
          if (this.packetLen === this.getDataByteLen(packet)) {
            this.trailStatus = true; //接收到包尾
            command = this.getResponseCommand(packet);
            this.receiveData = []; //一个包接收完毕后,清空接收到的蓝牙数据缓存
          }
        }
    
        this.receivedDelayTimer && clearTimeout(this.receivedDelayTimer);
        //接收到包头后,如果300ms还没收到包尾的话,就丢掉这一不完整的包数据,
        // 一般100ms足够,但某些情况可能会大于100ms,为确保准备接收,这里设置300ms
        this.receivedDelayTimer = setTimeout(() => {
          if (!this.trailStatus) {
            this.receiveData = [];
          }
        }, 300);
    
        //一个数据包接收完毕前不进行数据处理
        if (!this.trailStatus) return;
        this.trailStatus = false;
    
        // 根据具体的包命令进行相应的操作
        if (command == "8D") {
        }
      }
    
      /***
       * 判断返回的数据是否包含一个完整数据的包头
       * 这里假设蓝牙协议定义的包头为FEFD
       */
      isHead(str) {
        return str.substring(0, 4) == BLE_HEAD;
      }
    
      /***
       * 判断返回的数据是否包含一个完整数据的包尾
       * 这里假设蓝牙协议定义的包头为FCFB
       */
      isTrail(str) {
        const len = str.length;
        return str.substring(len - 4, len) == BLE_TRAIL;
      }
    
      /**
       * 获取返回数据的包命令
       */
      getResponseCommand(str) {
        return str.substring(6, 8);
      }
    
      /**
       * 返回一个数据包的包数据长度,不包含包头和包尾
       */
      getPacketByteLen(str) {
        let hexLen = str.substring(4, 6);
        return parseInt(hexLen, 16);
      }
    
      /**
       * Data实际的Byte长度
       * 2个16进制字符串表示1 Byte
       */
      getDataByteLen(str) {
        return str.substring(6, str.length - 4).length / 2;
      }
      /** 在字符串前面添加 0, 默认补充为2位*/
      addZero(str, bit = 2) {
        for (let i = str.length; i < bit; i++) {
          str = "0" + str;
        }
        return str;
      }
    }
    
    

    demo

    源码地址:适配android和ios平台

    PS:由于海马玩模拟器的android版本是4.2.2,而react-native-ble-manager支持的android最低版本为API 19,即4.4.2,所以用海马玩模拟器打开使用这个组件的App会闪退,如果需要使用模拟器调试,建议使用Android官方模拟器、Genymotion、夜神模拟器等等,不过这个组件涉及到蓝牙硬件,测试蓝牙功能只能使用真机。

    截图(ios)

    scan


    connect

    展开全文
  • React Native学习资料

    2018-08-23 02:06:23
    官网: React 官方网站:https://reactjs.org/ React Github网址:...React Native官方网站:https://facebook.github.io/react-native/ React Native Github网址:https://github.com/facebook/...

    官网:

    React 官方网站:https://reactjs.org/

    React Github网址:https://github.com/facebook/react

     

    React Native官方网站:https://facebook.github.io/react-native/

    React Native Github网址:https://github.com/facebook/react-native

    简书:

    React-Native 开发阵营https://www.jianshu.com/c/b4ce1d706d1f

    从零开始RNhttps://www.jianshu.com/c/5f409708aca0

    CSDN:

    ReactNative开发:https://blog.csdn.net/column/details/reactnative-android.html?&page=1

    Github:

    React Native开源项目:https://github.com/MarnoDev/react-native-open-project

     

    以下转载自:https://www.jianshu.com/p/d78696e9db3f

     

    构建 Facebook F8 2016 App / React Native 开发指南http://f8-app.liaohuqiu.net/

    React-Native入门指南https://github.com/vczero/react-native-lesson

    30天学习React Native教程https://github.com/fangwei716/30-days-of-react-native

    React-Native视频教程(部分免费)https://egghead.io/technologies/react

    react-native 官方api文档http://facebook.github.io/react-native/docs/getting-started.html

    react-native中文文档(极客学院)http://wiki.jikexueyuan.com/project/react-native/

    react-native中文文档(react native中文网,人工翻译,官网完全同步)http://react-native.cn/docs/getting-started.html

    react-native第一课http://html-js.com/article/2783

    深入浅出 React Native:使用 JavaScript 构建原生应用http://zhuanlan.zhihu.com/FrontendMagazine/19996445

    React Native通信机制详解http://blog.cnbang.net/tech/2698/

    React Native布局篇https://segmentfault.com/a/1190000002658374

    React Native 基础练习指北(一)https://segmentfault.com/a/1190000002645929

    React Native 基础练习指北(二)https://segmentfault.com/a/1190000002647733

    Diary of Building an iOS App with React Nativehttp://herman.asia/building-a-flashcard-app-with-react-native

    Use React Native in Existing iOS Apphttp://blog-en.leapoahead.com/post/use-react-native-in-existing-ios-app

    React Native For Beginners – The Next Big Thing?https://devdactic.com/react-native-for-beginners/

    How To Implement A Tab Bar With React Nativehttps://devdactic.com/react-native-tab-bar/

    tcomb-form-native使用视频教程(需翻墙)https://react.rocks/example/tcomb-form-native

    React Native分享记录https://segmentfault.com/a/1190000002678782

    React Native构建本地视图组件https://www.dobest.me/article/11

    react-native-android-lession(安卓系列教程)https://github.com/yipengmu/react-native-android-lession

    React Native模块桥接详解https://www.dobest.me/article/14

    React Native: 配置和起步http://www.liaohuqiu.net/cn/posts/react-native-1/

    React Native: Android 的打包http://www.liaohuqiu.net/cn/posts/react-native-android-package/

    ReactNative之原生模块开发并发布——iOS篇http://www.liuchungui.com/blog/2016/05/02/reactnativezhi-yuan-sheng-mo-kuai-kai-fa-bing-fa-bu-iospian/

    ReactNative之原生模块开发并发布——android篇http://www.liuchungui.com/blog/2016/05/08/reactnativezhi-yuan-sheng-mo-kuai-kai-fa-bing-fa-bu-androidpian/

    react-native的第一课https://github.com/coderyi/blog/blob/master/articles/2016/0122_react-native_first_lesson.md

    React-Native专题系列文章http://www.lcode.org/react-native/

    React.js

    react.js中文文档http://reactjs.cn/

    react.js入门教程(gitbook)https://hulufei.gitbooks.io/react-tutorial/content/introduction.html

    react.js快速入门教程 - 阮一峰http://www.ruanyifeng.com/blog/2015/03/react.html

    react.js视频教程http://react-china.org/t/reactjs/584

    ES6

    深入浅出ES6(一):ES6是什么http://www.infoq.com/cn/articles/es6-in-depth-an-introduction

    深入浅出ES6(二):迭代器和for-of循环http://www.infoq.com/cn/articles/es6-in-depth-iterators-and-the-for-of-loop

    深入浅出ES6(三):生成器 Generatorshttp://www.infoq.com/cn/articles/es6-in-depth-generators

    深入浅出ES6(四):模板字符串http://www.infoq.com/cn/articles/es6-in-depth-template-string

    深入浅出ES6(五):不定参数和默认参数http://www.infoq.com/cn/articles/es6-in-depth-rest-parameters-and-defaults

    系列教程

    深入浅出React(一):React的设计哲学 - 简单之美http://www.infoq.com/cn/articles/react-art-of-simplity

    深入浅出React(二):React开发神器Webpackhttp://www.infoq.com/cn/articles/react-and-webpack

    深入浅出React(三):理解JSX和组件http://www.infoq.com/cn/articles/react-jsx-and-component

    深入浅出React(四):虚拟DOM Diff算法解析http://www.infoq.com/cn/articles/react-dom-diff

    深入浅出React(五):使用Flux搭建React应用程序架构http://www.infoq.com/cn/articles/react-flux

    react-webpack-cookbook中文版http://fakefish.github.io/react-webpack-cookbook/

    Flex 布局语法教程http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

    React 初探http://www.alloyteam.com/2015/04/react-explore/

    React虚拟DOM浅析http://www.alloyteam.com/2015/10/react-virtual-analysis-of-the-dom/

    react组件间通信http://www.alloyteam.com/2015/07/react-zu-jian-jian-tong-xin/

    React 数据流管理架构之 Redux 介绍http://www.alloyteam.com/2015/09/react-redux/

    React服务器端渲染实践小结http://www.alloyteam.com/2015/10/8783/

    React Native Android 踩坑之旅http://www.alloyteam.com/2015/10/react-native-android-steps-on-tour/

    React Native 之 JSBridgehttp://www.alloyteam.com/2015/05/react-native-zhi-jsbridge/

    React Native探索系列教程

    React Native探索(一):背景、规划和风险http://www.infoq.com/cn/articles/react-native-overview

    React Native探索(二):布局篇http://www.infoq.com/cn/articles/react-native-layout

    React Native探索(三):与 react-web 的融合http://www.infoq.com/cn/articles/react-native-web

    开源APP

    研究源码也是一个很好的学习方式

    官方演示Apphttps://github.com/facebook/react-native/tree/master/Examples

    Facebook F8 Apphttps://github.com/fbsamples/f8app

    奇舞周刊 iOS 版(上架应用)https://github.com/fakefish/Weekly75

    react-native-dribbble-apphttps://github.com/catalinmiron/react-native-dribbble-app

    Gank.io客户端https://github.com/Bob1993/React-Native-Gank

    Leanote for iOS(云笔记)https://github.com/leanote/leanote-ios-rn

    ReactNativeRubyChinahttps://github.com/henter/ReactNativeRubyChina

    HackerNews-React-Nativehttps://github.com/iSimar/HackerNews-React-Native

    React-Native新闻客户端https://github.com/tabalt/ReactNativeNews

    newswatch(新闻客户端)https://github.com/bradoyler/newswatch-react-native

    buyscreen(购买页面)https://github.com/appintheair/react-native-buyscreen

    V2EX客户端https://github.com/samuel1112/v2er

    react-native-todohttps://github.com/joemaddalone/react-native-todo

    react-native-beerhttps://github.com/muratsu/react-native-beer

    react-native-starshttps://github.com/86/react-native-stars

    模仿天猫首页的apphttps://github.com/hugohua/react-native-demo

    ReactNativeChesshttps://github.com/csarsam/ReactNativeChess

    react native 编写的音乐软件https://github.com/Johnqing/miumiu

    react-native-pokedexhttps://github.com/ababol/react-native-pokedex

    CNode-React-Nativehttps://github.com/SFantasy/CNode-React-Native

    8tracks电台客户端https://github.com/voronianski/EightTracksReactNative

    React-Native实现的计算器https://github.com/yoxisem544/Calculator-using-React-Native

    房产搜索apphttps://github.com/jawee/react-native-PropertyFinder

    知乎专栏apphttps://github.com/LeezQ/react-native-zhihu-app

    ForeignExchangeApphttps://github.com/peralmq/ForeignExchangeApp

    Segmentfault 客户端https://github.com/fakefish/sf-react-native

    糗事百科apphttps://github.com/stormhouse/QiuShiReactNative

    孢子社区apphttps://github.com/Hi-Rube/baoz-ReactNative

    深JS apphttps://github.com/fraserxu/shenjs

    Den - 房屋销售app*https://github.com/asamiller/den

    Noder-cnodejs客户端https://github.com/soliury/noder-react-native

    知乎日报Android版https://github.com/race604/ZhiHuDaily-React-Native

    ziliun-react-nativehttps://github.com/sonnylazuardi/ziliun-react-native

    react-native-weather-apphttps://github.com/shevawen/react-native-weather-app

    React Native Sample App(Navigation,Flux)https://github.com/taskrabbit/ReactNativeSampleApp

    TesterHome社区apphttps://github.com/qddegtya/A-ReactNative-TesterHome

    Finance - 股票报价apphttps://github.com/7kfpun/FinanceReactNative

    shopping - 购物apphttps://github.com/bigsui/shopping-react-native

    zhuiyuan - 追源cms apphttps://github.com/kazaff/ZhuiYuanDemo

    uestc-bbs-react-native - UESTC清水河畔RN客户端(with Redux)https://github.com/just4fun/uestc-bbs-react-native

    react-native-nw-react-calculator(iOS/Android、Web、桌面多端)https://github.com/benoitvallon/react-native-nw-react-calculator

    react-native-nba-apphttps://github.com/wwayne/react-native-nba-app

    开源中国的Git@OSC客户端http://git.oschina.net/rplees/react-native-gitosc

    rn_bycloud 帮瀛律师端apphttps://github.com/liuchungui/rn_bycloud

    ReactNativeRollingExamples - react-native的一些examplehttps://github.com/joggerplus/ReactNativeRollingExamples

    Reading App Write In React-Native(Studying and Programinghttps://github.com/attentiveness/reading

    图书

    《React Native入门与实战》http://item.jd.com/11844102.html

    《React Native开发指南》http://www.ituring.com.cn/book/1846

    《React Native跨平台移动应用开发》http://item.jd.com/10372998311.html

    《React Native:用JavaScript开发移动应用》http://item.jd.com/11785195.html

    组件

    由于已经有较好的组件库网站,这里就不做总结。可以直接查看如下网站,过后可能精选一部分优质组件出来 :P

    React-native组件库(比较全的组件库)https://js.coach/

    React Native Moduleshttp://reactnativemodules.com/

    最佳轮播类组件https://github.com/leecade/react-native-swiper

    react-native-simple-routerhttps://github.com/react-native-simple-router-community/react-native-simple-router

    react-native-router-fluxhttps://github.com/aksonov/react-native-router-flux

    下拉刷新组件https://github.com/jsdf/react-native-refreshable-listview

    模态框https://github.com/brentvatne/react-native-modal

    react-native-navbarhttps://github.com/react-native-fellowship/react-native-navbar

    滚动轮播组件https://github.com/appintheair/react-native-looped-carousel

    HTML显示组件https://github.com/jsdf/react-native-htmlview

    Material React Native (MRN)- Material Design组件库https://github.com/binggg/mrn

    react-native-gitfeed - GitHub客户端(iOS/Android)https://github.com/xiekw2010/react-native-gitfeed

    工具

    react-native-snippets(代码提示)https://github.com/Shrugs/react-native-snippets

    react-native-babel(使用ES6+)https://github.com/roman01la/react-native-babel

    sqlite for react-nativehttps://github.com/almost/react-native-sqlite

    gulp-react-native-css(就像写css一样写React Style)https://github.com/soliury/gulp-react-native-css

    rnpm(React Native Package Manager)https://github.com/rnpm/rnpm

    Pepperoni - React Native项目初始化套件https://github.com/futurice/pepperoni-app-kit

    Deco IDE - React Native IDEhttps://www.decosoftware.com/

    ignite - React Native CLI项目生成器https://github.com/infinitered/ignite

    资源网站

    React-native官网http://facebook.github.io/react-native/

    React-China社区http://react-china.org/

    React Native中文社区http://bbs.react-native.cn/

    React-native组件库(比较全的组件库)http://react.parts/

    React Native Moduleshttp://reactnativemodules.com/

    Use React Native 资讯站(使用技巧及新闻)http://www.reactnative.com/

    11款React Native开源移动 UI 组件http://www.oschina.net/news/61214/11-react-native-ui-components

    稀土掘金的 React 标签http://gold.xitu.io/#/tag/React.jshttp://gold.xitu.io/#/tag/React%20Native

    业界讨论

    跨平台开发时代的 (再次) 到来?( Xamarin,NativeScript 和 React Native 对比)http://onevcat.com/2015/03/cross-platform/

    谈谈 React Native - 唐巧http://blog.devtang.com/blog/2015/02/01/talk-about-react-native/

    如何评价React-Native?https://www.zhihu.com/question/27852694/answer/43990708

    React Native概述:背景、规划和风险http://div.io/topic/938

    Native与Web的融合 - Qcon中React-Native演讲http://www.infoq.com/cn/presentations/the-fusion-of-native-and-web

    使用React Native一年后的感受http://www.dobest.me/blog/2016/06/12/%E4%BD%BF%E7%94%A8React%20Native%E4%B8%80%E5%B9%B4%E5%90%8E%E7%9A%84%E6%84%9F%E5%8F%97/

    Weex & ReactNative & JSPatch大对比http://awhisper.github.io/2016/07/22/Weex-ReactNative-JSPatch/

    weex&ReactNative对比https://zhuanlan.zhihu.com/p/21677103



    作者:wu大维
    链接:https://www.jianshu.com/p/d78696e9db3f
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    展开全文
  • React Native 发布一年多了,有不少公司已经在线上产品中或小范围试水,或大范围应用,很多公司或开发者都在为 React Native 的生态系统作出自己的贡献。React Native 的开发基本上是 Javascript + 系统原生开发...

    @author ASCE1885的 Github 简书 微博 CSDN 知乎
    本文首发于 InfoQ 移动技术公众号:移动开发前线
    由于潜在的商业目的,未经许可不开放全文转载许可,谢谢!

    gold.png-2324.9kB

    React Native 发布一年多了,有不少公司已经在线上产品中或小范围试水,或大范围应用,很多公司或开发者都在为 React Native 的生态系统作出自己的贡献。React Native 的开发基本上是 Javascript + 系统原生开发语言(Java,Objective-C,Swift),原生语言的开发所用的 IDE 没有多余的选择,Android 平台只能使用 Android Studio(不要告诉我你还在使用 Eclipse),iOS 平台只能使用 XCode,而开发 Javascript 的 IDE 选择就多了,从 FaceBook 官方推荐的 Atom+Nuclide,到与 Android Studio 同系列的 Javascript IDE WebStorm,再到功能强大的 Sublime Text 3,以及微软推出的 Visual Studio Code 和 decosoftware 专门为 React Native 打造的开源 IDE Deco,甚至 Vim,NodePad++ 等等,都可以用来开发 React Native,唯一的前提能够支持识别 Javascript 语法,识别 JSX 和 React Native API 的智能提醒。接下来我们就来介绍最常用的五款 IDE 的配置和选型。

    Atom1+Nuclide2

    Atom 是由 Github 打造的下一代编程开发利器,支持 Windows、Mac OS X、Linux 三大桌面平台,免费且开源。Atom 支持各种编程语言的代码高亮,同时具备强大的代码补全功能,能够极大的提高编程效率,Atom 本质上是一个文本编辑器,而不是一个 IDE,因此在用来开发 React Native 时需要配合 Nuclide 一起使用。

    image_1al7a82ds18sk1hq91h2tipn14d71t.png-426.3kB

    Nuclide 是 Facebook 基于 Atom 的基础上开发的一个插件 IDE,可以用来开发 React Native,iOS 和 Web 应用,目前不支持 Windows 平台,只支持 Mac OS X 和 Linux。

    image_1al7avfd1p72il9piv2qs9di2a.png-134.4kB

    Nuclide 内置了对 React Native 的支持,包括代码自动补全,代码诊断等,下图是代码补全的截图:

    image_1al7b2vv61gfl1a6d4f075h1fno2n.png-101.1kB

    Nuclide 是 Facebook 官方提供的 React Native IDE,对 React Native 的支持应该是最好的,因此,推荐大家首选这个,如果在你的电脑运行起来不会卡顿的话。Nuclide 的安装很简单,在确保 Atom 安装之后,在命令行中执行 apm install nuclide 即可。在使用 Nuclide 之前,建议好好看下官网的说明3

    WebStorm4

    WebStorm 是著名的 JetBrains5 公司开发的号称最智能的 Javascript 集成开发环境,可以用于复杂的客户端应用开发以及基于 Node.js6 的服务端开发。如果你之前使用 Android Studio 开发过 Android 应用的话,你一定会觉得 WebStorm 的界面似曾相识,没错,因为 WebStorm 和 Android Studio 都是 JetBrains 的杰作。WebStorm 支持 Windows、Mac OS X、Linux 三大桌面平台,不过和 Android Studio 可以免费使用不同,WebStorm 是需要付费使用的,只有 30 天的试用期。

    image_1al2crrfmp94opl1087tic1kl9.png-266.6kB

    由于 React Native 是基于 React 的,而 React 使用的是 JSX 语法,因此,使用 WebStorm 开发 React Native 之前,我们首先需要设置支持的 Javascript 语法,点击 WebStorm-Preferences,在打开的对话框中选择 Javascript language versionJSX Harmony 即可在代码编辑器中识别 JSX,如下图所示:

    image_1al4p5lld1eevdsv1mf21g6m1oii9.png-160.5kB

    当然,到这一步,只能使得编辑器识别 JSX 语法的 Javascript 代码,不会导致代码标红,但对于 React Native 的 API 名称,组件名称等并不会智能提醒和自动补全,因为目前 WebStorm 只支持 React 语法,还不支持 React Native 语法。为了解决这个问题,我们可以使用一个开源的插件:ReactNative-LiveTemplate7,按照 Github 上面的说明安装插件并重启 WebStorm 之后生效,这时在编辑器中输入 React Native 的组件或者 API 的首字母,会自动联想出相应的组件,如下所示,方便了很多。如果在使用过程中觉得这个插件有不完善的地方,你还可以在 Github 上面提交你的 Pull Request,贡献自己的一份力量。

    image_1al4ptcak3vt35s1p9d4a3gi5m.png-99.3kB

    Sublime Text 38

    Sublime Text 3 是一款广泛使用的代码编辑器,支持 Windows、Mac OS X、Linux 三大桌面平台,它是付费应用,但目前可以无限期的试用。它支持多种编程语言的语法高亮、拥有优秀的代码自动完成功能,还拥有代码片段(Snippet
    )的功能,可以将常用的代码片段保存起来,在需要时随时调用,极大的提高编程效率。

    image_1al7c3v7i329tmc1lo91v4b1qg34.png-65.3kB

    Sublime Text 3 强大功能的支撑在于它的插件机制,通过 Package Control 功能,开发者可以安装各种需要的插件,默认情况下,Sublime Text 3 没有集成 Package Control,我们需要自己安装。Package Control 有命令安装和手动安装两种方式,建议优先选择命令安装,可以参考官网安装指南9。本文我们介绍命令安装方式,在 Sublime Text 3 中通过 View->Show Console 打开命令行,执行如下命令:

    import urllib.request,os,hashlib; h = '2915d1851351e5ee549c20394736b442' + '8bc59f460fa1548d1514676163dafc88'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)

    上面的命令会创建安装所需的包目录,并下载 Package Control.sublime-package 到目录中。安装完成后,可以在 Preferences 菜单下找到 Package Settings 和 Package Control 两个子菜单。

    在 Sublime Text 3 中,React Native 开发相关的插件主要有:

    • babel-sublime10:支持 Javascript,ES6 和 JSX 语法高亮
    • react-native-snippets11:支持 React Native 代码片段

    在 Package Control 对话框中选择 Package Control:Install Packages 并在弹出的对话框中输入 Babel,即可找到 babel-sublime:

    image_1al738d691avg1ftf1b7ck4i1eie9.png-52.7kB

    安装完成之后,需要启用它,如下图所示菜单操作即可:

    image_1al74khdk1ack11aflo6stf19p2m.png-420.1kB

    react-native-snippets 插件同样通过 Package Control 进行安装,在 Install Package 对话框中搜索 react-native-snippets 安装即可:

    image_1al768abv2kf1miak917hsv0p13.png-81.4kB

    安装完成之后,在代码编辑器中输入代码片段的缩写,例如我们新建一个名为 UserDetail.js 的文件,在其中输入 rncc 来创建一个 React Native 的类,智能提醒如下所示:

    image_1al799aiune9k691hha1vli16be1g.png-54.8kB

    按下 Enter 键,插件自动生成如下样板代码,节省了很多手动输入所需花费的时间:

    'use strict';
    
    import React, { Component } from 'react';
    
    import {
      StyleSheet,
      View,
    } from 'react-native';
    
    class UserDetail extends Component {
      render() {
        return (
          <View />
        );
      }
    }
    
    const styles = StyleSheet.create({
    
    });
    
    export default UserDetail;

    除了 rncc,其他常见的代码片段如下所示,更多内容参见插件的 Github 首页。

    image_1al7mdbf7r9n5uu4rg5h1dr73h.png-83.1kB

    Visual Studio Code12

    Visual Studio Code 是微软推出的一个轻量级的开源 Web 集成开发环境,支持超过 30 种语言的开发,同时支持 Windows、Mac OS X、Linux 三大桌面平台。对于开发 React Native 而言,微软提供了专门的插件:vscode-react-native13,按照官网的说明进行插件的安装即可。这个插件使得开发者可以在 VS Code 中调试 React Native 代码,快速执行 react-native 命令,以及对 React Native 的 API 具备智能提醒功能,如下所示:

    react-features.gif-898.1kB

    Deco14

    Deco 是不久前刚发布的一个开源的专门为 React Native 打造的 IDE,目前只支持 Mac OS X 平台。它封装了 React Native 开发中经常会使用到的功能,例如集成 npm install 功能,集成 iPhone 和 iPad 模拟器,新建工程时快速生成 AwesomeProject,开发者不再需要通过执行 react-native init AwesomeProject 命令来生成了,关键是如果网络不好的话,免去了漫长的等待。

    image_1al4vjmbf5cb1141eqq11se8qo13.png-93.4kB

    Deco 区别于其他 IDE 最显著的特性是支持常用控件的拖拽生成代码和可视化编辑,这些控件既有 React Native 原生控件,也有一些知名的开源控件,当然,目前 Deco 集成的数量还比较少,如下图所示,我们拖拽一个名为 Lightbox 的开源控件,如果是第一次使用,Deco 会执行 npm install react-native-lightbox 命令首先下载安装这个控件,然后在代码编辑区自动生成代码,同时在右边的属性编辑区中会有对应的属性值,修改属性编辑区的属性值,会实时反应到代码中。

    image_1al6cr8411qte7uq1343eod51u9.png-242.6kB

    更直观的感受可以自己下载 Deco 执行一下,或者到官网观看一个 30 秒的演示视频15

    总结

    本文介绍了目前开发 React Native 的几款可选的主流 IDE,大家可以根据自己的具体情况进行选择,当然,团队开发中建议使用统一的 IDE。选择哪一款 IDE,首先取决于你们团队的硬件配置以及对付费软件使用的态度,然后才是 IDE 的功能特性。

    • 如果你的团队都是使用 MacBook Pro 进行开发,那么上面五款 IDE 都可以使用,如果团队中既有 Windows 电脑,也有 Mac 电脑,那么 Atom+Nuclide 和 Deco 就使用不了了。
    • 如果你们团队能够负担起付费应用,那么 WebStorm 是不错的选择,特别对于之前是 Android 开发的同学来说,可以实现 Android Studio 和 WebStorm 的无缝衔接。
    • 如果上面两个条件都不满足,那么就只剩下 Sublime Text 3 和 Visual Studio Code 可选了。从我们上面的介绍中可以了解到,这两款也都是非常强大的,如何选择取决于你自己。

    拓展阅读
    《VS Code(1.2.0)最新亮点和特性全介绍》16
    《Atom 1.8和1.9 beta发布》17
    《React Native开发IDE安装及配置》18
    《新编码神器Atom使用纪要》19
    《Visual Studio Code Guide[中文版]》20
    《Sublime Text 3 搭建 React.js 开发环境》21
    《用Sublime 3作为React Native的开发IDE》22


    欢迎关注我的微信公众号,专注与原创或者分享 Android,iOS,ReactNative,Web 前端移动开发领域高质量文章,主要包括业界最新动态,前沿技术趋势,开源函数库与工具等。

    展开全文
  • React Native 充分利用了 Facebook 的现有轮子,是一个很优秀的集成作品,使用 RN 即可做到无需编译就能远程热更新 App,再加上友好的开发方式、快到爆炸的开发效率、RN 已经完爆了其他的 App 开发方式,即使是一个...

    课程介绍

    React Native 充分利用了 Facebook 的现有轮子,是一个很优秀的集成作品,使用 RN 即可做到无需编译就能远程热更新 App,再加上友好的开发方式、快到爆炸的开发效率、RN 已经完爆了其他的 App 开发方式,即使是一个初入前端的开发者也能迅速开发一套 iOS、Android 双平台的 App。

    文章中将分三个部分讲解 RN 的开发。通过开发一个比较完整的电商 App 来逐步带领读者走入 React Native 的世界。读者可以从中汲取到完整的项目经验,从菜鸟到精通也只需要学完这个达人课即可。

    第一部分主要讲述封装基本组件、简单页面的开发:这个阶段主要解决开发中遇到的兼容问题,在文中会提出解决方案、避免无用的代码。

    第二个部分将讲述复杂的业务逻辑:让第一次做开发的读者也能非常快速的适应业务形态,让开发有底气、不再受制于产品的约束。

    第三部分将讲述性能的极致优化、热更新、统计等这些 App 必须的东西,让开发的 App 能够真正的比得上原生 App 和混合 App。

    作者介绍

    郭方超,技术总监、架构师、产品、运营。现担任泽旭商贸技术总监、达令前端架构师职务,有过多年的前端、后端开发经验,擅长 Node.js、.Net、Java 等开发技术。

    开发(参与)过的项目如下: 泽旭商贸 PC、移动、微信、App 项目,达令家 App 的开发,心燃灵动前端库、微信端电商框架的开发,来吧旅行前端项目。开源了以下项目:React-Native 的多个组件、React-Native 的监控系统、Node 爬虫框架、模块化前端脚手架、微信小游戏引擎。

    课程内容

    导读:为什么选择 React Native

    在所有的技术选型之前都有一个为什么。

    为什么我选择了 React-Native?选择这个技术到底给我带来了什么样的技术福利?如果你正在考虑 React-Native,或者还在研究选择什么样的技术实现自家的 App,不妨看看这个课程,说不定你就有了不一样的感受。

    注:React-Native 以下简称 RN。

    优点

    选择 RN 之前我也看过了其他的几种技术,之前也使用过其他的技术做 App,比如 HBuilder 的框架,非常的方便、非常的好用、什么都不需要管,但是开发完之后发现性能很差,做一个稍微复杂点的项目就非常上火,后来切了原生,但是使用 Java 做开发也有非常多的问题,开发效率很低。

    后来我选择了 RN,它使用 JS 更新虚拟 DOM,通过一个桥接器将需要更新的结果通知到 UI 层,让 Native 执行 UI 的改变。简单来说,就是用 JS 做驱动开发一个类原生的 App,所有的渲染都是和原生一样的,一下子就将原生开发和 JS 开发连接起来。通过这么一个模式,将传统的 Java 者 OC 开发转变成了简单易懂的前端 JS 开发,这是移动开发的一大进步,避免了一个 App 多个平台多套代码的尴尬,同时提升了开发效率,将移动开发带入了一个新的层面。

    enter image description here

    性能相似

    很多人说 RN 的性能比不上原生的 App,这个说法是要看具体的场景。

    在一般的应用场景下 RN 的表现和原生 App 是没有太大的差别的,一个 App 也不会到处都是复杂的交互效果。一些简单的点缀动画再加上列表图片等才是一个 App 最常见的内容,这种情况下它们之间的表现是一样的。

    RN 本身只是使用 JS 处理了 UI 渲染之前的一些逻辑,在最终的渲染上其实使用的还是原生的逻辑,尤其是渲染完成之后更是和原生的没有半点区别。

    我们的案例是一个电商项目,主要渲染逻辑是首页的自定义模板、无限加载的列表等。目前最大的性能瓶颈其实是在事件的优化上,优化之后用户已经感知不到和原生的区别了,我们会在后面的部分提到性能优化,将一个粗糙的 App 通过简单的方法提高 10 倍性能,再通过另外一个稍微复杂的方法减低内存占用。

    开发效率高

    通常情况开发一款 App 需要发布在 Android 和 iOS 两个平台,导致的结果就是一个 App 有两个团队、两套代码,界面几乎一样,为什么不能使用一套代码呢?之前也有大神使用各种手段达成这个目标,但是并不是很理想。

    由于使用熟悉的 React 和 JSX 的模式,开发者只需要有前端知识就可以很迅速的上手一个 RN 项目,如果再学一些实战的例子,稍微复杂一些的项目也难不倒各位前端开发者。

    Debug 超级方便,一边开发一边看效果再也不是梦。

    快速热更新

    RN 生成的 JS 文件,只要不涉及原生功能的增减,已经发布的 App 完全不需要重新安装即可完成新版功能的上线,用户只需打开 App 就能体验到最新的 App,省去了下载重装的各种麻烦,把 App 的更新做到了和网页更新一样的方便快捷。

    使用 RN 就能达到既有原生的所有能力,又有类似浏览器上的快速更新能力,同时还可以接入各种定制好的网页,将 App 的自由度提高到一个非常高的地步。

    大公司背书

    RN 的开发者是 Facebook,背靠大树好乘凉,社区更疯狂,Facebook 本身也在尝试使用 RN 技术开发自己的 App,RN 一定会越来越完善,截止写这篇文章的时候 RN 已经更新到了 0.53.0。

    RN 本身也是开源的,所有的源代码都是可以看到的,社区的讨论也是比较热烈的,现在可能中文文档还比较少,未来随着开发者的努力,这些坑都会填起来的。

    其实最开始的时候也没有想很多,仅仅是冲着 RN 可以快速开发,上线快体验好,经过了这么长时间的开发,我更加喜欢 RN 的这种开发方式,项目中也填了各种各样的坑,后面就用一个实际的例子来展示 RN 是怎样开发的吧。

    RN 的缺点

    升级快

    RN 本身其实还处于测试版,开发组经常会升级 RN,解决一些遗留或者隐藏的 bug,在这个过程中就导致了 RN 本身升级非常快,开发者在使用 RN 开发 App 的过程中应该尽量提高自己的版本,不需要一直是最新的,只要能够跟的上 Facebook 的节奏即可。

    自己搞定的问题也是可以合并入 RN 的源代码里的,不要一味的等 RN 更新,有些问题自己解决更方便,建议会 Android 或者 iOS 同学自己动手。

    动画难

    这里的难指的是复杂的动画在开发中很难去优化,尤其是开发者懂前端但是不懂原生的情况下,好在常见的 App 也不需要多么复杂的动画,一般使用位移变换就足够了,太复杂的动画建议使用 RN 的 SVG 组件来做。

    WebView 难用

    RN 自带的 WebView 跟浏览器有一定的差别,App 经常要打开一些网页,可能在开发的时候一切正常,但是到了 RN 里面就会有一些奇怪的问题,主要还是受到系统浏览器的影响,会有一些兼容方面的问题,这种情况下不如微信使用自己研发的浏览器,可以畅快的使用 ES 6 之类的新技术。

    需要原生支持

    简单的东西和界面的展现已经完全放手给了开发者,但是还是有一些功能只能原生去实现,如果原生部分的开发者对 RN 不太了解可能会给 App 带来不可预知的 bug,好在大多数开源看只需要执行 Link 命令就可以把原生部分也安装好。

    技术都会有优点和缺点,选择合适的技术才能给项目带来长久的生命力。

    其他技术

    Weex

    核心思想上,这两家其实并没有什么区别,Weex 也可以算是站在 RN 的肩膀上起步的,目前活跃度不高,大多数是在观望中。

    开发框架

    Weex 使用 Vue,熟悉 Vue 的开发者可能会更熟悉。

    RN 使用 React,都是 Facebook 出品,框架融合上会更方便一些,它们都是组件化开发,都属数据绑定,都有虚拟 DOM,社区同样活跃,使用人数也都非常多。

    学习成本

    React 的 JSX 初期会比较难上手,CSS 的写法也跟前端的样式写法不一样,Weex 使用模板的形式,直接 html+css 开发,上手会稍微简单一些。

    异步

    Weex 只支持 callback 的形式,RN 支持 promise 的形式,这些都是可以解决的,不是什么问题。

    社区

    RN 开源早,有 Facebook 支持,社区的组件库已经比较丰富,社区活跃度比较高。Weex 开源晚,社区活跃不高,以阿里系比较多。

    Flutter

    Flutter 是 Google 推出的一个跨平台(Android 和 iOS)移动开发框架,使用的是 Dart 语言。

    Flutter 的目标是用来创建高性能、高稳定性、高帧率、低延迟的 Android 和 iOS 应用,并且开发出来的应用在不同的平台用起来跟原生应用具有一样的体验。不同的平台的原生体验应该得到保留,让该应用看起来同整个系统更加协调;不同平台的滚动操作、字体、图标 等特殊的特性应该和该平台上的其他应用保持一致,让用户感觉就像操作原生应用一样。比如,返回图标 Android 和 iOS 是不一样的;滚动内容滚动到底的反馈也是不一样的。

    兼容

    Flutter 不使用系统提供的组件,自己实现了一套渲染机制,所以在性能优化、跨平台方面表现优秀。实际体验上,性能比 RN 要高不少。

    enter image description here

    RN 最终调用的还是系统的组件,虽然 Facebook 已经很努力了,但是在某些时候还是会有兼容性需要处理。

    组件

    Flutter 内置了对 Material Design 的支持,给开发者提供了丰富的 UI 控件库选择,同时所有的组件都有扩展,保持了很高的灵活性。

    RN 通过 React 也做到了组件式开发,跟 Flutter 相比,多了一个桥接器的转换,性能上肯定不如 Flutter。

    开发语言

    Flutter 使用 Dart 实现,Dart 号称要完全取代 JS,不过目前离这个目标还非常远,初期上手还是有一些难度的。

    RN 使用 JS 开发,做过前端的都非常熟悉,上手很容易。

    enter image description here

    Flutter 现在还在实验阶段,不排除 Google 使用别的框架替换它的可能性,Dart 语言也处于成长阶段,只有 Google 的浏览器在支持,或许在 Flutter 持续发展到一个阶段之后,才会有很多支持者。

    在写文章的时候 Google 放出了第一个测试版,感兴趣的可以下载下来玩玩儿。

    相比于其他几种技术,RN 是目前社区最活跃,开发效率最高的一种选择,选择 RN 也是需要在一个比较短的时间内能够完成 App 的开发。尤其现在前端开发者可以非常容易的从网页开发转到 App 开发上。对于我们包含 App、微信、小程序这样的三个平台更是需要 RN 这样的技术,一个团队就可以维护项目的持续增长。

    如果需要 RN 来开发自己的项目,那就继续往下看吧,我们将从简单的界面开发、数据更新等开始逐步深入,后面涉及到性能优化、自定义原生部分等。

    第01课:项目初始化

    知识点:

    • 快速创建 RN 项目
    • 添加使用路由功能
    • 不要在 RN 中使用的功能

    从这里开始,我们将一步一步的创建一个可以真正使用的 App:第一部分讲述开发一个 App 的大致过程,第二部分将开始优化性能、开发效率等,第三部分介绍添加热更新、支付、分享这些功能。大多数第三方组件可以很方便的 link 到项目里,部分需要手动导入甚至主动开发一些东西,这里也会在用到的时候讲出来。

    创建项目的前提条件

    使用 React-Native 创建一个新的项目请确保电脑上已经满足下面的这些条件。

    • nodejs:RN 的所有库都是从 NPM 上安装的,请确保电脑上已经安装了 NPM,可以使用npm -v来查看当前是否已安装,当前项目使用的 Node 版本是 8.9.3,NPM 的版本是 5.5.3。
    • react-native-cli:这个通常用作 RN 的初始化和启动模拟器等,使用 NPM 可以安装到电脑上。
    • Python:RN 里面有些脚本是使用 Python 写的,请确保电脑上已经安装了 Python 2.7 以上的版本。
    • JDK 1.8:安卓项目需要使用 JDK 1.8,请在电脑上安装好,安装的教程可以搜索其他资料。
    • Android Studio:调试以及编译安卓代码需要使用到,请在安装 Android Studio 之后安装好 Android SDK 以及模拟器,模拟器可以使用市面上的安卓模拟器,它们普遍比自带的模拟器要快,闲麻烦的读者可以直接连接手机调试。在安装好 IED 之后请下载好需要使用到的 Android SDK,下载 SDK 需要科学上网或者把下载地址替换成国内的几个下载源。
    • Git:后面的项目会加入到 Git 中,使用 Git 做版本管理的好处不言而喻。
    • Xcode(仅 iOS 项目中):安装了 Xcode 才能使用 iOS 模拟器,iOS 模拟器下的开发速度要明显优于安卓下,后面的开发过程大多数也是在 iOS 模拟器中进行的。在模拟器中的表现都差不多,有些不一样的效果可以在看到的时候单独调试。
    • Watchman(仅 Mac 系统用到):Watchman 用来监听文件变动等,Mac 下必须安装 Watchman。
    • VSCode:文中演示项目使用的编辑器是 VSCode,这里也推荐使用 VSCode 开发前端项目。

    创建项目

    我们先在本地创建一个可运行的项目,同时这个项目会加入到 Git 的版本管理中。

    (1)执行react-native init anxintao --version 0.53.0

    enter image description here

    • 图中1代表之前安装的 react-native-cli 的命令。
    • 图中2代表初始化命令。
    • 图中3代表项目的名称,这里是anxintao
    • 图中4代表指定 RN 的版本号,这个参数不传默认使用最新版。
    • 图中5代表 RN 具体使用的版本号。

    (2)使用 VSCode 打开项目,在项目根目录下执行命令启动初始化之后的项目,Mac 下推荐react-native run-ios,Window 下推荐react-native run-android启动默认的项目。如果能启动说明项目初始化完成,否则说明项目的某些东西没有安装好。

    enter image description here

    这里推荐把启动的命令写入到 package.json 文件中,比如,输入npm run ios即可代替原来的react-native run-ios,输入命令的速度快了不少,也可以给 VSCode 安装一个启动 RN 的插件,不过效果跟输命令差不多,具体要看个人习惯了。

    (3)到这里就说明项目创建成功了,这个项目现在还很简单,它的原生部分只有一个简单的空壳,这个空壳仅仅是初始化了一个 RN 的 Activity,所有的 JS 都是运行在根视图上的。

    (4)这里注意一下,新建的项目提示了按键可以刷新页面或者调出菜单,这显示的是 iOS 模拟器,按键为command R刷新和command D调出菜单。

    添加路由

    一个完整的项目不能没有路由,这里使用 React Navigation

    在写文章的时候已经有一个路由组件react-native-navigation热度超过了 react-navigation,它更多的使用的是原生的路由切换,效果更好,想用的读者可以去尝试一下。

    • 安装路由,在根目录下执行npm install --save react-navigation

    • 在根目录下新建src目录,所有页面放入这个文件夹下。

    • 新建一个首页,给后面的路由调用,页面路径为根目录/src/home/index.js

      'use strict';import React from 'react';import {    StyleSheet,    View,    Text} from 'react-native';export default class extends React.Component {    render() {        return <View style={{ marginTop: 200 }}>            <Text>这是首页</Text>        </View>    }}

    修改根目录下的index.js,添加整个项目的路由。

    import { AppRegistry } from 'react-native';import Pages from './src';//启动AppRegistry.registerComponent('anxintao', () => Pages);

    在 src 目录下新建index.js文件,在这个文件里添加路由,这里从简单的一个页面开始。

    'use strict';import React from 'react';import {    StyleSheet} from 'react-native';//添加路由组件import Navigation from 'react-navigation';//添加展示用的首页import Home from './home/index'//创建路由const Pages = Navigation.StackNavigator({    'Home': {        screen: Home    }}, {    //这里做了一个页面跳转的动画    transitionConfig: () => ({        screenInterpolator: sceneProps => {            const { layout, position, scene } = sceneProps;            const { index } = scene;            //设置页面跳转的动画            const translateX = position.interpolate({                inputRange: [index - 1, index, index + 1],                outputRange: [layout.initWidth, 0, 0]            });            const opacity = position.interpolate({                inputRange: [index - 1, index - 0.99, index, index + 0.99, index + 1],                outputRange: [0, 1, 1, 0.3, 0]            });            return { opacity, transform: [{ translateX }] };        }    }),    navigationOptions: {        header: null    }});//创建一个自己的容器,方便以后对路由做一些处理export default class extends React.Component{    constructor(props) {        super(props);    }    render() {        return <Pages onNavigationStateChange={this.listenChange.bind(this)}></Pages>;    }    //监听路由的跳转    listenChange(state1, state2, action) {    }}

    添加完成之后删除掉初始化项目之后的 App.js,这个时候在模拟器中使用快捷键command+R即可刷新刷新页面。

    479e89e0-1876-11e8-8087-9b6b3b447adf

    至此就完成了简单的路由设置,之后只需要添加页面并在路由中注册即可使用。

    路由升级版

    简单的路由并不能起到很好的作用,我们还是创建一个更实用的路由吧,比如带 3 个 tab 切换的首页,这也是大多数 App 使用套路。

    添加 4 个 tab 切换页,我们假定未来需要 4 个切换页,分别是首页、分类页、购物车、个人中心,在 home 下分别创建他们。

    084841b0-187a-11e8-8087-9b6b3b447adf

    修改路由所在的 index 文件,引入下面要用到的几个组件和页面。

    添加新加入的页面:

    import React from 'react';import {    StyleSheet,    Image} from 'react-native';//添加路由组件import Navigation from 'react-navigation';//添加展示用的首页import Home from './home/index'import Products from './home/products'import Shop_Cart from './home/shop_cart'import My from './home/my'

    创建底部的样式:

    //创建tab页的顶部样式const styles = StyleSheet.create({    tab: {        height: 40,        backgroundColor: '#fbfafc',        borderTopColor: '#efefef'    },    tabIcon: {        width: 20,        height: 20    },    tabLabel: {        marginBottom: 4    }})

    创建一个 tab 路由,为了简单这里只展示 2 个页面的,具体的代码可以去 Git 仓库查看。

    //创建首页的tab页const Tabs = Navigation.TabNavigator({    'Home': {        screen: Home,        navigationOptions: ({ navigation, screenProps }) => {            return {                tabBarLabel: '首页',                tabBarIcon: (opt) => {                    if (opt.focused) return <Image source={{ uri: require('./images/tab-home-active') }} style={styles.tabIcon}></Image>;                    return <Image source={{ uri: require('./images/tab-home') }} style={styles.tabIcon}></Image>;                }            }        }    },    'Products': {        screen: Products,        navigationOptions: ({ navigation, screenProps }) => {            return {                tabBarLabel: '产品分类',                tabBarIcon: (opt) => {                    if (opt.focused) return <Image source={{ uri: require('./images/tab-products-active') }} style={styles.tabIcon}></Image>;                    return <Image source={{ uri: require('./images/tab-products') }} style={styles.tabIcon}></Image>;                }            }        }    },}, {    //设置tab使用的组件    tabBarComponent: Navigation.TabBarBottom,    //点击哪个才加载哪个tab里的页面    lazy: true,    //设置tab放在界面的底部    tabBarPosition: 'bottom',    //设置tab里面的样式    tabBarOptions: {        style: styles.tab,        labelStyle: styles.tabLabel,        activeTintColor: '#d0648f'    }});

    替换 Pages 里的第一个页面为刚才创建的 tab 路由。由于默认加载第一个,所以需要将第一个设置成 tab 页。

    'Tabs': {        screen: Tabs }

    现在再刷新模拟器,就会发现底部的 tab 切换已经好了,点击可以切换不同的页面。

    da200e80-187e-11e8-8087-9b6b3b447adf

    这里将图片转化成 base 64 的方式再引入到图片组件中,好处是打包之后会变成一个整体,坏处是打包之后的 bundle 文件会变大,做增量更新也比较麻烦。

    不推荐使用的东西

    • 投影:安卓不支持投影,在开发的时候如果没有必要就使用别的方式代替吧,比如使用图片代替投影。
    • 边框色:在长列表中尽量不要使用边框色,在某些安卓手机下会闪退。
    • 使用了圆角的情况再使用背景色:iOS 手机会出现边框颜色异常或者异常色块,去掉背景即可。
    • 过于深层次的结构。
    • 过于频繁的刷新 state。
    第02课:封装自己的库

    知识点:

    • 封装一个自己的日志,代替原生的 console
    • 封装一个自己的请求库,代替原生的 fetch
    • 自适应方法,兼容各种手机屏幕
    • 封装一个本地存储
    • 集成 Toast 弹出提示
    • 让 iOS 支持 HTTP 地址
    • 安卓需要证书才能编译

    在正式开始之前,我们先封装几个要用到的库。

    自定义本地日志

    自定义日志的一个好处就是省的每次都要手动注释 console,而且还可以同时将日志存在本地,或者发到日志服务器,一个方法一举多得了。

    在 src 目录下新建一个 utils 文件夹,封装一个日志输出类,开发模式下使用console.log命令,正式情况下记录在变量中,方便在手机上查看日志。

    新建一个 log.js 文件,路径为根目录/src/utils/log.js

    80223590-1886-11e8-8087-9b6b3b447adf

    新建一个数组变量logs,用来临时存放日志信息。

    98604390-1886-11e8-8087-9b6b3b447adf

    将日志分成信息、警告和错误 3 种,分别给出 3 个可调用的方法,同时给第一个参数加一个好看的颜色。

    cc840ad0-1886-11e8-8087-9b6b3b447adf

    在 index 中引入日志组件,写几个方法来看看调用的结果。

    enter image description here

    这里稍微定义一下日志的要求,参数 0,字符串;参数 1,对象;参数 2,字符串。

    enter image description here

    自定义请求

    RN 默认提供了 fetch 方法去请求远程数据,我们再封装一次,这个方法将会针对现有的项目做封装,在使用请求的时候能够更适合、更方便。这里使用 header 保存了一些临时的变量,算是一个小小的全局缓存吧。

    创建 request.js 文件,目录:根目录/src/utils/request.js

    80223590-1886-11e8-8087-9b6b3b447adf

    将请求 header 里的信息单独出来,每次请求都需要带上这个共享 header 数据。

    29975590-1889-11e8-8087-9b6b3b447adf

    创建一个 Request 类,并将这个类对外公开,这里将请求初始化一次,以后用到别的请求的时候也可以单独实例化一次。

    /** * 请求库 */class Request {}export default new Request();

    每次请求都将 header 中的内容带入请求中,单独检测 httpcode 和后端返回的 code 值,这里可以直接做权限检测,在需要的时候跳转到登录页。

    /** * 请求库 */class Request {    /**    * 检测返回状态码    * @param {*} status     * @param {*} res     */    async _checkStatus(status, res, url) {        if (status !== 200) {            logWarm('请求失败参数', await res.text(), url, headers);            throw new Error('网络连接失败,请检查网络');        }    }    /**    * 检查后端返回的状态码    * @param {*} status     */    _checkAppStatus(json, url) {        if (json.status != 0) {            logWarm('返回状态报错', json, url);            throw new Error(`${json.errorMsg}`);        }    }    /**     * 内部实现网络请求     * @param {*} url      * @param {*} options      */    async _request(url, options, type) {        url = url.indexOf('http') == 0 ? url : url.indexOf('/api') == 0 ? domain + url : baseUrl + url;        let res = await fetch(url, options);        this._checkStatus(res.status, res, url)        if (type === 'json') return await this._jsonFactory(res, url, options)        return await this._jsonFactory(res, url, options)    }    /**         * 处理json数据         * @param {*} res          * @param {*} url          */    async _jsonFactory(res, url, options) {        let json;        let txt = '';        try {            txt = await res.text();        } catch (e) {            log('未拿到返回字符串', { url: url, txt: txt });            throw new Error('数据格式错误');        }        try {            json = JSON.parse(txt);        } catch (e) {            logErr('返回数据格式错误', { url: url, txt: txt });            throw new Error('数据格式错误');        }        this._checkAppStatus(json, url)        log("请求返回", json, url, options);        return json.data;    }    /**     * get请求     * @param {*} url      */    async get(url, data) {        if (data) data = urlEncoded(data);        if (url.indexOf('?') < 0 && data) url += '?' + data;        return this._request(url, {            method: 'GET',            headers: headers,            timeout: 10000        }, 'json')    }    /**     * post请求     * @param {*} url      * @param {*} data      */    async post(url, data) {        return this._request(url, {            method: 'POST',            headers: Object.assign(headers, { 'Content-Type': 'application/x-www-form-urlencoded' }),            timeout: 10000,            body: urlEncoded(data)        }, 'json')    }}

    调用一次远程端口并查看日志输出,这里调用的也是案例中要使用到的获取 banner 的接口,该接口不需要用户权限,后面还会遇到需要用户权限的接口。

    enter image description here

    自适应方法

    这里推荐一种自适应的方法,同时也是前端在开发移动端页面的时候常用的方法,将手机屏幕宽度默认为 750 像素,然后将所有的宽高按照这个比例去缩放,这要求设计出的设计稿宽度也是 750 像素。

    在 utils 下新建一个px.js文件,按照出入的大小根据当前屏幕的宽度获取到缩放的比例并返回结果。

    ab16ed00-188a-11e8-8087-9b6b3b447adf

    在首页引入 px 方法,查看使用 px 之后的效果。

    d4992580-188a-11e8-8087-9b6b3b447adf

    d97950c0-188a-11e8-8087-9b6b3b447adf

    可以看到使用 px 将 500 像素缩放之后的效果和最开始设置的纯数字 200 效果是一致的,这里使用的是 iOS 模拟器,真实的屏幕宽是 375,按照 750 宽去算的话会把传入的参数统统除以 2。

    封装本地存储

    RN 提供的 AsyncStorage 可以根据 key 存储相应的字符串,我们这里改进一下,让它可以存储所有类型的字段,利用的是将传入的参数改造成对象,然后使用 JSON 的方法将对象转化成一个可以存储的字符串。

    在 utils 下新建一个Storage.js

    3ddb9950-188c-11e8-8087-9b6b3b447adf

    将传入的对象转化为字符串并存入 AsyncStorage。

    'use strict';import { AsyncStorage } from 'react-native';/** * 获取存储的数据 * @param {*} key  */exports.getItem = async (key) => {    let item = await AsyncStorage.getItem(key);    if (!item) {        return null;    }    return JSON.parse(item).v || null;}/** * 存入数据 * @param {*} key  * @param {*} value  */exports.setItem = (key, value) => AsyncStorage.setItem(key, JSON.stringify({    v: value}));/** * 删除已经存在的数据 * @param {*} key  */exports.removeItem = (key) => AsyncStorage.removeItem(key);/** * 清除所有 */exports.clear = () => AsyncStorage.clear();/** * 获取所有的key */exports.getAllKeys = () => AsyncStorage.getAllKeys();

    在首页使用 setItem 存入数据,然后第二次进入页面再使用 getItem 获取数据。

    e9e272a0-188c-11e8-8087-9b6b3b447adf

    这里用到了 componentDidMount 这个方法,该方法是在组件生命周期中的初始化完成之后执行的。

    enter image description here

    添加弹出 Toast

    之前公司使用的是自己开发的提示方法,该方法需要改变原生代码,非常的不方便,这里推荐使用第三方的开源组件react-native-root-toast,只需要安装一下就好了。

    执行命令,安装 Toast:npm i --save react-native-root-toast

    在 utils 目录下新建 toast.js 文件,添加 Toast 的默认方法并填入默认参数,这里设置显示时间为 1000 毫秒,背景颜色是一个半透明的黑色,其目的也是为了方便调用,如果需要多种效果的就定义多个即可。

    enter image description here

    在首页引入 Toast 并看看实际的效果。

    41c80400-18b3-11e8-8087-9b6b3b447adf

    让 iOS 支持 Http 协议

    苹果之前推荐使用 Https 协议,现在默认是不支持 Http 的,如果需要支持 Http 则要单独设置,案例中的项目也用到了 Http,所以需要修改 info.plist 文件,让 iOS 可以访问 Http 的地址。

    使用 Xcode 选择打开其他项目。

    0da257a0-1919-11e8-8087-9b6b3b447adf

    打开项目下的 iOS 文件夹,选择项目文件并打开。

    4d6ce8a0-1919-11e8-8087-9b6b3b447adf

    选择 info.plist 文件,在右边选择第一行并点击 + 号添加一项。

    enter image description here

    选择 App Transport Security Setting 这一项,会弹出提示,单击“确定”按钮即可自动刷新。

    01e614a0-191a-11e8-8087-9b6b3b447adf

    在上面添加的新配置中添加一个新的配置 Allow Arbitrary Loads,同时设置为 YES。

    enter image description here

    改完配置还需要编译一次,单击左上角的三角形或者菜单中 product 下的 build 选项。

    enter image description here

    这里我使用的是 Xcode 修改配置文件,如果你发现配置文件没有变化,也可以自己改 info.plist 文件的内容。

    编译安卓客户端

    使用 Android Studio 打开根项目下的 Android 目录,打开 build.gradle 文件,这个就是项目的 Gradle 配置文件,通常使用这个文件对整个项目进行描述。

    3d6d4f10-204b-11e8-94c8-43a1e6ab60e0

    经过一定时间的等待,IDE 就会初始化整个项目,如果有一些需要下载的文件也会在这个时间通知下载。

    单击菜单build/gennerate signed apk,这个就是编译一个可以安装在安卓手机上的安装包,也可以通过单击make project来看看项目是否可以编译通过。

    enter image description here

    单击 Next 按钮,IDE 会提示需要选择一个证书,这里可以选择一个已有的并输入密码,也可以通过单击create new来创建一个,后面一直单击 Next 按钮就可以了,IDE 会在生成 APK 之后弹出通知。

    021b3160-204c-11e8-8063-17489f4fcf26

    选择创建一个新的证书,根据提示填入相应的内容,之后单击 OK 按钮即可生成,记得选择 remember password,下次直接填入密码。

    8d33ddb0-204c-11e8-a1dd-77d9e3f43337

    经过一整机器躁动之后,IDE 弹出编译结果,点击蓝色字可以快速打开 APK 的地址。

    enter image description here

    将 APK 文件发送到手机上安装即可,一个自己开发的 App 就安装好了。

    14e47fd0-204d-11e8-94c8-43a1e6ab60e0

    这里要注意,在打包之前要把 RN 打包生成的 bundle 文件放入 Android 文件下的 assets 目录中,否则 Android 会因为找不到启动文件而报错。

    第03课:首页模板和商品列表
    第04课:组件和商品详情
    第05课:使用 WebView 和运营页通讯
    第06课:个人中心设置及 Debug 日志
    第07课:添加自定义组件
    第08课:注册登录
    第09课:全局状态管理购物车
    第10课:实现购买(1)
    第11课:实现购买(2)
    第12课:订单管理
    第13课:添加优化动画
    第14课:优化 App,提升10倍效率
    第15课:使用 ESlint 规范化代码
    第16课:使用 TypeScript 编写代码
    第17课:集成极光
    第18课:热更新
    第19课:总结

    阅读全文: http://gitbook.cn/gitchat/column/5aa8a68b0bb9e857450e2308

    展开全文
  • 转载于:https://www.jianshu.com/p/05b7e02d21d0 关于react native 第三方组件安装、卸载的一些注意事项 我使用的react native版本是 0.49.5,在开发的过程中经常会遇到安装、卸载的操作,一不小心(没有安装...

    转载于:https://www.jianshu.com/p/05b7e02d21d0

    关于react native 第三方组件安装、卸载的一些注意事项

     

    我使用的react native版本是 0.49.5,在开发的过程中经常会遇到安装、卸载的操作,一不小心(没有安装流程做)项目运行的就会报各种错误。建议在把第三方组件引入正式项目工程之前先在demo工程中跑通,否者你会浪费很多不必要的时间。下面是正常安装、卸载流程。

    安装:

    1,正确的安装方式是 npm install xxx --save,经常看到许多组件安装文档上后面没有 --save,如果没有建议加上 否则组件信息不会出现在package.json中,导入组件的时候可能会找不到路径。
    2,安装完成后执行一下 react-native link xxx

    卸载:

    1,在卸载之前先执行 react-native unlink xxx
    2,然后在执行 npm uninstall xxx --save 别忘了加--save

    卸载的时候特别注意,如果卸载的组件有link的,uninstall之前一定要先unlink,否则运行项目会报错的(如果你没有在uninstall之前先unlink且运行已经报错了,重新再安装和卸载一遍就ok了)。

    展开全文
  • React Native是facebook刚开源的框架,可以用javascript直接开发原生APP,先不说这个框架后续是否能得到大众认可,单从源码来说,这个框架源码里有非常多的设计思想和实现方式值得学习,本篇先来看看它最基础的...
  • iOS 动态更新方案 JSPatch 与 React Native 的对比
  • ReactNative 基础学习

    2019-09-24 10:36:19
    安卓Back键的处理·...http://bbs.reactnative.cn/topic/480/%E5%AE%89%E5%8D%93back%E9%94%AE%E7%9A%84%E5%A4%84%E7%90%86-%E5%9F%BA%E6%9C%AC-%E9%AB%98%E7%BA%A7%E7%AF%87 热更新: React-Native 实现增量...
  • react native 极光推送

    2018-12-20 17:27:54
    github: https://github.com/jpush/jpush-react-native 安装:  ...yarn add jpush-react-native jcore-react-native 或者 npm install jpush-react-native --save npm install jcore-rea...
  • React Native初探(转)

    2019-01-25 14:05:32
    很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情。 PS:任何新技术的尝鲜都...
  • React Native Ble蓝牙通信

    2019-01-15 17:20:35
    由于项目需要,基于React Native 开发的App要跟BLE蓝牙设备通信。  在js.coach上搜索React Native BLE蓝牙组件,只找到三个组件: react-native-ble-manager:文档清晰明了,简单易懂,基本每个月都有更新,遇到...
  • 有了一些对React Native开发的简单了解,让我们从实战出发,一起来构建一个简单的京东客户端。 这个客户端是仿照之前版本的京东客户端开发的Android版应用,来源于CSDN上的一位分享者,再次向他表示感谢! 本文会对...
  • reactNative性能优化

    2020-01-02 11:23:12
    本文将简单介绍一下我所收集到的React Native应用优化方法,希望对你有所启发。很多方法也是适用React web应用的。 包体积优化 无论是热更新方案走网络下载js,还是直接将js打进apk,减小js bundle体积都很必要。 ...
  • 当我们在开发React Native应用时,如果在调试状态下,获取错误的异常信息是非常简单的,JS异常会立即在真机上显示(或者打开调试模式在浏览器控制台中显示),原生层的java闪退异常则可以通过Android Studio的Logcat...
  • React-Native学习资料

    2018-05-27 13:35:53
    React Native 构建 Facebook F8 2016 App / React Native 开发指南 http://f8-app.liaohuqiu.net/ React-Native入门指南 https://github.com/vczero/react-native-lesson 30天学习React Native教程 ...
  • class DateUtil{ /** * 例如:2017-06-28 10:48:46转成date类, * 可把- replace成/ * @param dateString * @return Date */ static parserDateString(dateString){ if(dateString){
  • 根据http://facebook.github.io/react-native/docs/getting-started.html或者https://reactnative.cn/docs/0.51/getting-started.html先安装好对应软件以及配置好相应环境。 二、 1、新建(不含android和ios包的)...
  • ReactNative开发——Navigation的使用React Native 0.43之前 ‘react-native’包里面,但0.43之后了’rea移除了rect-native’。网上的大部分资料,也是ReactNative 0.43之前的 Navigator的用法,然而我用的0.44版本在...
  • 本课程主要讲解 React 的基础知识及应用案例,包括 props、state、生命周期函数等,样式和 Flex 布局,React Native 内置的 API 和 UI 组件介绍、路由、状态管理 Redux,如何使用第三方组件,JS 跟原生代码之间的...
1 2 3 4 5 ... 20
收藏数 924
精华内容 369
关键字:

%02d reactnative