React Native開(kāi)發(fā)實(shí)用技巧

原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處

代碼規(guī)范篇

JavaScript是否需要使用分號(hào)?

我的回答是:不需要分號(hào)
這個(gè)是討論很火的一個(gè)話題。我剛從Objective-C轉(zhuǎn)到JavaScript的時(shí)候,很不習(xí)慣省略分號(hào)。但是沒(méi)過(guò)多久,我就把項(xiàng)目里所有的分號(hào)全部去掉了。原因很簡(jiǎn)單,明明是可以省去的工作量,同時(shí)又不會(huì)造成不良影響的事情。為什么不去省呢?
就像Objective-C剛推出ARC(自動(dòng)內(nèi)存回收)的時(shí)候,很多“老人”覺(jué)得ARC不放心,堅(jiān)持要使用MRC(手動(dòng)內(nèi)存回收)。我猜想現(xiàn)在還在堅(jiān)持使用MRC的人,可能不到1%了吧。

代碼的縮進(jìn),使用4個(gè)空格,還是2個(gè)空格?

我的回答是:2個(gè)空格
跟上面一樣,我剛轉(zhuǎn)到JavaScript的時(shí)候,覺(jué)得2個(gè)空格很反人類,太擠了。所以一直堅(jiān)持4個(gè)空格。直到UI代碼慢慢變多,嵌套變深。。。我才開(kāi)始慢慢懷疑自己。前幾天,我搜索了多個(gè)熱門的JavaScript開(kāi)源項(xiàng)目,無(wú)一例外使用的是2個(gè)空格。所以,我果斷把代碼改成2個(gè)空格。

UI布局篇

如何根據(jù)不同屏幕尺寸,做等比例布局?

美工出標(biāo)注圖的時(shí)候,通常是按照屏幕寬度750出的。如果需求是UI根據(jù)不同屏幕尺寸,做等比例的縮放。我們可以寫一個(gè)叫px2dp的公共方法

import {Dimensions} from 'react-native'

const basePixelWidth = 750
const px2dp = (px: number): number => {
  return px * Dimensions.get('window').width / basePixelWidth
}

比如要設(shè)置View的寬度為屏幕的一半。就可以這樣:

<View style={{width: px2dp(375)}} />

如何快速獲取屏幕寬高?

常見(jiàn)的做法,是通過(guò)react native提供的Dimensions獲取

const screenWidth = Dimensions.get('window').width
const screenHeight = Dimensions.get('window').height

我們可以做一個(gè)簡(jiǎn)單的封裝,創(chuàng)建一個(gè)screen.js文件

import {Dimensions, PixelRatio} from 'react-native'

const screenWidth = Dimensions.get('window').width
const screenHeight = Dimensions.get('window').height

let screen = {
  width: screenWidth,
  height: screenHeight,
}

export default screen

然后在外部,就可以通過(guò)screen.width或者screen.height獲取寬高了。

如何判斷設(shè)備是iPhoneX?

我們有時(shí)候需要對(duì)iPhoneX的異形屏做一些特殊的適配,就需要判斷一個(gè)設(shè)備是否是iPhoneX

import {Platform, Dimensions} from 'react-native'

const isIphoneX = () => {
  let dimen = Dimensions.get('window')
  return (
    Platform.OS === 'ios' &&
    !Platform.isPad &&
    !Platform.isTVOS &&
    (dimen.height === 812 || dimen.width === 812)
  )
}

如果你比較愛(ài)偷懶,可以順便再封裝幾個(gè)方法

// 判斷是否是iOS設(shè)備
const isIOS = () => {
  return Platform.OS === 'ios'
}

// 是否是Android設(shè)備
const isAndroid = () => {
  return Platform.OS === 'android'
}

// 狀態(tài)欄的高度
const statusBarHeight = () => (isIOS() ? (isIphoneX() ? 44 : 20) : 0)

如何優(yōu)雅的為每個(gè)頁(yè)面增加Loading頁(yè)、錯(cuò)誤頁(yè)?

很多App大部分頁(yè)面都需要用到Loading頁(yè)。如果數(shù)據(jù)獲取失敗,還需要顯示錯(cuò)誤頁(yè),告訴用戶錯(cuò)誤原因,并且讓用戶點(diǎn)擊重試。這時(shí)候,我們可以定義一個(gè)通用的組件叫Scene,然后在這個(gè)類中,定義Loading頁(yè)面和錯(cuò)誤頁(yè)。使用的時(shí)候,只需要在我們的component這樣實(shí)現(xiàn)

render() {
  let {isLoading, error} = this.state
  return (
    <Scene
      isLoading={isLoading}
      error={error}
      onRetry={this.requestData}
    >
      ...
    </Scene>
  )
}

當(dāng)我們需要請(qǐng)求網(wǎng)絡(luò)的時(shí)候,把isLoading設(shè)置為true。這樣Scene組件就會(huì)為我們展示一個(gè)Loading頁(yè)面。
當(dāng)網(wǎng)絡(luò)請(qǐng)求失敗的時(shí)候,把isLoading設(shè)置為false,error設(shè)置為具體的錯(cuò)誤信息。Scene組件就會(huì)顯示一個(gè)錯(cuò)誤頁(yè),并且有一個(gè)“重試”按鈕。用戶點(diǎn)擊“重試”后,會(huì)調(diào)用onRetry回調(diào)。
Scene的代碼簡(jiǎn)化后,大概是這樣

type Props = {
  isLoading?: boolean,
  error?: ?string,
  onRetry?: Function,
  children?: any,
  style?: Object,
}

class Scene extends PureComponent<Props> {
  render() {
    let {isLoading, style, error, children, onRetry} = this.props
    return (
      <View style={[styles.container, style]}>
        {!isLoading && children}
        <ErrorView text={error} onPress={onRetry} />
        <LoadingView isLoading={isLoading} />
      </View>
    )
  }
}

這里需要注意的是,ErrorView、LoadingView需要使用下面這種絕對(duì)布局。否則可能會(huì)被頁(yè)面的內(nèi)容頂?shù)狡聊煌饷妗?/p>

{
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  }

我們還可以給Scene增加其他的功能,比如一個(gè)通用的HUD,空數(shù)據(jù)頁(yè)面等等。

做布局的時(shí)候,需要用到大量的style,這些style我要寫在哪里呢?

首先,我強(qiáng)烈反對(duì)把所有的style放在一個(gè)文件中。這對(duì)維護(hù)來(lái)說(shuō)是一個(gè)噩夢(mèng)。我喜歡把每個(gè)component特有的style,放在該component所在的文件中。比如下面的container樣式是Scene這個(gè)組件獨(dú)有的,就可以直接寫在Scene中。

class Scene extends PureComponent<{}> {
  render() {
    return (
      <View style={styles.container}>
        ...
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Colors.paper,
    marginTop:20,
  },
})

如果是多個(gè)頁(yè)面都需要用到的style,這時(shí)候考慮兩種情況。
第一,如果這個(gè)style非常非常通用,比如一個(gè)分隔線的樣式。那么優(yōu)先考慮單獨(dú)抽出一個(gè)分割線的類,比如這樣

class Separator extends PureComponent<{}> {
  render() {
    return (
      <View style={styles.separator} />
    )
  }
}
const styles = StyleSheet.create({
  separator: {
    height: StyleSheet.hairlineWidth,
    backgroundColor: Colors.line,
  }
})

第二,如果這個(gè)style只是在部分頁(yè)面中出現(xiàn)。那么可以創(chuàng)建一個(gè)styles.js文件,在里面定義通用的樣式,比如:

let styles = StyleSheet.create({
  border: {
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: Colors.border,
  },
  line: {
    height: StyleSheet.hairlineWidth,
    backgroundColor: Colors.line,
  },
  fillParent: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  list: {
    marginHorizontal: px(34),
    marginTop: px(38),
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: Colors.border,
    borderRadius: 3,
    shadowColor: 'black',
    shadowOffset: {
      width: 2,
      height: 2,
    },
    shadowOpacity: 0.07,
    shadowRadius: 2,
  },
  // ...
}
export default styles

Flow篇

flow是什么?

flow是有facebook推出的JavaScript靜態(tài)類型檢查工具。弱類型語(yǔ)言有個(gè)很大的問(wèn)題,就是一些類型錯(cuò)誤引起的bug,可能產(chǎn)品上線后才會(huì)被發(fā)現(xiàn)。導(dǎo)致很多事故的發(fā)生。
flow很好的解決了JavaScript弱類型引發(fā)的一系列問(wèn)題。對(duì)于習(xí)慣了強(qiáng)類型語(yǔ)言的我來(lái)說(shuō),只有借助flow這種類型檢查工具,才能找到安全感。這里不對(duì)flow的用法進(jìn)行詳細(xì)的介紹。只是說(shuō)一下一些使用的技巧。具體的使用可以查看官方網(wǎng)站

我的項(xiàng)目是否需要flow?

可能有人會(huì)問(wèn):我的項(xiàng)目很小,只有我一個(gè)人開(kāi)發(fā)。而且我對(duì)自己的編碼能力很有自信。那么我是否需要使用flow呢?
我認(rèn)為,如果你是一個(gè)剛剛接觸React Native的新手,對(duì)很多東西還不了解。那么我建議你可以先把flow放在一邊。
如果你接觸React Native已經(jīng)有一段時(shí)間,已經(jīng)可以開(kāi)發(fā)一個(gè)簡(jiǎn)單的頁(yè)面了。那么,我強(qiáng)烈建議你學(xué)習(xí)下flow。剛開(kāi)始使用flow的時(shí)候,可能會(huì)增加一定的工作量。但是當(dāng)flow替你找出代碼里一個(gè)又一個(gè)潛在問(wèn)題的時(shí)候,你會(huì)慶幸你使用了flow。而不是等到代碼上線后bug才被發(fā)現(xiàn)。

如何優(yōu)雅的定義props和state?

比如這樣一個(gè)復(fù)選框的控件


image.png

它需要外部傳入標(biāo)題。那么可以定義一個(gè)叫title的prop,它是string類型的:

class Checkbox extends PureComponent<{title:string}> {
  ....
}

當(dāng)然,我更喜歡這樣定義:

type Props = {
  title: string,
}

class Checkbox extends PureComponent<Props> {

因?yàn)槿绻枰獋魅氲膶傩苑浅6啵褂玫谝环N方式的話,可讀性就會(huì)大打折扣。
復(fù)選框控件還需要一個(gè)叫做checked的state,表示是否是選中狀態(tài):

class Checkbox extends PureComponent<{title:string},{checked:boolean}> {
  // ...
}

同樣,我更喜歡在外部定義:

type Props = {
  title: string,
}
type State = {
  checked: boolean,
}
class Checkbox extends PureComponent<Props, State> {
  // ...
}

這里props是必須定義的,而State是可選的。所以如果你的組件不需要接收props,可以這么寫:

class Checkbox extends PureComponent<{}> {
  // ...
}

具體props和state的定義,可以查看官方文檔

如何定義redux的state類型?

定義state的方式跟定義其他普通的對(duì)象一樣

type State = {
  users: Array<{
    id: string,
    name: string,
    age: number,
    phoneNumber: string,
  }>,
  activeUserID: string,
  // ...
};

或者這樣

type User = {
  id: string,
  name: string,
  age: number,
  phoneNumber: string,
}

type State = {
  users: Array<User>,
  activeUserID: string,
  // ...
}

如何定義redux的action類型?

我們項(xiàng)目中肯定不止只用一個(gè)action,所以需要使用連接符,把多個(gè)action類型合并起來(lái)

type FooAction = { type: "FOO", foo: number }
type BarAction = { type: "BAR", bar: boolean }

type Action =
  | FooAction
  | BarAction

定義redux的state是很有必要的。因?yàn)槟阈枰?jīng)常回過(guò)頭查看state的結(jié)構(gòu)。至于action嘛。。。在小項(xiàng)目中,可以暫時(shí)用Object或者any敷衍一下。

如何定義redux的reducer類型?

reducer使用到了我們剛才定義的state和action類型

function reducer(state: State, action: Action): State {
  // ...
}

如果你比較喜歡用箭頭函數(shù):

const reducer = (state: State, action: Action): State => {
  // ...
}

最后

今天是2月14情人節(jié),我因?yàn)閿?shù)月前騎電動(dòng)車光榮負(fù)傷,至今出不了門。
謹(jǐn)以此文獻(xiàn)給我的太太,情人節(jié)快樂(lè) :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容