react-native官方开发文档

更多内容参见个人技术博客,无广告欢迎关注

 

将文档重点标记出来,其他的内容到用的时候再去查。

 

View属性

enum('flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-around')

align-items属性适用于所有的flex容器,它是用来设置每个flex元素在侧轴上的默认对齐方式。 align-items和align-content有相同的功能,不过不同点是它是用来让每一个单行的容器居中而不是让整个容器居中。alignItems单行alignContent多行

alignItems决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式

align-items:center 没有效果

align-content:center 容器中多行所有内容都居中了

  • justifyContent 内容对齐,FlexBox主轴方向上的排列方式
    enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly')

在 React Native 中flex的表现和 CSS 有些区别。flex在 RN 中只能为整数值,其具体表现请参考yoga布局引擎的文档,其地址为https://github.com/facebook/yoga

flex为一个正整数时,组件尺寸会具有弹性,并根据具体的 flex 值来按比例分配。比如两个组件在同一个父容器中,一个flex为 2,另一个flex为 1,则两者的尺寸比为 2:1。

flex为 0 时,组件尺寸由widthheight决定,此时不再具有弹性。

flex为-1 时,组件尺寸一般还是由widthheight决定。但是当空间不够时,组件尺寸会收缩到minWidthminHeight所设定的值。

flexGrowflexShrinkflexBasis和在 CSS 上表现一致。

主容器的成员从左到右横向布局,而非默认的从上到下纵向布局

enum('row', 'row-reverse', 'column', 'column-reverse')

enum('absolute', 'relative')

transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])

object: {perspective: number}, ,

object: {rotate: string}, ,

object: {rotateX: string}, ,

object: {rotateY: string}, ,

object: {rotateZ: string}, ,

object: {scale: number}, ,

object: {scaleX: number}, ,

object: {scaleY: number}, ,

object: {translateX: number}, ,

object: {translateY: number}, ,

object: {skewX: string}, ,

object: {skewY: string}

 

 

 

Text属性(继承View)

head  "...efg"

middle  "ab...yz"

tail "abcd..."

clip  "abc"

textShadowOffset: object: {width: number,height: number}

colorcolor

fontSize: number

fontStyle: enum('normal', 'italic')

fontWeight: enum('normal', 'bold', '100', '900')

lineHeight: number

textAlign: enum('auto', 'left', 'right', 'center', 'justify')
textDecorationLine: enum('none', 'underline', 'line-through', 'underline line-through')

textShadowColorcolor

fontFamily: string

textShadowRadius: number

includeFontPadding: bool (Android)
Android在默认情况下会为文字额外保留一些padding,以便留出空间摆放上标或是下标的文字。对于某些字体来说,这些额外的padding可能会导致文字难以垂直居中。如果你把
textAlignVertical设置为center之后,文字看起来依然不在正中间,那么可以尝试将本属性设置为false。默认值为true。

textAlignVertical: enum('auto', 'top', 'bottom', 'center') (Android)

letterSpacing: number 字母间距 default is 0

textDecorationColorcolor (iOS)

textDecorationStyle: enum('solid', 'double', 'dotted', 'dashed') (iOS)

textTransform: enum('none', 'uppercase', 'lowercase', 'capitalize')

writingDirection: enum('auto', 'ltr', 'rtl') (iOS)

 

  • testID 用来在端到端测试中定位此视图
  • selectionColor highlight color of the text (Android)
  • adjustsFontSizeToFit  (iOS) 指定字体是否随着给定样式的限制而自动缩放fontFamily
  • minimumFontScale  (iOS) 当adjustsFontSizeToFit开启时,指定最小的缩放比 0.5

 

 

 

 

TextInput

<TextInput placeholder="placeholder" onChangeText={(text) => this.setState({text})}/>

react 中的 onChange 对应的是 rn 中的 onChangeText,onSubmitEditing会在文本被提交后(用户按下软键盘上的提交键)调用

TextInput在安卓上默认有一个底边框,同时会有一些padding。如果要想使其看起来和iOS上尽量一致,则需要设置padding: 0

又又,在安卓上长按选择文本会导致windowSoftInputMode设置变为adjustResize,这样可能导致绝对定位的元素被键盘给顶起来。要解决这一问题你需要在AndroidManifest.xml中明确指定合适的windowSoftInputModehttps://developer.android.com/guide/topics/manifest/activity-element.html )值,或是自己监听事件来处理布局变化。

在大多数情况下,我们推荐使用 受控组件 来实现表单。 在受控组件中,表单数据由 React 组件处理。如果让表单数据由 DOM 处理时,替代方案为使用非受控组件

属性说明

如果为true,在componentDidMount后会获得焦点(becomeFirstResponder

enum('never', 'while-editing', 'unless-editing', 'always')

是否要在文本框右侧显示“清除”按钮。仅在单行模式下可用。默认值为never

如果为true,每次开始输入的时候都会清除文本框的内容。

设置 text input 内能被转化为可点击URL的数据的类型。当且仅当multiline={true}editable={false}时起作用。默认情况下不检测任何数据类型可接受一个类型值或类型值数组。

dataDetectorTypes的可用值有:

  • 'phoneNumber'
  • 'link'
  • 'address'
  • 'calendarEvent'
  • 'none'
  • 'all'

如果为true,键盘会在文本框内没有文字的时候禁用确认按钮。默认值为false。

图片必须放置在/android/app/src/main/res/drawable目录下

<TextInput inlineImageLeft='search_icon' />

指定键盘的颜色 iOS enum('default', 'light', 'dark')

决定弹出何种软键盘类型,譬如numeric(纯数字键盘)。

See screenshots of all the types here.

这些值在所有平台都可用:

  • default
  • number-pad
  • decimal-pad
  • numeric
  • email-address
  • phone-pad

下面的值仅iOS可用:

  • ascii-capable
  • numbers-and-punctuation
  • url
  • name-phone-pad
  • twitter
  • web-search

下面的值仅Android可用:

  • visible-password

React中原生事件{ nativeEvent: { eventCount, target, text} },ReactNative中是onChangeText

当文本框内容变化时调用此回调函数,改变后的文字内容会作为参数传递

获取 { nativeEvent: { target } }

回调参数 { nativeEvent: { key: keyValue } }

当组件加载或者布局变化的时候调用{ nativeEvent: {layout: {x, y, width, height}, target } }

-> scrollViewDidScroll { nativeEvent: { contentOffset: { x, y } } }

{ nativeEvent: { selection: { start, end } } }

此回调函数当软键盘的确定/提交按钮被按下的时候调用此函数。如果multiline={true},此属性不可用

决定“确定”按钮显示的内容。在Android上你还可以使用returnKeyLabel

下列这些选项是跨平台可用的:

  • done
  • go
  • next
  • search
  • send

下列这些选项仅Android可用:

  • none
  • previous

下列这些选项仅iOS可用:

  • default
  • emergency-call
  • google
  • join
  • route
  • yahoo

文本框中的文字内容。 TextInput是一个受约束的(Controlled)的组件,意味着如果提供了value属性,原生值会被强制与value属性保持一致。在大部分情况下这都工作的很好,不过有些情况下会导致一些闪烁现象——一个常见的原因就是通过不改变value来阻止用户进行编辑。如果你希望阻止用户输入,可以考虑设置editable={false};如果你是希望限制输入的长度,可以考虑设置maxLength属性,这两个属性都不会导致闪烁。

方法

  • clear()
  • isFocused()

 

 

 

 

Button

一般用TouchableOpacity定制button,视频教程如何制作一个按钮讲述了完整的过程,或 github.com 搜索 'react native button'

 

 

 

 

 

Picker

<Picker selectedValue={this.state.language}

style={{ height: 50, width: 100 }}

onValueChange={(itemValue, itemIndex) => this.setState({language: itemValue})}>

<Picker.Item label="Java" value="java" />

<Picker.Item label="JavaScript" value="js" />

</Picker>

 

'dialog': 显示一个模态对话框。默认选项。

'dropdown': 以选择器所在位置为锚点展开一个下拉框。

对话框标题

 

 

 

Slider

 

 

 

 

Switch

受控组件 你必须使用onValueChange回调来更新value属性以响应用户的操作

 

 

 

 

 

Touchable 系列组件

这个组件的样式是固定的。所以如果它的外观并不怎么搭配你的设计,那就需要使用TouchableOpacity或是TouchableNativeFeedback组件来定制自己所需要的按钮,视频教程如何制作一个按钮讲述了完整的过程。或者你也可以在 github.com 网站上搜索 'react native button' 来看看社区其他人的作品。

具体使用哪种组件,取决于你希望给用户什么样的视觉反馈:

  • 一般来说,你可以使用TouchableHighlight来制作按钮或者链接。注意此组件的背景会在用户手指按下时变暗。
  • 在 Android 上还可以使用TouchableNativeFeedback,它会在用户手指按下时形成类似墨水涟漪的视觉效果。
  • TouchableOpacity会在用户手指按下时降低按钮的透明度,而不会改变背景的颜色。

某些场景中你可能需要检测用户是否进行了长按操作。可以在上面列出的任意组件中使用onLongPress属性来实现。

 

 

 

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

FlatList组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同。

FlatList更适于长列表数据,且元素个数可以增删。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。

FlatList组件必须的两个属性是datarenderItemdata是列表的数据源,而renderItem则从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染。

 

 

 

使用 Fetch

React Native 提供了和 web 标准一致的Fetch API,用于满足开发者访问网络的需求。

 

网络请求天然是一种异步操作(译注:同样的还有asyncstorage,请不要再问怎样把异步变成同步!无论在语法层面怎么折腾,它们的异步本质是无法变更的。异步的意思是你应该趁这个时间去做点别的事情,比如显示 loading,而不是让界面卡住傻等)。Fetch 方法会返回一个Promise,这种模式可以简化异步风格的代码

 

你也可以在 React Native 应用中使用 ES2017 标准中的async/await 语法:

 

 

 

 

事件

 

View

作为创建 UI 时最基础的组件,View 是一个支持 Flexbox 布局、样式、一些触摸处理、和一些无障碍功能的容器,并且它可以放到其它的视图里,也可以有任意多个任意类型的子视图。不论在什么平台上,View 都会直接对应一个平台的原生视图。

 

合成触摸事件

用于 View 响应属性 (例如, onResponderMove), 合成触摸事件采用以下的格式:

  • nativeEvent
  • changedTouches - 从上一次事件以来的触摸事件数组。
  • identifier - 触摸事件的 ID。
  • locationX - 触摸事件相对元素位置的 X 坐标。
  • locationY - 触摸事件相对元素位置的 Y 坐标。
  • pageX - 触摸事件相对根元素位置的 X 坐标。
  • pageY - 触摸事件相对根元素位置的 Y 坐标。
  • target - 接收触摸事件的元素 ID.
  • timestamp - 触摸事件的时间标记,用来计算速度.
  • touches - 屏幕上所有当前触摸事件的数组.

 

查看 Props

设置这个视图是否要响应 touch start 事件

View.props.onStartShouldSetResponder: (event) => [true | false], 其中 event 是一个合成触摸事件

当组件挂载或者布局变化的时候调用,参数为:{nativeEvent: { layout: {x, y, width, height}}}

这个事件会在布局计算完成后立即调用一次,不过收到此事件时新的布局可能还没有在屏幕上呈现,尤其是一个布局动画正在进行中的时候。

如果父视图想要阻止子视图响应 touch move 事件时,它就应该设置这个方法并返回true

其他某个视图想要成为事件的响应者,并要求这个视图放弃对事件的响应时,就会调用这个函数。如果允许释放响应,就返回true

用于控制当前视图是否可以作为触控事件的目标。

auto:视图可以作为触控事件的目标。

none:视图不能作为触控事件的目标。

box-none:视图自身不能作为触控事件的目标,但其子视图可以。类似于你在 CSS 中这样设置:

'box-only':视图自身可以作为触控事件的目标,但其子视图不能。类似于你在 CSS 中这样设置:

例如:.box-none { pointer-events: none; } .box-none * { pointer-events: all; }

决定这个视图是否要先离屏渲染再进行半透明度处理,来确保颜色和混合效果正确,默认值(false)

 

 

 

 

Image

用于显示多种不同类型图片的 React 组件,包括网络图片、静态资源、临时的本地图片、以及本地磁盘上的图片(如相册)等

请注意对于网络和 base64 数据的图片需要手动指定尺寸!

在 Android 上支持 GIF 和 WebP 格式图片

默认情况下 Android 是不支持 GIF 和 WebP 格式的。你需要在android/app/build.gradle文件中根据需要手动添加以下模块:

dependencies {
 // 如果你需要支持WebP格式,包括WebP动图
 
compile 'com.facebook.fresco:animated-webp:1.10.0'
 
compile 'com.facebook.fresco:webpsupport:1.10.0'

}

属性

overflow: enum('visible', 'hidden')

resizeMode: ImageResizeMode(containcoverstretchcenterrepeat)

overlayColor在不能圆角透明时,设置overlayColor和背景色一致

决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小。

cover: 等比缩放不留空白,铺满屏幕

contain: 等比缩放可能空白

stretch

repeat

center

当图片实际尺寸和容器样式尺寸不一致时,决定以怎样的策略来调整图片的尺寸。默认为auto

auto: 使用启发式算法来在resizescale中自动决定。

resize: 在图片解码之前,使用软件算法对其在内存中的数据进行修改。当图片尺寸比容器尺寸大得多时,应该优先使用此选项。

scale: 对图片进行缩放。和resize相比,scale速度更快(一般有硬件加速),而且图片质量更优。在图片尺寸比容器尺寸小或者只是稍大一点时,应该优先使用此选项。

关于resizescale的详细说明请参考http://frescolib.org/docs/resizing.html

  • onProgress {nativeEvent: {loaded, total}} (iOS)

方法

静态图片引用语法require('./image.jpg')所返回的资源 id 或是一个ImageSource.

 

 

 

ScrollView

ScrollView必须有一个确定的高度才能正常工作,因为它实际上所做的就是将一系列不确定高度的子组件装进一个确定高度的容器(通过滚动操作)。一般来说我们会给ScrollView设置flex: 1以使其自动填充父容器的空余空间,但前提条件是所有的父容器本身也设置了flex或者指定了高度,否则就会导致无法正常滚动

属性

用户拖拽滚动视图的时候,是否要隐藏软键盘。

'none' (默认值),拖拽时不隐藏软键盘。

'on-drag',当拖拽开始的时候隐藏软键盘。

'interactive',软键盘伴随拖拽操作同步地消失,并且如果往上滑动会恢复键盘iOS

很多人反应TextInput无法自动失去焦点/需要点击多次切换到其他组件等等问题,其关键都是需要将TextInput放到ScrollView中再设置本属性

'never' (默认值),点击TextInput以外的子组件会使当前的软键盘收起。此时子元素不会收到点击事件。

'always',键盘不会自动收起,ScrollView也不会捕捉点击事件,但子组件可以捕获。

'handled',当点击事件被子组件捕获时,键盘不会自动收起。这样切换TextInput时键盘可以保持状态。多数带有TextInput的情况下你应该选择此项。

stickyHeaderIndices={[0]}会让第一个成员固定在滚动视图顶端,不能和horizontal={true}一起使用。

默认false,是否水平排列

如果你的ScrollView或FlatList的头部出现莫名其妙的空白,尝试将此属性置为false

方法

scrollTo({x: 0, y: 0, animated: true})

scrollTo({x: 0, y: 0, duration: 500})

scrollToEnd({animated: boolean, duration: number});

 

 

 

 

 

 

 

FlatList

如果需要分组//区(section),请使用<SectionList>

  • 对于MyListItem组件来说,其onPressItem属性使用箭头函数而非 bind 的方式进行绑定,使其不会在每次列表重新 render 时生成一个新的函数,从而保证了 props 的不变性(当然前提是 idselectedtitle也没变),不会触发自身无谓的重新 render。换句话说,如果你是用 bind 来绑定onPressItem,每次都会生成一个新的函数,导致 props 在===比较时返回 false,从而触发自身的一次不必要的重新 render。
  • FlatList指定extraData={this.state}属性,是为了保证state.selected变化时,能够正确触发FlatList的更新。如果不指定此属性,则FlatList不会触发更新,因为它是一个PureComponent,其 props 在===比较中没有变化则不会触发更新。
  • keyExtractor属性指定使用 id 作为列表每一项的 key。

 

本组件实质是基于<VirtualizedList>组件的封装,继承了其所有 props(也包括所有<ScrollView>)的 props)

注意事项:

  • 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。
  • 本组件继承自PureComponent而非通常的Component,这意味着如果其props浅比较中是相等的,则不会重新渲染。所以请先检查你的renderItem函数所依赖的props数据(包括data属性以及可能用到的父组件的 state),如果是一个引用类型(Object 或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的 Object 或者数组中),然后再修改其值,否则界面很可能不会刷新。(译注:这一段不了解的朋友建议先学习下js 中的基本类型和引用类型。)
  • 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。
  • 默认情况下每行都需要提供一个不重复的 key 属性。你也可以提供一个keyExtractor函数来生成 key。

本组件如果嵌套在其他同滚动方向的 FlatList 中,则不会继承ScrollView 的 Props

属性

可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度

  getItemLayout={(data, index) => (
    {
length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
  )}

方法

 

 

 

 

ActionSheetIOS

static showActionSheetWithOptions(options, callback)

options (字符串数组) - 一组按钮的文字(必选)

cancelButtonIndex (整型) - 取消性质的按钮在options中的位置(索引)

destructiveButtonIndex (整型) - 删除性质的按钮在options中的位置(索引)

title (字符串) - 弹出框顶部的标题

message (字符串) - 弹出框顶部标题下方的信息

 

static showShareActionSheetWithOptions(options, failureCallback, successCallback)

 

 

 

AlertIOS

AlertIOS.alert( '温馨提示', '您的话费余额不足请充值.' );

 

 

 

 

DatePickerIOS

 

 

 

 

ImagePickerIOS

方法

 

 

 

native-navigation

react-native-navigation

我们推荐使用跨平台的react-navigation中的 DrawerNavigation 来代替此组件。

 

 

PushNotificationIOS

本模块帮助你处理应用的推送通知,包括权限控制以及应用图标上的角标数(未读消息数)。

要使用推送通知功能,首先在苹果后台配置推送通知服务并且准备好服务端的系统。

首先请手动链接PushNotificationIOS的库:

  • node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj文件拖到Xcode界面中
  • 在Xcode的Link Binary With Libraries中添加libRCTPushNotification.a

然后你需要在AppDelegate中启用推送通知的支持以及注册相应的事件。

查看方法

 

 

 

BackHandler

Android:监听后退按钮事件。如果没有添加任何监听函数,或者所有的监听函数都返回 false,则会执行默认行为,退出应用。

方法

 

 

 

DatePickerAndroid

 

 

PermissionsAndroid

PermissionsAndroid 可以访问Android M(也就是6.0)开始提供的权限模型。有一些权限写在AndroidManifest.xml就可以在安装时自动获得,但有一些“危险”的权限则需要弹出提示框供用户选择。本API即用于后一种情形。

在低于Android 6.0的设备上,权限只要写在AndroidManifest.xml里就会自动获得,此情形下check会始终返回true和而request方法将始终解析为PermissionsAndroid.RESULTS.GRANTED

如果用户之前拒绝过你的某项权限请求,那么系统会建议你显示一个为什么需要这个权限的“详细解释”(rationale参数)。如果用户之前拒绝过,那么当你再次申请的时候,弹出的就可能不是原先的申请信息,而是rationale参数里提供的进一步解释。

 

 

需要提示用户的权限都以常量形式列在PermissionsAndroid.PERMISSIONS中:

  • READ_CALENDAR: 'android.permission.READ_CALENDAR'
  • WRITE_CALENDAR: 'android.permission.WRITE_CALENDAR'
  • CAMERA: 'android.permission.CAMERA'
  • READ_CONTACTS: 'android.permission.READ_CONTACTS'
  • WRITE_CONTACTS: 'android.permission.WRITE_CONTACTS'
  • GET_ACCOUNTS: 'android.permission.GET_ACCOUNTS'
  • ACCESS_FINE_LOCATION: 'android.permission.ACCESS_FINE_LOCATION'
  • ACCESS_COARSE_LOCATION: 'android.permission.ACCESS_COARSE_LOCATION'
  • RECORD_AUDIO: 'android.permission.RECORD_AUDIO'
  • READ_PHONE_STATE: 'android.permission.READ_PHONE_STATE'
  • CALL_PHONE: 'android.permission.CALL_PHONE'
  • READ_CALL_LOG: 'android.permission.READ_CALL_LOG'
  • WRITE_CALL_LOG: 'android.permission.WRITE_CALL_LOG'
  • ADD_VOICEMAIL: 'com.android.voicemail.permission.ADD_VOICEMAIL'
  • USE_SIP: 'android.permission.USE_SIP'
  • PROCESS_OUTGOING_CALLS: 'android.permission.PROCESS_OUTGOING_CALLS'
  • BODY_SENSORS: 'android.permission.BODY_SENSORS'
  • SEND_SMS: 'android.permission.SEND_SMS'
  • RECEIVE_SMS: 'android.permission.RECEIVE_SMS'
  • READ_SMS: 'android.permission.READ_SMS'
  • RECEIVE_WAP_PUSH: 'android.permission.RECEIVE_WAP_PUSH'
  • RECEIVE_MMS: 'android.permission.RECEIVE_MMS'
  • READ_EXTERNAL_STORAGE: 'android.permission.READ_EXTERNAL_STORAGE'
  • WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE'

 

返回值都以常量形式记录在PermissionsAndroid.RESULTS中:

  • GRANTED: 'granted', 表示用户已授权
  • DENIED: 'denied', 表示用户已拒绝
  • NEVER_ASK_AGAIN: 'never_ask_again',表示用户已拒绝,且不愿被再次询问。

方法

 

 

 

ViewPagerAndroid

一个允许在子视图之间左右翻页的容器。每一个 ViewPagerAndroid 的子容器会被视作一个单独的页,并且会被拉伸填满 ViewPagerAndroid。所有的子视图都必须是纯 View,而不能是自定义的复合容器。

当在页间切换时(不论是由于动画还是由于用户在页间滑动/拖拽)执行,event.nativeEvent

position 从左数起第一个当前可见的页面的下标。

offset 一个在[0,1]之内的范围(可以等于0或1),代表当前页面切换的状态。值 x 表示现在"position"所表示的页有(1 - x)的部分可见,而下一页有 x 的部分可见。

 

 

 

Alert

alert(title, message?, buttons?, options?, type?)

iOS 上你可以指定任意数量的按钮。每个按钮还都可以指定自己的样式,此外还可以指定提示的类别。

Android 上最多能指定三个按钮,这三个按钮分别具有中间态消极态积极态的概念。

 

 

 

 

Animated

配置动画

Animated提供了三种动画类型。每种动画类型都提供了特定的函数曲线,用于控制动画值从初始值变化到最终值的变化过程:

大多数情况下你应该使用timing()。默认情况下,它使用对称的 easeInOut 曲线,将对象逐渐加速到全速,然后通过逐渐减速停止结束。

使用动画

通过在动画上调用start()来启动动画。 start()需要一个 完成 回调函数,当动画完成时将会调用它。如果动画运行正常,则将通过{finished:true}触发回调。如果动画是因为调用了stop()而结束(例如,因为它被手势或其他动画中断),则它会收到{finished:false}

启用原生动画驱动

使用原生动画,我们会在开始动画之前将所有关于动画的内容发送到原生代码,从而使用原生代码在 UI 线程上执行动画,而不是通过对每一帧的桥接去执行动画。一旦动画开始,JS 线程就可以在不影响动画效果的情况下阻塞(去执行其他任务)掉了。

您可以通过在动画配置中指定useNativeDriver:true 来使用原生动画驱动。你可以在动画文档 中看到更详细的解释。

自定义动画组件

组件必须经过特殊处理才能用于动画。所谓的特殊处理主要是指把动画值绑定到属性上,并且在一帧帧执行动画时避免 react 重新渲染和重新调和的开销。此外还得在组件卸载时做一些清理工作,使得这些组件在使用时是安全的。

Animated中默认导出了以下这些可以直接使用的动画组件,当然它们都是通过使用上面这个方法进行了封装:

  • Animated.Image
  • Animated.ScrollView
  • Animated.Text
  • Animated.View

组合动画

动画还可以使用组合函数以复杂的方式进行组合:

动画也可以通过将toValue设置为另一个动画的Animated.Value来简单的链接在一起。请参阅动画指南中的跟踪动态值值。

默认情况下,如果一个动画停止或中断,则组合中的所有其他动画也会停止。

合成动画值

你可以使用加减乘除以及取余等运算来把两个动画值合成为一个新的动画值:

插值

interpolate()函数允许输入范围映射到不同的输出范围。默认情况下,它将推断超出给定范围的曲线,但也可以限制输出值。它默认使用线性插值,但也支持缓动功能。

你可以在动画文档中了解到更多。

处理手势和其他事件

手势,如平移或滚动,以及其他事件可以使用Animated.event()直接映射到动画值。这是通过结构化映射语法完成的,以便可以从复杂的事件对象中提取值。第一层参数是一个数组,你可以在其中指定多个参数映射,这种映射可以是嵌套的对象。

例如,在使用水平滚动手势时,为了将event.nativeEvent.contentOffset.x映射到scrollXAnimated.Value),您需要执行以下操作:

 

 

 

 

CameraRoll

CameraRoll模块提供了访问本地相册的功能。在 iOS 上使用这个模块之前,你需要先链接RCTCameraRoll库,具体做法请参考链接原生库文档。

译注:本模块只提供了基本的访问图片的功能,并没有提供相册界面。对于多数开发者来说,可能第三方的react-native-image-crop-picker的功能更为完整易用(可多选、压缩、裁剪等)。

权限

从 iOS 10 开始,访问相册需要用户授权。你需要在Info.plist中添加一条名为NSPhotoLibraryUsageDescription的键,然后在其值中填写向用户请求权限的具体描述。编辑完成后这个键在 Xcode 中实际会显示为Privacy - Photo Library Usage Description

从 iOS 11 开始,如果需要保存图片,则需要额外申请用户授权。你需要在Info.plist中添加一条名为NSPhotoLibraryAddUsageDescription的键,然后在其值中填写向用户请求权限的具体描述。编辑完成后这个键在 Xcode 中实际会显示为Privacy - Photo Library Additions Usage Description。而名为NSPhotoLibraryUsageDescription的键此时仅控制相册的读取。具体说明请翻阅官方文档搜索相关键值。

 

 

 

 

Clipboard

Clipboard组件可以在 iOS Android 的剪贴板中读写内容。

 

 

 

 

 

 

 

Dimensions

本模块用于获取设备屏幕的宽高。

尽管尺寸信息立即就可用,但它可能会在将来被修改(譬如设备的方向改变),所以基于这些常量的渲染逻辑和样式应当每次 render 之后都调用此函数,而不是将对应的值保存下来。(举例来说,你可能需要使用内联的样式而不是在StyleSheet中保存相应的尺寸)。

var {height, width} = Dimensions.get('window');

Dimensions.get('screen')

 

 

 

 

 

KeyboardAvoidingView

本组件用于解决一个常见的尴尬问题:手机上弹出的键盘常常会挡住当前的视图。本组件可以自动根据键盘的位置,调整自身的 position 或底部的 padding,以避免被遮挡。

 

 

 

 

Linking

仅用在原生代码的项目

Linking提供了一个通用的接口来与传入和传出的 App 链接进行交互。

要了解更多如何在 Android 上支持深度链接的说明,请参阅Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links.如果要在现有的 MainActivity 中监听传入的 intent,那么需要在AndroidManifest.xml中将 MainActivity launchMode设置为singleTask。相关解释可参考<activity>文档。

<activity android:name=".MainActivity" android:launchMode="singleTask">

对于 iOS 来说,如果要在 App 启动后也监听传入的 App 链接,那么首先需要在项目中链接RCTLinking,具体步骤请参考手动链接这篇文档,然后需要在AppDelegate.m中增加代码

 

然后你的 React 组件就可以监听Linking的相关事件:

要启动一个链接相对应的应用(打开浏览器、邮箱或者其它的应用),只需调用:

Linking.openURL(url).catch(err => console.error('An error occurred', err));

getInitialURL() 如果应用是被一个链接调起的,则会返回相应的链接地址,否则返回null

 

 

 

 

 

 

Modal

Modal 组件是一种简单的覆盖在其他视图之上显示内容的方式。

slide 从底部滑入滑出

fade 淡入淡出

none 没有动画,直接蹦出来

 

 

 

 

 

 

PixelRatio

PixelRatio 类提供了访问设备的像素密度的方法

返回设备的像素密度

PixelRatio.get() === 1

mdpi Android devices

PixelRatio.get() === 1.5

hdpi Android devices

PixelRatio.get() === 2   @2x

iPhone 4, 4S

iPhone 5, 5C, 5S

iPhone 6, 7, 8

xhdpi Android devices

PixelRatio.get() === 3   @3x

iPhone 6 Plus, 7 Plus, 8 Plus

iPhone X, XS, XS Max

Pixel, Pixel 2

xxhdpi Android devices

PixelRatio.get() === 3.5

Nexus 6

Pixel XL, Pixel 2 XL

xxxhdpi Android devices

返回字体大小缩放比例

将一个布局尺寸(dp)转换为像素尺寸(px) 返回一个整数值

 

 

 

 

RefreshControl

这一组件可以用在ScrollView或FlatList内部,为其添加下拉刷新的功能。当ScrollView处于竖直方向的起点位置(scrollY: 0),此时下拉会触发一个onRefresh事件

refreshing是一个受控属性, 所以必须在onRefresh函数中设置为true,否则loading指示器会立即停止

 

 

 

 

 

StatusBar

控制应用状态栏的组件

和导航器一起使用的注意事项

由于StatusBar可以在任意视图中加载,且后加载的设置会覆盖先前的设置。因此在配合导航器使用时,请务必考虑清楚StatusBar的放置顺序。

静态API

有些场景并不适合使用组件,因此StatusBar也暴露了一个静态API。然而不推荐大家同时通过静态API和组件来定义相同的属性,因为静态API定义的属性值在后续的渲染中会被组件中定义的值所覆盖。

属性

方法

enum('default', 'light-content', 'dark-content')

查看类型定义

enum('none', 'fade', 'slide')

 

 

 

 

 

WebView

webview已经废弃请使用:react-native-community/react-native-webview 

设置 js 字符串,在网页加载之前注入的一段 JS 代码

在网页加载完成之后,还可以主动调用此方法(以 ref 形式调用)继续给 WebView 注入 JS 代码。注入后会立即执行

控制是否启用 JavaScript。仅在安卓下使用,因为 IOS 默认为启用 JavaScript。默认值为true

指定混合内容模式。即 WebView 是否应该允许安全链接(https)页面中加载非安全链接(http)的内容,

never (默认) - WebView 不允许安全链接页面中加载非安全链接的内容

always - WebView 允许安全链接页面中加载非安全链接的内容。

compatibility - WebView 会尽量和浏览器当前对待此情况的行为一致

在 webview 内部的网页中调用 window.postMessage 方法时可以触发此属性对应的函数,从而实现网页和 RN 之间的数据交换。 设置此属性的同时会在 webview 中注入一个 postMessage 的全局函数并覆盖可能已经存在的同名实现。

网页端的 window.postMessage 只发送一个参数 data,此参数封装在 RN 端的 event 对象中, event.nativeEvent.data。data 只能是一个字符串。

http:// https://  *

在 WebView 中载入一段静态的 html 代码或是一个 url(还可以附带一些 header 选项)。注意如果是载入html代码,则需要设置originWhitelist,比如可以设为["*"]来允许运行本地代码

Load uri

  • uri (string) - {uri: 'https://github.com/facebook/react-native'}
  • method (string) - GET and POST.
  • headers (object) - Additional HTTP headers to send with the request. On Android only GET
  • body (string) - The HTTP body to send with the request. This must be a valid UTF-8 string, and will be sent exactly as specified, with no additional encoding (e.g. URL-escaping or base64) applied. On Android, this can only be used with POST requests.

Static HTML

  • html (string) - A static HTML page to display in the WebView.
  • baseUrl (string) - The base URL to be used for any relative links in the HTML.

设置true的时候会使用新的WKWebView来代替老的UIWebView

方法

 

 

 

 

 

 

特定平台代码

React Native 提供了两种方法来区分平台:

 

Platform 模块

Platform.OS iOS 上会返回ios,而在 Android 设备或模拟器上则会返回android

Platform.select()可以以 Platform.OS key,从传入的对象中返回对应平台的值

 

检测 Android 版本

在 Android 上,Version属性是一个数字,表示 Android 的 api level:

if (Platform.Version === 25) {
 
  console.log("Running on Nougat!");
}

检测 iOS 版本

在 iOS 上,Version属性是-[UIDevice systemVersion]的返回值,具体形式为一个表示当前系统版本的字符串。比如可能是"10.3"

const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 9) {
 
  console.log("Work around a change in behavior");
}

特定平台扩展名

当不同平台的代码逻辑较为复杂时,最好是放到不同的文件里,这时候我们可以使用特定平台扩展名。React Native 会检测某个文件是否具有.ios.或是.android.的扩展名,然后根据当前运行的平台自动加载正确对应的文件。

比如你可以在项目中创建下面这样的组件:

BigButton.ios.js
BigButton.android.js

然后去掉平台扩展名直接引用:

import BigButton from './BigButton';

React Native 会根据运行平台的不同自动引入正确对应的组件

 

如果你还希望在 web 端复用 React Native 的代码,那么还可以使用.native.js的扩展名。此时 iOS Android 会使用BigButton.native.js文件,而 web 端会使用BigButton.js。(注意目前官方并没有直接提供 web 端的支持,请在社区搜索第三方方案)

 

 

 

 

 

 

静态图片资源

React Native 提供了一个统一的方式来管理 iOS 和 Android 应用中的图片。要往 App 中添加一个静态图片,只需把图片文件放在代码文件夹中某处,然后像下面这样去引用它:

<Image source={require('./my-icon.png')} />

图片文件的查找会和 JS 模块的查找方式一样。在上面的这个例子里,是哪个组件引用了这个图片,Packager 就会去这个组件所在的文件夹下查找my-icon.png。并且,如果你有my-icon.ios.pngmy-icon.android.png,Packager 就会根据平台而选择不同的文件。

你还可以使用@2x@3x这样的文件名后缀,来为不同的屏幕精度提供图片。比如下面这样的代码结构:

.
├──
button.js
└──
img
    ├── check
.png
    ├── check@
2x.png
    └── check@
3x.png

并且button.js里有这样的代码:

<Image source={require('./img/check.png')} />

Packager 会打包所有的图片并且依据屏幕精度提供对应的资源。iPhone 7 会使用check@2x.png,而 iPhone 7 plus 或是 Nexus 5 上则会使用check@3x.png。如果没有图片恰好满足屏幕分辨率,则会自动选中最接近的一个图片。

注意事项:

  • 如果你添加图片的时候 packager 正在运行,可能需要重启 packager 以便能正确引入新添加的图片。
  • 为了使新的图片资源机制正常工作,require 中的图片名字必须是一个静态字符串(不能使用变量!因为 require 是在编译时期执行,而非运行时期执行!)
  • 通过这种方式引用的图片资源包含图片的尺寸(宽度,高度)信息,如果你需要动态缩放图片(例如,通过 flex),你可能必须手动在 style 属性设置{ width: null, height: null }

 

上面描述的require语法也可以用来静态地加载你项目中的声音、视频或者文档文件。大多数常见文件类型都支持,包括.mp3.wav.mp4.mov.htm 和 .pdf等(完整列表请看 packager defaults)。

你也可以创建自己的配置文件来支持其他类型的文件。

需要注意的是视频必须指定尺寸而不能使用flex样式,因为我们目前还不能从非图片资源中获取到尺寸信息。对于直接链接到 Xcode 或者 Android 资源文件夹的视频,则不会有这个限制。

 

使用混合 App 的图片资源

如果你在编写一个混合 App(一部分 UI 使用 React Native,而另一部分使用平台原生代码),也可以使用已经打包到 App 中的图片资源(以拖拽的方式放置在 Xcode 的 asset 类目中,或是放置在 Android 的 drawable 目录里)。注意此时只使用文件名,不带路径也不带后缀你需要自己确保图片在应用中确实存在,而且还需要指定尺寸

<Image source={{uri: 'app_icon'}} style={{width: 40, height: 40}} />

 

网络图片

很多要在 App 中显示的图片并不能在编译的时候获得,又或者有时候需要动态载入来减少打包后的二进制文件的大小。这些时候,与静态资源不同的是,你需要手动指定图片的尺寸
<Image source={{
uri: 'https://facebook.github.io/react/logo-og.png'}} style={{width: 400, height: 400}} />

 

iOS 会为同一张图片在相册中保存多个不同尺寸的副本。对于一处 200x200 大小的缩略图,显然不应该选择最高质量的 3264x2448 大小的图片。如果恰好有匹配的尺寸,那么 React Native 会自动为你选好。如果没有,则会选择最接近的尺寸进行缩放,但也至少缩放到比所需尺寸大出 50%,以使图片看起来仍然足够清晰。这一切过程都是自动完成的,所以你不用操心自己去完成这些繁琐且易错的代码。

 

为什么不在所有情况下都自动指定尺寸呢?

在浏览器中,如果你不给图片指定尺寸,那么浏览器会首先渲染一个 0x0 大小的元素占位,然后下载图片,在下载完成后再基于正确的尺寸来渲染图片。这样做的最大问题是 UI 会在图片加载的过程中上下跳动,使得用户体验非常差。

在React Native中我们有意避免了这一行为。如此一来开发者就需要做更多工作来提前知晓远程图片的尺寸(或宽高比),但我们相信这样可以带来更好的用户体验。然而,读取本地静态图片(使用require('./my-icon.png')语法)则无需指定尺寸,因为它们的尺寸在加载时就可以立刻知道。

比如这样一个引用require('./my-icon.png')的实际输出结果可能是:

{"__packager_asset":true,"uri":"my-icon.png","width":591,"height":573}

资源属性source是一个对象

在 React Native 中,另一个值得一提的变动是我们把src属性改为了source属性,而且并不接受字符串,正确的值是一个带有uri属性的对象。

<Image source={{uri: 'something.jpg'}} />

深层次的考虑是,这样可以使我们在对象中添加一些元数据(metadata)。假设你在使用require('./my-icon.png'),那么我们就会在其中添加真实文件路径以及尺寸等信息(这只是举个例子,未来的版本中 require 的具体行为可能会变化)。此外这也是考虑了未来的扩展性,比如我们可能会加入精灵图(sprites)的支持:在输出{uri: ...}的基础上,我们可以进一步输出裁切信息{uri: ..., crop: {left: 10, top: 50, width: 20, height: 40}},这样理论上就可以在现有的代码中无缝支持精灵图的切分。

对于开发者来说,则可以在其中标注一些有用的属性,例如图片的尺寸,这样可以使图片自己去计算将要显示的尺寸(而不必在样式中写死)。请在这一数据结构中自由发挥,存储你可能需要的任何图片相关的信息。

 

背景图片与嵌套写法

开发者们常面对的一种需求就是类似 web 中的背景图(background-image)。要实现这一用例,只需使用<ImageBackground>组件(其 props 与<Image>完全相同),然后把需要背景图的子组件嵌入其中即可。

你可以阅读其文档然后思考你是否有更好更简单的布局方案。注意你必须指定宽高样式。

return (
 
<ImageBackground source={...} style={{width: '100%', height: '100%'}}>
    <
Text>Inside</Text>
  </
ImageBackground>
);

在主线程外解码图片

图片解码有可能会需要超过一帧的时间。在 web 上这是页面掉帧的一大因素,因为解码是在主线程中完成的。然而在 React Native 中,图片解码则是在另一线程中完成的。在实际开发中,一般对图片还没下载完成时的场景都做了处理(添加 loading 等),而图片解码时显示的占位符只占用几帧时间,并不需要你改动代码去额外处理。

 

 

 

 

 

定时器

  • setTimeout, clearTimeout
  • setInterval, clearInterval
  • setImmediate, clearImmediate
  • requestAnimationFrame, cancelAnimationFrame

requestAnimationFrame(fn)setTimeout(fn, 0)不同,前者会在每帧刷新之后执行一次,而后者则会尽可能快的执行(在 iPhone5S 上有可能每秒 1000 次以上)。

setImmediate则会在当前 JavaScript 执行块结束的时候执行,就在将要发送批量响应数据到原生之前。注意如果你在setImmediate的回调函数中又执行了setImmediate,它会紧接着立刻执行,而不会在调用之前等待原生代码。Promise的实现就使用了setImmediate来执行异步调用。

InteractionManager

原生应用感觉如此流畅的一个重要原因就是在互动和动画的过程中避免繁重的操作。在 React Native 里,我们目前受到限制,因为我们只有一个 JavaScript 执行线程。不过你可以用InteractionManager来确保在执行繁重工作之前所有的交互和动画都已经处理完毕。

应用可以通过以下代码来安排一个任务,使其在交互结束之后执行:

InteractionManager.runAfterInteractions(() => {
 
// ...需要长时间同步执行的任务...
});

requestAnimationFrame(): 用来执行在一段时间内控制视图动画的代码

setImmediate/setTimeout/setInterval(): 在稍后执行代码。注意这有可能会延迟当前正在进行的动画。

runAfterInteractions(): 在稍后执行代码,不会延迟当前进行的动画。

触摸处理系统会把一个或多个进行中的触摸操作认定为'交互',并且会将runAfterInteractions()的回调函数延迟执行,直到所有的触摸操作都结束或取消了。

InteractionManager 还允许应用注册动画,在动画开始时创建一个交互“句柄”,然后在结束的时候清除它。

var handle = InteractionManager.createInteractionHandle();
// 执行动画... (`runAfterInteractions`中的任务现在开始排队等候)
// 在动画完成之后
InteractionManager.clearInteractionHandle(handle);
// 在所有句柄都清除之后,现在开始依序执行队列中的任务

务必在卸载组件前清除定时器!

我们发现很多 React Native 应用发生致命错误(闪退)是与计时器有关。具体来说,是在某个组件被卸载(unmount)之后,计时器却仍然在运行。要解决这个问题,只需铭记在unmount组件时清除(clearTimeout/clearInterval)所有用到的定时器即可:

import React, { Component } from "react";

export default class Hello extends Component {
  componentDidMount() {
   
this.timer = setTimeout(() => {
     
console.log("把一个定时器的引用挂在this上");
    },
500);
  }
  componentWillUnmount() {

    // 如果存在this.timer,则使用clearTimeout清空。
   
// 如果你使用多个timer,那么用多个变量,或者用个数组来保存引用,然后逐个clear
   
this.timer && clearTimeout(this.timer);
  }
}

 

 

 

 

调试

开启调试的快捷键

iOS -> Connect Hardware Keyboard

 

访问 App 内的开发菜单

你可以通过摇晃设备或是选择 iOS 模拟器的"Hardware"菜单中的"Shake Gesture"选项来打开开发菜单。

 iOS -> Command⌘ + D    Android -> Command⌘ + M

在发布(production)版本中开发者菜单将无法使用

 

刷新 JavaScript

iOS -> Command⌘ + R ,Android -> RR 

自动刷新

Enable Live Reload  +   Enable Hot Reloading

红屏错误   console.error()

黄屏警告 console.warn()

 

Chrome 开发者工具

开启 Debug JS Remotely -> http://localhost:8081/debugger-ui

在 Chrome 的菜单中选择Tools → Developer Tools可以打开开发者工具,也可以通过键盘快捷键来打开(Mac 上是Command + Option + I,Windows 上是Ctrl + Shift + I或是 F12)。打开有异常时暂停(Pause On Caught Exceptions)选项,能够获得更好的开发体验。

注意:使用 Chrome 调试目前无法观测到 React Native 中的网络请求,你可以使用功能更强大的第三方的react-native-debugger来进行观测。

 

React Developer Tools

安装npm install -g react-devtools

启动react-devtools

性能监测:开发者菜单 -> "Pref Monitor"悬浮层中会显示应用的当前帧数

 

访问控制台日志

react-native log-ios
react-native log-android

 

真机调试

对于 iOS 真机来说,需要打开 RCTWebSocketExecutor.m文件,然后将其中的"localhost"改为你的电脑的 IP 地址,最后启用开发者菜单中的"Debug JS Remotely"选项。

对于 Android 5.0+设备(包括模拟器)来说,将设备通过 USB 连接到电脑上后,可以使用adb命令行工具来设定从设备到电脑的端口转发:adb reverse tcp:8081 tcp:8081

 

 

 

性能

关于“帧”你所需要知道的

iOS 设备提供了每秒 60 的帧率,这就留给了开发者和 UI 系统大约 16.67ms 来完成生成一张静态图片(帧)所需要的所有工作。如果在这分派的 16.67ms 之内没有能够完成这些工作,就会引发‘丢帧’的后果,使界面表现的不够流畅。

开发菜单 ->Show FPS Monitor. 你会注意到有两个不同的帧率.

JS 帧率(JavaScript 线程)

对大多数 React Native 应用来说,业务逻辑是运行在 JavaScript 线程上的。这是 React 应用所在的线程,也是发生 API 调用,以及处理触摸事件等操作的线程。更新数据到原生支持的视图是批量进行的,并且在事件循环每进行一次的时候被发送到原生端,这一步通常会在一帧时间结束之前处理完(如果一切顺利的话)。如果 JavaScript 线程有一帧没有及时响应,就被认为发生了一次丢帧。 例如,你在一个复杂应用的根组件上调用了this.setState,从而导致一次开销很大的子组件树的重绘,可想而知,这可能会花费 200ms 也就是整整 12 帧的丢失。此时,任何由 JavaScript 控制的动画都会卡住。只要卡顿超过 100ms,用户就会明显的感觉到。这种情况经常发生在老的Navigator导航器的切换过程中:当你 push 一个新的路由时,JavaScript 需要绘制新场景所需的所有组件,以发送正确的命令给原生端去创建视图。由于切换是由 JavaScript 线程所控制,因此经常会占用若干帧的时间,引起一些卡顿。有的时候,组件会在componentDidMount函数中做一些额外的事情,这甚至可能会导致页面切换过程中多达一秒的卡顿。

另一个例子是老的触摸事件的响应:如果你正在 JavaScript 线程处理一个跨越多个帧的工作,你可能会注意到TouchableOpacity的响应被延迟了。这是因为 JavaScript 线程太忙了,不能够处理主线程发送过来的原始触摸事件,结果TouchableOpacity就不能及时响应这些事件并命令主线程的页面去调整透明度了。

 

UI 帧率(主线程)

很多人会注意到,NavigatorIOS的性能要比老的纯 JS 实现的Navigator好的多。原因就是它的切换动画是完全在主线程上执行的,因此不会被 JavaScript 线程上的掉帧所影响。

同样,当 JavaScript 线程卡住的时候,你仍然可以欢快的上下滚动ScrollView,因为ScrollView运行在主线程之上(尽管滚动事件会被分发到 JS 线程,但是接收这些事件对于滚动这个动作来说并不必要)。

 

性能问题的常见原因

开发模式 (dev=true)

JavaScript 线程的性能在开发模式下是很糟糕的。这是不可避免的,因为有许多工作需要在运行的时候去做,譬如使你获得良好的警告和错误信息,又比如验证属性类型(propTypes)以及产生各种其他的警告。请务必注意在release 模式下去测试性能。

 

console.log 语句

在运行打好了离线包的应用时,控制台打印语句可能会极大地拖累 JavaScript 线程。注意有些第三方调试库也可能包含控制台打印语句,比如redux-logger,所以在发布应用前请务必仔细检查,确保全部移除。

这里有个小技巧可以在发布时屏蔽掉所有的console.*调用。React Native 中有一个全局变量__DEV__用于指示当前运行环境是否是开发环境。我们可以据此在正式环境中替换掉系统原先的 console 实现。

if (!__DEV__) {
  global.console = {
   
info: () => {},
   
log: () => {},
   
warn: () => {},
   
debug: () => {},
   
error: () => {}
  };
}

还有个babel 插件可以帮你移除所有的console.*调用。首先需要使用yarn add --dev babel-plugin-transform-remove-console来安装,然后在项目根目录下编辑(或者是新建)一个名为·.babelrc`的文件,在其中加入:

{
 
"env": {
   
"production": {
     
"plugins": ["transform-remove-console"]
    }
  }
}

这样在打包发布时,所有的控制台语句就会被自动移除,而在调试时它们仍然会被正常调用。

 

ListView 首次渲染缓慢或者由于列表很大导致滑动很慢

用新的FlatList或者SectionList组件替代。除了简化了API,这些新的列表组件在性能方面都有了极大的提升, 其中最主要的一个是无论列表有多少行,它的内存使用都是常数级的。

如果你的FlatList渲染得很慢, 请确保你使用了getItemLayout,它通过跳过对items的处理来优化你的渲染速度。

 

在重绘一个几乎没有什么变化的页面时,JS 帧率严重降低

你可以实现shouldComponentUpdate函数来指明在什么样的确切条件下,你希望这个组件得到重绘。如果你编写的是纯粹的组件(界面完全由 props 和 state 所决定),你可以利用PureComponent来为你做这个工作。再强调一次,不可变的数据结构(immutable,即对于引用类型数据,不修改原值,而是复制后修改并返回新值)在提速方面非常有用 —— 当你不得不对一个长列表对象做一个深度的比较,它会使重绘你的整个组件更加快速,而且代码量更少。

 

在屏幕上移动视图(滚动,切换,旋转)时,UI 线程掉帧

当具有透明背景的文本位于一张图片上时,或者在每帧重绘视图时需要用到透明合成的任何其他情况下,这种现象尤为明显。设置shouldRasterizeIOS或者renderToHardwareTextureAndroid属性可以显著改善这一现象。 注意不要过度使用该特性,否则你的内存使用量将会飞涨。在使用时,要评估你的性能和内存使用情况。如果你没有需要移动这个视图的需求,请关闭这一属性。

 

使用动画改变图片的尺寸时,UI 线程掉帧

在 iOS 上,每次调整 Image 组件的宽度或者高度,都需要重新裁剪和缩放原始图片。这个操作开销会非常大,尤其是大的图片。比起直接修改尺寸,更好的方案是使用transform: [{scale}]的样式属性来改变尺寸。比如当你点击一个图片,要将它放大到全屏的时候,就可以使用这个属性。

 

Touchable 系列组件不能很好的响应

有些时候,如果我们有一项操作与点击事件所带来的透明度改变或者高亮效果发生在同一帧中,那么有可能在onPress函数结束之前我们都看不到这些效果。比如在onPress执行了一个setState的操作,这个操作需要大量计算工作并且导致了掉帧。解决方案是将onPress处理函数中的操作封装requestAnimationFrame中:

handleOnPress() {
 
// 谨记在使用requestAnimationFrame、setTimeout以及setInterval时
 
// 要使用TimerMixin(其作用是在组件unmount时,清除所有定时器)
 
this.requestAnimationFrame(() => {
   
this.doExpensiveAction();
  });
}

分析

你可以利用内置的分析器来同时获取 JavaScript 线程和主线程中代码执行情况的详细信息。

对于 iOS 来说,Instruments 是一个宝贵的工具库,Android 的话可以使用 systrace

But first, make sure that Development Mode is OFF! You should see __DEV__ === false, development-level warning are OFF, performance optimizations are ON in your application logs.

Another way to profile JavaScript is to use the Chrome profiler while debugging. This won't give you accurate results as the code is running in Chrome but will give you a general idea of where bottlenecks might be. Run the profiler under Chrome's Performance tab. A flame graph will appear under User Timing. To view more details in tabular format, click at the Bottom Up tab below and then select DedicatedWorker Thread at the top left menu.

 

使用 systrace 调试 Android UI 性能(Find your process)

  • UI Thread. This is where standard android measure/layout/draw happens. The thread name on the right will be your package name (in my case book.adsmanager) or UI Thread. The events that you see on this thread should look something like this and have to do with Choreographertraversals, and DispatchUI:

  • JS Thread. This is where JavaScript is executed. The thread name will be either mqt_js or <...> depending on how cooperative the kernel on your device is being. To identify it if it doesn't have a name, look for things like JSCallBridge.executeJSCall, etc:

  • Native Modules Thread. This is where native module calls (e.g. the UIManager) are executed. The thread name will be either mqt_native_modules or <...>. To identify it in the latter case, look for things like NativeCallcallJavaModuleMethod, and onBatchComplete:

  • Bonus: Render Thread. If you're using Android L (5.0) and up, you will also have a render thread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either RenderThread or <...>. To identify it in the latter case, look for things like DrawFrame and queueBuffer:

 

 

 

 

 

拆包(RAM bundles)和内联引用

如果你有一个较为庞大的应用程序,你可能要考虑使用RAM(Random Access Modules,随机存取模块)格式的 bundle 和内联引用。这对于具有大量页面的应用程序是非常有用的,这些页面在应用程序的典型使用过程中可能不会被打开。通常对于启动后一段时间内不需要大量代码的应用程序来说是非常有用的。例如应用程序包含复杂的配置文件屏幕或较少使用的功能,但大多数会话只涉及访问应用程序的主屏幕更新。我们可以通过使用RAM格式来优化bundle的加载,并且内联引用这些功能和页面(当它们被实际使用时)。

 

加载 JavaScript

在 react-native 执行 JS 代码之前,必须将代码加载到内存中并进行解析。如果你加载了一个 50MB 的 bundle,那么所有的 50mb 都必须被加载和解析才能被执行。RAM 格式的 bundle 则对此进行了优化,即启动时只加载 50MB 中实际需要的部分,之后再逐渐按需加载更多的包。

内联引用

内联引用(require 代替 import)可以延迟模块或文件的加载懒加载机制,直到实际需要该文件。

 

启用 RAM 格式

在 iOS 上使用 RAM 格式将创建一个简单的索引文件,React Native 将根据此文件一次加载一个模块。在 Android 上,默认情况下它会为每个模块创建一组文件。你可以像 iOS 一样,强制 Android 只创建一个文件,但使用多个文件可以提高性能,并降低内存占用。

在 Xcode 中启用 RAM 格式,需要编辑 build phase 里的"Bundle React Native code and images"。在../node_modules/react-native/scripts/react-native-xcode.sh.sh中添加 export BUNDLE_COMMAND="ram-bundle":

export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh.sh

在 Android 上启用 RAM 格式,需要编辑 android/app/build.gradle 文件。在apply from: "../../node_modules/react-native/react.gradle"之前修改或添加project.ext.react

project.ext.react = [
  bundleCommand:
"ram-bundle",
]

如果在 Android 上,你想使用单个索引文件(如前所述),请在 Android 上使用以下行:

project.ext.react = [
  bundleCommand:
"ram-bundle",
  extraPackagerArgs: [
"--indexed-ram-bundle"]
]

配置预加载及内联引用

现在我们已经启用了RAM格式,然而调用require会造成额外的开销。因为当遇到尚未加载的模块时,require需要通过bridge来发送消息。这主要会影响到启动速度,因为在应用程序加载初始模块时可能触发相当大量的请求调用。幸运的是,我们可以配置一部分模块进行预加载。为了做到这一点,你将需要实现某种形式的内联引用。

添加 packager 配置文件

在项目中创建一个名为 packager 的文件夹,并创建一个名为 config.js 的文件。添加以下内容:

const config = {
  transformer: {
    getTransformOptions: () => {
      return {
        transform: { inlineRequires:
true },
      };
    },
  },
};

module.exports = config;

在 Xcode 的 Build phase 中添加export BUNDLE_CONFIG="packager/config.js"

export BUNDLE_COMMAND="ram-bundle"
export BUNDLE_CONFIG="packager/config.js"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh.sh

编辑 android/app/build.gradle 文件,添加bundleConfig: "packager/config.js",

project.ext.react = [
  bundleCommand:
"ram-bundle",
  bundleConfig:
"packager/config.js"
]

最后,在 package.json 的“scripts”下修改“start”命令来启用配置文件:

"start": "node node_modules/react-native/local-cli/cli.js start --config ../../../../packager/config.js",

此时用npm start启动你的 packager 服务即会加载配置文件。请注意,如果你仍然通过 xcode 或是 react-native run-android 等方式自动启动 packager 服务,则由于没有使用上面的参数,不会加载配置文件。

 

调试预加载的模块

在您的根文件 (index.(ios|android).js) 中,您可以在初始导入(initial imports)之后添加以下内容:

const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds
  .filter(
moduleId => modules[moduleId].isInitialized)
  .map(
moduleId => modules[moduleId].verboseName);
const waitingModuleNames = moduleIds
  .filter(
moduleId => !modules[moduleId].isInitialized)
  .map(
moduleId => modules[moduleId].verboseName);

// make sure that the modules you expect to be waiting are actually waiting
console.log(
 
'loaded:',
  loadedModuleNames.length,
 
'waiting:',
  waitingModuleNames.length
);

// grab this text blob, and put it in a file named packager/modulePaths.js
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);

当你运行你的应用程序时,你可以查看 console 控制台,有多少模块已经加载,有多少模块在等待。你可能想查看 moduleNames,看看是否有任何意外。注意在首次 import 时调用的内联引用。你可能需要检查和重构,以确保只有你想要的模块在启动时加载。请注意,您可以根据需要修改 Systrace 对象,以帮助调试有问题的引用。

require.Systrace.beginEvent = (message) => {
 
if(message.includes(problematicModule)) {
   
throw new Error();
  }
}

虽然每个 App 各有不同,但只加载第一个页面所需的模块是有普适意义的。把 loadedModuleNames 的输出放到 packager/modulePaths.js 文件中。

 

更新配置文件

Returning to packager/config.js we should update it to use our newly generated modulePaths.js file.

const modulePaths = require('./modulePaths');
const resolve = require('path').resolve;
const fs = require('fs');

// Update the following line if the root folder of your app is somewhere else.
const ROOT_FOLDER = resolve(__dirname, '..');

const config = {
 
transformer: {
   
getTransformOptions: () => {
     
const moduleMap = {};
      modulePaths.forEach(
path => {
       
if (fs.existsSync(path)) {
          moduleMap[resolve(path)] =
true;
        }
      });
     
return {
       
preloadedModules: moduleMap,
       
transform: { inlineRequires: { blacklist: moduleMap } },
      };
    },
  },
 
projectRoot: ROOT_FOLDER,
};

module.exports = config;

在启用RAM格式之后,配置文件中的preloadedModules条目指示哪些模块需要预加载。当 bundle 被加载时,这些模块立即被加载,甚至在任何 requires 执行之前。blacklist 表明这些模块不应该被要求内联引用,因为它们是预加载的,所以使用内联没有性能优势。实际上每次解析内联引用 JavaScript 都会花费额外的时间。

 

 

 

 

 

 

JavaScript 运行时环境

在使用 React Native 时,你的 JavaScript 代码将会运行在两个不同的环境上:

  • 大多数情况下,React Native 使用的是JavaScriptCore,也就是 Safari 所使用的 JavaScript 引擎。但是在 iOS 上 JavaScriptCore 并没有使用即时编译技术(JIT),因为在 iOS 中应用无权拥有可写可执行的内存页(因此无法动态生成代码)。
  • 在使用 Chrome 调试时,所有的 JavaScript 代码都运行在 Chrome 中,并且通过 WebSocket 与原生代码通信。此时的运行环境是V8 引擎

虽然两个环境非常类似,但开发者还是可能碰到一些不一致的地方。未来我们很可能会尝试一些其他的 JS 引擎,所以请尽量避免使用依赖于特定运行环境的代码。

常见的不一致比如有:iOS 上有部分日期构造函数未实现;Android 上重复定义的 props 可能会导致报错。

 

JavaScript 语法转换器

语法转换器可以使编写代码的过程更加享受,因为开发者可以借助转换器直接使用新的 JavaScirpt 语法标准,而无需等待 JS 解释器的支持。

React Native 内置了Babel 转换器。你可以查看Babel 的文档来了解有关它可以转换的语法的详情。

这里可以看到目前 React Native 默认开启的语法转换特性

注:若想学习相关语法,译者推荐阮一峰老师的《ECMAScript 6 入门》以及论坛的讨论帖

 

ES5

保留关键字: promise.catch(function() { });

 

ES6

箭头函数 Arrow functions<C onPress={() => this.setState({pressed: true})}

块级作用域 Block scopinglet greeting = 'hi';

数组的扩展运算 Call spreadMath.max(...array);

类 Classesclass C extends React.Component { render() { return <View />; } }

常量 Constantsconst answer = 42;

解构 Destructuringvar {isActive, style} = this.props;

for...offor (var num of [1, 2, 3]) {}

模块 Modulesimport React, { Component } from 'react';

动态属性键 Computed Propertiesvar key = 'abc'; var obj = {[key]: 10};

对象方法的简写 Object Consise Method: var obj = { method() { return 10; } };

对象属性的简写 Object Short Notationvar name = 'vjeux'; var obj = { name };

参数的扩展运算 Rest Paramsfunction(type, ...args) { }

字符串模板 Template Literalsvar who = 'world'; var str = `Hello ${who}`;

 

ES8

参数列表末尾允许放置逗号 Function Trailing Commafunction f(a, b, c,) { }

异步函数 Async Functionsasync function doStuffAsync() { const foo = await doOtherStuffAsync(); };

 

Stage 3

对象的扩展运算 Object Spreadvar extended = { ...obj, a: 10 };

其他特性

JSX<View style={{color: 'red'}} />

Flowfunction foo(x: ?number): string {}

__DEV__ 用于判断当前是否开发环境的全局变量

 

 

 

 

 

 

直接操作

有时候我们需要直接改动组件并触发局部的刷新,但不使用 state 或是 props。譬如在浏览器中使用 React 库,有时候会需要直接修改一个 DOM 节点,而在手机 App 中操作 View 时也会碰到同样的情况。在 React Native 中,setNativeProps就是等价于直接操作 DOM 节点的方法。

什么时候使用 setNativeProps 呢?

在(不得不)频繁刷新而又遇到了性能瓶颈的时候。

直接操作组件并不是应该经常使用的工具。一般来说只是用来创建连续的动画,同时避免渲染组件结构和同步太多视图变化所带来的大量开销。setNativeProps是一个“简单粗暴”的方法,它直接在底层(DOM、UIView 等)而不是 React 组件中记录 state,这样会使代码逻辑难以理清。所以在使用这个方法之前,请尽量先尝试用setStateshouldComponentUpdate方法来解决问题。

 

 

 

 

 

 

颜色

Components in React Native are styled using JavaScript. Color properties usually match how CSS works on the web.

红-绿-蓝

React Native 支持 rgb() 和 rgba() 两种十六进制与函数方法

  • '#f0f' (#rgb)
  • '#ff00ff' (#rrggbb)
  • '#ff00ff'
  • 'rgba(255, 255, 255, 1.0)'
  • '#f0ff' (#rgba)
  • '#ff00ff00' (#rrggbbaa)

色调-饱和度-亮度

也支持 hsl() 和 hsla() 函数方法:

  • 'hsl(360, 100%, 100%)'
  • 'hsla(360, 100%, 100%, 1.0)'

透明度

rgba(0,0,0,0) 的快捷方式是:'transparent'

你还可以使用颜色名称来作为颜色值. React Native 遵循CSS3 规范

  • aliceblue (#f0f8ff)
  • antiquewhite (#faebd7)
  • aqua (#00ffff)
  • aquamarine (#7fffd4)
  • azure (#f0ffff)
  • beige (#f5f5dc)
  • bisque (#ffe4c4)
  • black (#000000)
  • blanchedalmond (#ffebcd)
  • blue (#0000ff)
  • blueviolet (#8a2be2)
  • brown (#a52a2a)
  • burlywood (#deb887)
  • cadetblue (#5f9ea0)
  • chartreuse (#7fff00)
  • chocolate (#d2691e)
  • coral (#ff7f50)
  • cornflowerblue (#6495ed)
  • cornsilk (#fff8dc)
  • crimson (#dc143c)
  • cyan (#00ffff)
  • darkblue (#00008b)
  • darkcyan (#008b8b)
  • darkgoldenrod (#b8860b)
  • darkgray (#a9a9a9)
  • darkgreen (#006400)
  • darkgrey (#a9a9a9)
  • darkkhaki (#bdb76b)
  • darkmagenta (#8b008b)
  • darkolivegreen (#556b2f)
  • darkorange (#ff8c00)
  • darkorchid (#9932cc)
  • darkred (#8b0000)
  • darksalmon (#e9967a)
  • darkseagreen (#8fbc8f)
  • darkslateblue (#483d8b)
  • darkslategrey (#2f4f4f)
  • darkturquoise (#00ced1)
  • darkviolet (#9400d3)
  • deeppink (#ff1493)
  • deepskyblue (#00bfff)
  • dimgray (#696969)
  • dimgrey (#696969)
  • dodgerblue (#1e90ff)
  • firebrick (#b22222)
  • floralwhite (#fffaf0)
  • forestgreen (#228b22)
  • fuchsia (#ff00ff)
  • gainsboro (#dcdcdc)
  • ghostwhite (#f8f8ff)
  • gold (#ffd700)
  • goldenrod (#daa520)
  • gray (#808080)
  • green (#008000)
  • greenyellow (#adff2f)
  • grey (#808080)
  • honeydew (#f0fff0)
  • hotpink (#ff69b4)
  • indianred (#cd5c5c)
  • indigo (#4b0082)
  • ivory (#fffff0)
  • khaki (#f0e68c)
  • lavender (#e6e6fa)
  • lavenderblush (#fff0f5)
  • lawngreen (#7cfc00)
  • lemonchiffon (#fffacd)
  • lightblue (#add8e6)
  • lightcoral (#f08080)
  • lightcyan (#e0ffff)
  • lightgoldenrodyellow (#fafad2)
  • lightgray (#d3d3d3)
  • lightgreen (#90ee90)
  • lightgrey (#d3d3d3)
  • lightpink (#ffb6c1)
  • lightsalmon (#ffa07a)
  • lightseagreen (#20b2aa)
  • lightskyblue (#87cefa)
  • lightslategrey (#778899)
  • lightsteelblue (#b0c4de)
  • lightyellow (#ffffe0)
  • lime (#00ff00)
  • limegreen (#32cd32)
  • linen (#faf0e6)
  • magenta (#ff00ff)
  • maroon (#800000)
  • mediumaquamarine (#66cdaa)
  • mediumblue (#0000cd)
  • mediumorchid (#ba55d3)
  • mediumpurple (#9370db)
  • mediumseagreen (#3cb371)
  • mediumslateblue (#7b68ee)
  • mediumspringgreen (#00fa9a)
  • mediumturquoise (#48d1cc)
  • mediumvioletred (#c71585)
  • midnightblue (#191970)
  • mintcream (#f5fffa)
  • mistyrose (#ffe4e1)
  • moccasin (#ffe4b5)
  • navajowhite (#ffdead)
  • navy (#000080)
  • oldlace (#fdf5e6)
  • olive (#808000)
  • olivedrab (#6b8e23)
  • orange (#ffa500)
  • orangered (#ff4500)
  • orchid (#da70d6)
  • palegoldenrod (#eee8aa)
  • palegreen (#98fb98)
  • paleturquoise (#afeeee)
  • palevioletred (#db7093)
  • papayawhip (#ffefd5)
  • peachpuff (#ffdab9)
  • peru (#cd853f)
  • pink (#ffc0cb)
  • plum (#dda0dd)
  • powderblue (#b0e0e6)
  • purple (#800080)
  • rebeccapurple (#663399)
  • red (#ff0000)
  • rosybrown (#bc8f8f)
  • royalblue (#4169e1)
  • saddlebrown (#8b4513)
  • salmon (#fa8072)
  • sandybrown (#f4a460)
  • seagreen (#2e8b57)
  • seashell (#fff5ee)
  • sienna (#a0522d)
  • silver (#c0c0c0)
  • skyblue (#87ceeb)
  • slateblue (#6a5acd)
  • slategray (#708090)
  • snow (#fffafa)
  • springgreen (#00ff7f)
  • steelblue (#4682b4)
  • tan (#d2b48c)
  • teal (#008080)
  • thistle (#d8bfd8)
  • tomato (#ff6347)
  • turquoise (#40e0d0)
  • violet (#ee82ee)
  • wheat (#f5deb3)
  • white (#ffffff)
  • whitesmoke (#f5f5f5)
  • yellow (#ffff00)
  • yellowgreen (#9acd32)

 

 

 

 

集成到现有原生应用

 

 

 

 

 

posted @ 2019-03-22 12:44  一只特立独行的程序猿  阅读(3315)  评论(0编辑  收藏  举报