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)

 

 

 

 

集成到現有原生應用

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM