机器学习&全栈

RN精进笔记(五)RN组件篇

2018-06-01  本文已影响842人  采香行处蹙连钱

react-native

rn搭建开发环境
  1. 必须安装

    1. brew install node(或者全局安装node)
    2. brew install watchman and flow
    3 . 初始化项目
    react-native init AwesomeProject
    cd AwesomeProject
    react-native run-ios
    
  2. 直接打开xcodeproj

    报错:8081端口被占用,于是关掉php-fpm
    
  3. over

Props(属性) & State
  1. 系统组件使用props

    <Image source={pic} style={{width: 193, height: 110}} />
    
  2. 自定义组件使用props

    // 需要在constructor中初始化state
    class Greeting extends Component {
      constructor(props) {
        super(props);
        this.state = {showText: true};
        setInterval(() => {
          this.setState(previousState => {
            return {showText: !previousState.showText};
          });
        }, 1000);
      }
      render() {
        let display = this.state.showText ? this.props.name : '';
        return (
          <View>
            <Text>Hello I am {this.props.name}!</Text>
            <Text>{display}</Text>
          </View>
        );
      }
    }
    
  3. over

基础
  1. 样式

    样式名基本上是遵循了web上的CSS的命名,只是按照JS的语法要求使用了驼峰命名法,例如将background-color改为backgroundColor

  2. 宽度和高度

    最简单的给组件设定尺寸的方式就是在样式中指定固定的widthheight

    在组件样式中使用flex可以使其在可利用的空间中动态地扩张或收缩。

  3. 使用Flexbox布局

    • 在组件的style中指定flexDirection可以决定布局的主轴。子元素是应该沿着水平轴(row)方向排列,还是沿着竖直轴(column)方向排列呢?默认值是竖直轴(column)方向。

    • 在组件的style中指定justifyContent可以决定其子元素沿着主轴排列方式。子元素是应该靠近主轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-startcenterflex-endspace-around以及space-between

    • 在组件的style中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-startcenterflex-end以及stretch

  4. 处理文本输入

    <TextInput
              style={{height: 40}}
              placeholder="Type here to translate!"
              onChangeText={(text) => this.setState({text})}
            />
    
  5. 使用滚动视图

    scrollview

  6. 使用长列表

    React Native提供了几个适用于展示长列表数据的组件,一般而言我们会选用FlatList或是SectionList

     <FlatList data={[
              {key: 'Devin'},
              {key: 'Jackson'}
            ]} 
            renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
            /> 
    
    如果要渲染的是一组需要分组的数据,也许还带有分组标签的,那么SectionList将是个不错的选择.
    
  7. 网络

    1. 使用Fetch
    getMoviesFromApiAsync() {
        return fetch('https://facebook.github.io/react-native/movies.json')
          .then((response) => response.json())
          .then((responseJson) => {
            return responseJson.movies;
          })
          .catch((error) => {
            console.error(error);
          });
      }
    2. 或者使用ES7
    // 注意这个方法前面有async关键字
      async getMoviesFromApi() {
        try {
          // 注意这里的await语句,其所在的函数必须有async关键字声明
          let response = await fetch('https://facebook.github.io/react-native/movies.json');
          let responseJson = await response.json();
          return responseJson.movies;
        } catch(error) {
          console.error(error);
        }
      }
    
    3. 使用XMLHttpRequest API
    var request = new XMLHttpRequest();
    request.onreadystatechange = (e) => {
      if (request.readyState !== 4) {
        return;
      }
    
      if (request.status === 200) {
        console.log('success', request.responseText);
      } else {
        console.warn('error');
      }
    };
    
    request.open('GET', 'https://mywebsite.com/endpoint/');
    request.send();
    
    4. React Native还支持WebSocket
    var ws = new WebSocket('ws://host.com/path');
    
    ws.onopen = () => {
      // 打开一个连接
    
      ws.send('something'); // 发送一个消息
    };
    
    ws.onmessage = (e) => {
      // 接收到了一个消息
      console.log(e.data);
    };
    
    ws.onerror = (e) => {
      // 发生了一个错误
      console.log(e.message);
    };
    
    ws.onclose = (e) => {
      // 连接被关闭了
      console.log(e.code, e.reason);
    };
    
  8. over

RN Hello world项目部分代码详解
  1. index.ios.js 文件

    import React, { Component } from 'react';
    import {
       AppRegistry,
       StyleSheet,
       Text,
       View
     } from 'react-native';
    
    import App from './Test/App.js';
    
    export default class RNProject extends Component {
      render() {
        return (
      <App  />
     ); }
       }
       AppRegistry.registerComponent('RNProject', () => RNProject);
          此处至少要了解import和export语法
    
  2. const常量

      // 该案例解释:const {width, height} =  Dimensions.get('window');原理
    function testFunction() {
    let a = "高"
    let b = "低"
    return {test_1: a, test_2:b}
    }
    const {test_1, test_2} =  testFunction()
    const WelcomeWord = "Hello ,欢迎使用"
    
  3. 生命周期

    点击查看大图:RN生命周期图片

    1. 初始化类的构造器,通常在此初始化state数据模型
    constructor(props) {
        super(props);
        this.state = {
            //key: true
        }
    }
    2. 表示组件将要加载到虚拟DOM,在render方法之前执行,整个生命周期只执行一次
    componentWillMount() {
        
    }
    3. 表示组件已经加载到虚拟DOM,在render方法之后执行,整个生命周期只执行一次。通常在该方法中完成异步网络请求或集成其他JavaScript库
    componentDidMount() {
        
    }
    4. 在组件接收到其父组件传递的props的时候执行,参数为父组件传递的props。在组件的整个生命周期可以多次执行。通常在此方法接收新的props值,重新设置state。
    componentWillReceiveProps(nextProps) {
         this.setState({
          //key : value
         });
     }
    5. shouldComponentUpdate(nextProps, nextState) {
        return true;
        
     }
     该函数在componentWillReceiveProps(nextProps)执行之后立刻执行。
     componentWillUpdate(nextProps, nextState) 在shouldComponentUpdate(nextProps, nextState)函数执行完毕之后立刻调用
    6. componentDidUpdate(preProps, preState)在在render()方法执行之后立刻调用。可多次执行
    7. render方法,渲染组件
    render() {
      return(
        <View/>
     );}
    8. componentWillUnmount在组件由虚拟DOM卸载的时候调用。
    9. 
    
  4. Component类和继承

    所有组件都继承自Component,继承相关语法参考阮一峰es6研习。

    参考:https://blog.csdn.net/heqiangflytosky/article/details/53909030

    可以通过以下方式创建一个组件类
    class Home extends Component {     
      constructor(props) {
       super(props);
       this.state = {};
     }
    }
    也可以通过React.createClass()的方式创建:
    var HelloWorldAppp = React.createClass({
      getDefaultProps() {},
      getInitialState() {},
      render() {
        return (
        <View>
         <!--ref是React的一种属性,可以给render函数中某个节点添加一个ref属性。要获取一个React组件的引用,可以使用ref来获取你拥有的子组件的引用-->
         <TextInput style={styles.welcome}  ref="mytestinput"></TextInput>
        </View>
        );
      },
      focus() {
        if (this.refs.mytestinput != null) {
            this.refs.mytestinput.focus();
        }
      }
    });
    
    组见间通讯可以用一下方法:
      父组件调用子组件:可以通过this.props方法。
      子组件调用父组件:通过回调函数;
      兄弟组件:通过其父组件;通过ref来实现;
      没有关联的组件:通过发送事件:Event:Emitter/Target/Dispatcher或者可以通过ref来实现。
      框架:Redux
      框架:Flux
    
  5. RN和原生通讯

    参考《RN精进笔记(三)集成篇 React和Native原生通讯》

    参考文献:RN中文文档

    参考文献:原生向RN传值

    参考文献:RCTEventEmitter使用

    参考:RN调用OC原生方法

    1. 从原生组件传递属性到RN
    
    NSArray *imageList = @[@"http://foo.com/bar1.png",
                  @"http://foo.com/bar2.png"];
    NSDictionary *props = @{@"images" : imageList};
    RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"ImageBrowserApp" initialProperties:props];
     
    'use strict';
    import React, { Component } from 'react';
    import {
      AppRegistry,
      View,
      Image,
    } from 'react-native';
    
    class ImageBrowserApp extends Component {
      renderImage(imgURI) {
        return (
         <Image source={{uri: imgURI}} />
        );
      }
      render() {
        return (
         <View>
          {this.props.images.map(this.renderImage)}
         </View>
        );
      }
    }
    
    AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp);
    
    这里传值的方式就是在Appdelegate初始化RCTRootView时将props传递给RN Component组件。
    
    2. 原生给RN发送通知,或者说是RN监听原生的变化
     
    react-native 0.27 之前使用sendDeviceEventWithName和继承eventDispatcher方式传值
    RCT_EXPORT_MODULE()
    @synthesize bridge = _bridge; 
    -(void)iseCallback:(NSString*)code result:(NSString*) result  {  
      [_bridge.eventDispatcher   sendDeviceEventWithName:@"iseCallback"  body:@{  
          @"code":code,  
          @"result":result,  
          @"index":self.bridgeIndex,  
          @"category":self.bridgeCategory  
        }];  
    }  
    
    然后js端接收代码如下:
     import RCTDeviceEventEmitter from 'RCTDeviceEventEmitter';  
     this.listener = myNativeEvt.addListener('iseCallback', this.iseCallback.bind(this));  
     this.listener && this.listener.remove(); 
     
     0.28版本之后采用RCTEventEmitter方法
     
     #import <UIKit/UIKit.h>
     #import <React/RCTBridgeModule.h>
     #import <React/RCTEventEmitter.h>
     #import <React/RCTBridge.h>
     @interface RNBridgeModule :    RCTEventEmitter<RCTBridgeModule>
      - (void)iseCallback;
     @end
     
     #import "RNBridgeModule.h"
     @implementation RNBridgeModule
     RCT_EXPORT_MODULE()
     - (NSArray <NSString *> *)supportedEvents
     {
       return @[@"iseCallback", @"iseVolume", @"playCallback"];
     }
     - (void)iseCallback {
     [self sendEventWithName:@"iseCallback" body:@{@"code": @"1000", @"result": @"ise警告"}];}
     
     JS 接收:
     导入必须的框架
     import {
       NativeModules,
       NativeEventEmitter
     } from 'react-native';
     
     var nativeBridge = NativeModules.RNIOSExportJsToReact;//你的类名
     const NativeModule = new NativeEventEmitter(nativeBridge);
     
     componentDidMount(){
       NativeModule.addListener('iseCallback',(data)=>this._getNotice(data));
     }
    
     _getNotice (body) {//body 看你传什么
        this.forceUpdate();//重新渲染
     }
    
     componentWillUnmount() {
      //删除监听
      this.NativeModule.remove()
      }
      
      
    最后一步,需要在原生的某个地方调用并触发iseCallback函数。
    
    这种方式其实是iOS接收到了通知或者值变化,如原生键盘组件高度变化,需要通知RN改变样式,则调用 “iseCallback”方法,sendEvent给RN,rn因为一直在监听该方法,所有监听到了之后可以做出反馈。
    
    3. RN向Native传值
    
    RN直接调用iOS方法。这里一般是RN需要调用iOS的一些原生组件或者SDK的方法。
    
    先将方法封装在某个类中,这个类就是1、2部分自定义的RNBridgeModule
    //导出方法,桥接到js的方法返回值类型必须是void
    原生代码如下:
    RCT_EXPORT_METHOD(doSomething:(NSString *)name)
    {
        NSLog(@"doSomething:%@",name);
    }
    
    js代码如下:
    var RNBridgeModule = NativeModules.RNBridgeModule;
    <TouchableHighlight onPress={ ()=>RNBridgeModule.doSomething('test')}>
          <Text style={styles.text}>
            点击
         </Text>
    </TouchableHighlight>
    
    断点监听原生的doSomething方法即可。
    
    
RN进阶
  1. 集成到现有原生应用

    1. 项目根目录下创建package.json文件
    {
      "name": "MyReactNativeApp",
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "start": "node node_modules/react-native/local-cli/cli.js start"
      },
      "dependencies": {
        "react": "16.5.3",
        "react-native": "0.55.3"
      }
    }
    #可以根据npm info react-native & react查找rn的依赖包版本
    2. npm install 
    3. 新建iOS项目并 pod init & pod install 
    4. Podfile文件如下:
    # target的名字一般与你的项目名字相同
    target 'NumberTileGame' do
    
      # 'node_modules'目录一般位于根目录中
      # 但是如果你的结构不同,那你就要根据实际路径修改下面的`:path`
      pod 'React', :path => '../node_modules/react-native', :subspecs => [
        'Core',
        'CxxBridge', # 如果RN版本 >= 0.45则加入此行
        'DevSupport', # 如果RN版本 >= 0.43,则需要加入此行才能开启开发者菜单
        'RCTText',
        'RCTNetwork',
        'RCTWebSocket', # 这个模块是用于调试功能的
        # 在这里继续添加你所需要的RN模块
      ]
      # 如果你的RN版本 >= 0.42.0,则加入下面这行
      pod "yoga", :path => "../node_modules/react-native/ReactCommon/yoga"
    
       # 如果RN版本 >= 0.45则加入下面三个第三方编译依赖
      pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
      pod 'GLog', :podspec => '../node_modules/react-native/third-party-podspecs/GLog.podspec'
      pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
    
    end
    
    #此处多处报错,待重头来捣鼓一下。
    
    5. 开发
    创建index.js文件 RCTRootView跳转到对应的js页面
    
  2. ReactNavigation

    社区今后主推的方案是一个单独的导航库react-navigation,它的使用十分简单。

  3. 颜色&背景图片&触摸事件

    <Image source={require('./my-icon.png')} />
    <Image source={{uri: 'app_icon'}} style={{width: 40, height: 40}} /> #Xcode静态文件
    网络图片
    // 正确
    <Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
           style={{width: 400, height: 400}} />
    缓存控制
    本地系统文件:相册
    背景图片
     <ImageBackground source={...}>
        <Text>Inside</Text>
      </ImageBackground>
      <TouchableHighlight onPress={this._onPressButton}>
            <Text>Button</Text> </TouchableHighlight>                          长按 onLongPress
    手势
    
  4. 动画

    1. 使用Animated库
    Animated仅封装了四个可以动画化的组件:View、Text、Image和ScrollView,不过你也可以使用Animated.createAnimatedComponent()来封装你自己的组件。
    2. over
    
  5. 定时器

    componentDidMount() {
        this.timer = setTimeout(
          () => { console.log('把一个定时器的引用挂在this上'); },
          500
        );
      }
      componentWillUnmount() {
        // 请注意Un"m"ount的m是小写
    
        // 如果存在this.timer,则使用clearTimeout清空。
        // 如果你使用多个timer,那么用多个变量,或者用个数组来保存引用,然后逐个clear
        this.timer && clearTimeout(this.timer);
      }
    只需铭记在unmount组件时清除(clearTimeout/clearInterval)所有用到的定时器
    
  6. 直接修改Dom节点

    setOpacityTo: function(value) {
      // Redacted: animation related code
      this.refs[CHILD_REF].setNativeProps({
        opacity: value
      });
    },
    
  7. over

RN状态管理redux和组件间通讯
RN调试和性能相关
  1. 调试

    1. 模拟器Command⌘ + D || 快捷键 通过摇晃设备或是选择iOS模拟器的"Hardware"菜单中的"Shake Gesture"选项来打开开发菜单
    
    2. Command⌘ + R 刷新js
    
    3. Chrome开发者工具
    在开发者菜单中选择"Debug JS Remotely"选项,即可以开始在Chrome中调试JavaScript代码。点击这个选项的同时会自动打开调试页面 http://localhost:8081/debugger-ui.
    在Chrome的菜单中选择Tools → Developer Tools可以打开开发者工具,也可以通过键盘快捷键来打开(Mac上是Command⌘ + Option⌥ + I
    
    4. 在Chrome浏览器--> source目录下,选中DebuggerWorker.js, 下面有localhost:8081 目录,该目录即为我们的项目文件,可以断点调试。
    
  2. 自动化测试

  3. JS环境

    React Native从0.5.0版本开始已经内置Babel转换器

  4. 性能

  5. 升级

  6. 手势

RN常用组件
  1. Button & Switch & Slide & Text & View & TouchableHightlight&TouchableOpacity

    
    
  2. DatePickerIOS

  3. Image

  4. TextInput

  5. RefreshControl

RN列表
  1. ScrollView
  2. SectionList
  3. ListView
  4. ListView.DataSource
  5. FlatList
RN导航栏
  1. NavigatorIOS
  2. TabBarIOS
  3. SegmentedControlIOS
List组件

参考文献:https://blog.csdn.net/lx520aa/article/details/77140737

常见报错踩坑
  1. 使用cnpm导致报错

    安装完node后建议设置npm镜像以加速后面的过程(或使用科学上网工具)。注意:不要使用cnpm!cnpm安装的模块路径比较奇怪,packager不能正常识别!

    有一个项目一直跑不起来,报错也非常奇怪,找不到bundle文件。因为我采用的是cnpm,所以一直无法解决,多次删除node_modules文件仍然无法解决,于是尝试使用npm后发现项目能够正常启动。

  2. 清除缓存

    有一个项目一直报错,无法找到入口文件,无法匹配NavigatorIOS等组件,我删除该组件引用后仍然报错,多次CMD+K clean后,watchman 更新后,仍然无法报错,而且多次是 项目loading 到70%就没法再次运行下去,因此使用以下方式清空缓存:

    $ npm start -- --reset-cache
    
    使用该方式清空缓存失败,报8081端口无法启动,因此删除终端,关闭8081端口后,再次运行这段代码清空缓存,OK,完美能够运行起来
    
  3. 8081端口被占用

    仔细看日记发现Xcode无法启动RN项目,8081端口被占用,因此尝试关闭占用该端口的进程后,项目能够重新启动。

  4. 使用VPN导致报错

    参考:https://blog.csdn.net/suyie007/article/details/70768410

    这个哥们因为vpn导致RN报错,无法找到jsbundle。关闭vpn的全局代理后能够正常运行。

  5. 代码层面的报错

    代码层面的报错要好解决得多

  6. over

上一篇下一篇

猜你喜欢

热点阅读