1. 程式人生 > >我的專案react-native-meituan總結與反思

我的專案react-native-meituan總結與反思

react-native-meituan

通過這個小專案,掌握了react-native的一些元件的用法。本專案react-native的版本是0.44.2

一些變化

  • 自0.40版本開始,對於Image元件的source屬性,不再支援require('image!...')。詳情請見:http://reactnative.com/react-native-december-2016-v0-40-0-released/
    所以本專案中使用的都是uri的形式,不知為何,我使用uri的形式使用相對路徑應用圖片無法識別。所以我將圖片放在專案目錄下。在android下是\android\app\src\main\res\drawable

    (針對不同解析度裝置目錄為drawable,drawable-mdpi,drawable-hdpi,drawable-xhdpi,drawable-xxhdpi)。在IOS下目錄是\ios\專案名\Images.xcassets

  • 從0.44版本開始,Navigator被從react-native的核心元件庫中剝離到了一個名為react-native-deprecated-custom-components的單獨模組中。如果你需要繼續使用Navigator,則需要先npm i -S react-native-deprecated-custom-components,然後從這個模組中import,即import { Navigator } from 'react-native-deprecated-custom-components'

    .

元件

Image

  • 對於source屬性,使用uri代替require。
  • 圖片需要設定寬高。
  • 圖片有resizeMode屬性,用來表示圖片的拉伸方式。值為”cover”,”contain”,”stretch”。

Text

  • 如果一些屬性在IOS和Android平臺上不能達到特定的效果,可考慮在Text元件外包一層View元件。TextInput元件也是如此。

TextInput

  • 在Android下會有下劃線,使用underlineColorAndroid=’transparent’去除下劃線。
  • returnKeyType屬性表示決定“確定”按鈕顯示的內容(我在IOS和Android下並無效果)。
  • returnKeyLabel屬性表示決定“確定”按鈕顯示的內容(在Android裝置有效)。
  • clearButtonMode屬性用來表示輸入文字後會否顯示清除(x)按鈕。

ScrollView

當分頁的時候,在componentDidMount中開啟定時器,在componentWillUnmount中清除定時器。定時器中ScrollView需要滾動到某個位置。使用元件屬性scrollTo({x,y,animated})。

  • horizontal:表示滾動列表是水平還是豎直。
  • showsHorizontalScrollIndicator:表示是否顯示一個橫向的滾動條。
  • showsVerticalScrollIndicator:表示是否顯示一個豎向的滾動條。
  • bounces:相關屬性可以設定在IOS端是否有彈跳效果。
  • pagingEnabled:表示是否可以分頁,即滾動時是否每次都滾動一頁,而不是停在一頁中間某個位置。
  • scrollEnabled:當值為false的時候,ScrollView不可以滾動。
  • onMomentumScrollEnd(function):當滾動的趨勢結束的時候呼叫。此時頁面已經停在了新的一頁。可以計算出其位移。
let x = e.nativeEvent.contentOffset.x;
let y = e.nativeEvent.contentOffset.y;
  • onScrollBeginDrag(function):手指按到螢幕的時候呼叫。當在分頁的時候,此時需要停止定時器。
  • onScrollEndDrag(function):手指離開螢幕的時候。當在分頁的時候,此時需要恢復定時器。
<ScrollView
  ref="scrollView"
  horizontal={true}
  pagingEnabled={true}
  showsHorizontalScrollIndicator={false}
  bounces={false}
  onMomentumScrollEnd={this._onMomentumScrollEnd}
  onScrollBeginDrag={this._onScrollBeginDrag}
  onScrollEndDrag={this._onScrollEndDrag}
>
  {this.renderItems()}
</ScrollView>

ListView

  • dataSource:資料來源。
// 對於沒有頭部的情況
constructor(props) {
  super(props);
  this.state = {
    dataSource: new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2,  
    }).cloneWithRows(['1', '2', '3'])
  }
}


// 對於具有頭部的情況
constructor(props) {
  super(props);
  const getSectionHeaderData = (dataBlob, sectionID) => {
    return dataBlob[sectionID];
  };
  const getRowData = (dataBlob, sectionID, rowID) => {
    return dataBlob[sectionID+':'+rowID];
  };

  this.state = {
    dataSource: new ListView.DataSource({
      getRowData: getRowData,
      getSectionHeaderData: getSectionHeaderData,
      rowHasChanged: (r1, r2) => r1 !== r2,
      sectionHeaderHasChanged: (s1, s2) => s1 !== s2
    })
  }
}

componentDidMount() {
  let carsData = ImageData.data; // 陣列,每一個元素都是關於車的物件
  let dataBlob = {};
  let sectionID = [];
  let rowID = [];
  let i, j;
  for(i = 0; i < carsData.length; i++) {
    let carData = carsData[i];
    sectionID.push(i);
    dataBlob[i] = carData.title;
    rowID[i] = [];
    for(j = 0; j < carData['cars'].length; j++) {
      rowID[i].push(j);
      dataBlob[i+':'+j] = carData['cars'][j];
    }
  }
  this.setState({
    dataSource: this.state.dataSource.cloneWithRowsAndSections(dataBlob, sectionID, rowID)
  })
}

// 資料形式如下
{
  "data": [
    {
      "cars": [
        {
          "icon": "m_180_100",
          "name": "AC Schnitzer"
        },
        {
          "icon": "m_92_100",
          "name": "阿爾法·羅密歐"
        }
      ],
      "title": "A"
    },
    {
      "cars": [
        {
          "icon": "m_172_100",
          "name": "巴博斯"
        }
      ],
      "title": "B"
    },
    {
      "cars": [
        {
          "icon": "m_129_100",
          "name": "昌河"
        }
      ],
      "title": "C"
    },
  ]
}
  • renderRow:每一行的樣式。
renderRow = (text) => <Text>text</Text>
  • initialListSize: 指定在元件剛掛載的時候渲染多少行資料。用這個屬性來確保首屏顯示合適數量的資料,而不是花費太多幀逐步顯示出來。

  • onEndReached: 到達底部時候出發的函式。

  • onEndReachedThreshold: 到達底部的臨界值(如果值是100,表明距離底部100畫素就觸發函式)。
  • renderHeader: 所有行資料的頭部。
  • renderFooter: 所有行資料的尾部。
  • renderSeparator: 每一行的間隔(分割線)。
  • stickySectionHeadersEnabled: 是否啟用粘性標題。
  • renderSectionHeader: 粘性頭部的樣式。
  • stickyHeaderIndices: 一個子檢視下標的陣列,用於決定哪些成員會在滾動之後固定在螢幕頂端。我嘗試了沒有效果。
<ListView
  dataSource={this.state.dataSource}
  renderRow={this.renderRow}
  initialListSize={6}
  onEndReachedThreshold={100}
  onEndReached={() => {alert(1)}}
  renderHeader={this.renderHeader}
  renderFooter={this.renderFooter}
  renderSeparator={this.renderSeparator}
  stickySectionHeadersEnabled={true}
  renderSectionHeader={this.renderSectionHeader}
  stickyHeaderIndices={[0]}
>

TabNavigator(專案作者推薦使用react-navigation)

TabNavigator.Item如下:

<TabNavigator.Item
  title={title}
  renderIcon={() => <Image source={{uri: renderIcon}} style={styles.iconStyle}/>}
  renderSelectedIcon={() => <Image source={{uri: renderSelectedIcon}} style={styles.iconStyle}/>}
  selected={this.state.selectedTabBar===selectedTabBar}
  onPress={() => {this.setState({selectedTabBar})}}
  selectedTitleStyle={styles.selectedTitleStyle}
  badgeText={badgeText}
>

上述屬性很清晰明瞭。

  • configureScene: 一個頁面切換到另一個頁面的動畫。可能的選項有:
Navigator.SceneConfigs.PushFromRight (預設)
Navigator.SceneConfigs.FloatFromRight
Navigator.SceneConfigs.FloatFromLeft
Navigator.SceneConfigs.FloatFromBottom
Navigator.SceneConfigs.FloatFromBottomAndroid
Navigator.SceneConfigs.FadeAndroid
Navigator.SceneConfigs.HorizontalSwipeJump
Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
Navigator.SceneConfigs.VerticalUpSwipeJump
Navigator.SceneConfigs.VerticalDownSwipeJump
  • renderScene: 用來渲染指定路由的場景。呼叫的引數是路由和導航器。一般除了自定義屬性外,都會將navigator也作為路由屬性傳遞。這樣,下一個路由就可以通過this.props.navigator來操作路由。可能的操作有:
getCurrentRoutes() - 獲取當前棧裡的路由,也就是push進來,沒有pop掉的那些。
jumpBack() - 跳回之前的路由,當然前提是保留現在的,還可以再跳回來,會給你保留原樣。
jumpForward() - 上一個方法不是調到之前的路由了麼,用這個跳回來就好了。
jumpTo(route) - 跳轉到已有的場景並且不解除安裝。
push(route) - 跳轉到新的場景,並且將場景入棧,你可以稍後跳轉過去
pop() - 跳轉回去並且解除安裝掉當前場景
replace(route) - 用一個新的路由替換掉當前場景
replaceAtIndex(route, index) - 替換掉指定序列的路由場景
replacePrevious(route) - 替換掉之前的場景
resetTo(route) - 跳轉到新的場景,並且重置整個路由棧
immediatelyResetRouteStack(routeStack) - 用新的路由陣列來重置路由棧
popToRoute(route) - pop到路由指定的場景,在整個路由棧中,處於指定場景之後的場景將會被解除安裝。
popToTop() - pop到棧中的第一個場景,解除安裝掉所有的其他場景。
  • initialRoute: 定義啟動時載入的路由。路由是導航欄用來識別渲染場景的一個物件。initialRoute必須是initialRouteStack中的一個路由。initialRoute預設為initialRouteStack中最後一項。
<Navigator
  initialRoute={{name, component}}
  configureScene={() => {return Navigator.SceneConfigs.PushFromRight}}
  renderScene={(route, navigator) => {
    let Component = route.component;
    return <Component {...route.passProps} navigator={navigator}/>
  }}
/>

TouchableOpacity

  • activeOpacity(number): 按下時的透明度。

WebView(用於將一個網頁執行在app上)

  • automaticallyAdjustContentInsets: 是否自動調整嵌入的內容。
  • source: 使用uri表示網頁的uri。
  • javaScriptEnabled: 是否允許js。
  • domStorageEnabled: Boolean value to control whether DOM Storage is enabled. Used only in Android.
  • decelerationRate(ios): 使用者擡起手指後以多快的速度停下來。可選的值有”normal”, “fast”。
  • onNavigationStateChange: WebView開始或者結束的時候呼叫。
  • startInLoadingState: 強迫WebView載入前載入loading。
  • scalesPageToFit: 是否允許內容根據檢視(我猜是螢幕?)自動調整以及是否允許使用者縮放。
<WebView
  automaticallyAdjustContentInsets={false}
  style={styles.webView}
  source={{uri: this.state.url}}
  javaScriptEnabled={true}
  domStorageEnabled={true}
  decelerationRate="normal"
  onNavigationStateChange={this.onNavigationStateChange}
  onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
  startInLoadingState={true}
  scalesPageToFit={this.state.scalesPageToFit}
/>

API

BackHandler(代替過時的BackAndroid)

只應用於Android。用於定義當用戶點選返回鍵時呼叫的函式(預設是退出應用)。

BackHandler.addEventListener('hardwareBackPress', function() {
  // maybe you want to do like this
  this.props.navigator.pop();
}

Dimensions

用來獲取螢幕的寬高以及畫素比。需要注意的是,當用戶旋轉螢幕時,寬高是會改變的。依賴於之前寬高的元素將不再適用。我的方法是通過state變數來標誌寬高。每當頁面方向改變時,相應改變state屬性。對於此,Dimensions有一個方法Dimensions.addEventListener('change', f => f)。還有一個方法就是禁止螢幕旋轉,需要用到外掛react-native-orientation

import Dimensions from 'Dimensions';
let totalWidth = Dimensions.get('window').width; // 寬度
let totalHeight = Dimensions.get('window').height; // 高度

class Demo extends Component {
  state = {
    w: totalWidth,
    h: totalHeight
  }
  componentDidMount() {
    Dimensions.addEventListener('change', e => {
      this.setState({
        w: e.window.width,
        h: e.window.height,  
      })
    })
  }
  render() {
    // ...
  }
}

暫時就到這裡(2017.06.05);

反思

做事不能懶,不能拖。