自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我在使用React Native/Redux開發(fā)中犯過的11個錯誤

開發(fā) 前端
在使用React Native近一年之后,是時候分享一下我剛開始用RN開發(fā)項(xiàng)目時犯過的錯誤了。有可能你對第一個React Native(RN)應(yīng)用程序的預(yù)估是完全錯誤的!

在使用React Native近一年之后,是時候分享一下我剛開始用RN開發(fā)項(xiàng)目時犯過的錯誤了。

1.錯誤的估計

有可能你對***個React Native(RN)應(yīng)用程序的預(yù)估是完全錯誤的!

  • 1)你需要分別考慮iOS和Android版本的布局!在布局的時候,有很多組件可以重復(fù)使用;如果ios和Android的頁面結(jié)構(gòu)不同,就需要對他們分開單獨(dú)布局。
  • 2)對form進(jìn)行評估時,***也考慮一下數(shù)據(jù)層驗(yàn)證。
  • 3)了解數(shù)據(jù)庫結(jié)構(gòu),有助于正確地規(guī)劃redux

2.盡量使用基礎(chǔ)組件(buttons,footers,headers,inputs,texts)

google搜索RN的基礎(chǔ)組件,你會發(fā)現(xiàn)有很多現(xiàn)有組件可以方便的用到項(xiàng)目中,如buttons,footers等。如果項(xiàng)目中沒有特別的布局設(shè)計,只需要使用這些基礎(chǔ)組件就可以構(gòu)建一個頁面。如果有一些特殊的設(shè)計,例如,特殊的button樣式,你就需要為每個按鈕設(shè)置自定義樣式。你可以封裝已經(jīng)構(gòu)建好的組件,并為它們定制樣式。但是我覺得使用View,Text,TouchableOpacity和RN的其他組件來構(gòu)建自己的組件更加有意義。因?yàn)槟銜懈嗟膔n實(shí)踐,并且深刻理解如何使用RN。最重要的一點(diǎn),你可以確定你自己構(gòu)建的組件版本不會被改變。

3.不要把iOS和Android的布局分開

如果iOS和Android布局大致一樣,只有一小部分不同,你可以簡單地使用RN提供的Platform API根據(jù)設(shè)備平臺進(jìn)行區(qū)分。

如果布局完全不同 – ***分散在不同的文件中單獨(dú)布局。

如果你給一個文件命名為index.ios.js – 程序打包時,在iOS中將使用這個文件顯示iOS布局。 index.android.js也是一樣的道理。

你可能會問:“代碼怎么復(fù)用呢?” 你可以將重復(fù)的代碼移動到助手函數(shù)中。需要的時候只復(fù)用這些助手函數(shù)。

4.錯誤的redux store規(guī)劃。

初學(xué)者經(jīng)常會犯的一個很大的錯誤就是,當(dāng)你在規(guī)劃你的應(yīng)用程序時,你可能會考慮很多布局相關(guān)的問題,卻很少考慮關(guān)于數(shù)據(jù)的處理。

Redux能夠幫助我們正確地存儲數(shù)據(jù)。如果redux規(guī)劃的好 – 它將是管理應(yīng)用程序數(shù)據(jù)的強(qiáng)大工具。

當(dāng)我剛剛開始構(gòu)建RN應(yīng)用程序時,我曾考慮將reducers作為每個container的數(shù)據(jù)存儲。所以,如果你有登錄,忘記密碼,待辦事項(xiàng)列表頁面 – 使用他們的縮寫比較簡單:SignIn, Forgot, ToDoList.

在進(jìn)行了一段工作后,我發(fā)現(xiàn)管理數(shù)據(jù)沒有想象中的容易。

當(dāng)我從ToDo列表中選擇項(xiàng)目時 – 我需要將數(shù)據(jù)傳遞給ToDoDetails reducer。這意味著使用了額外的操作來發(fā)送數(shù)據(jù)到reducer。

在做了一些調(diào)查之后,我決定以不同的方式規(guī)劃結(jié)構(gòu)。一個例子:

  1. Auth
  2. Todos
  3. Friends

Auth用于存儲認(rèn)證的token。

而ToDos和Friends reducers用于存儲實(shí)體,當(dāng)我去ToDo Detail頁面時 – 我只需要通過ID搜索所有的ToDos。

對于更復(fù)雜的結(jié)構(gòu),我推薦使用這種規(guī)劃,你可以快速的定位到你想找到的。

5.錯誤的項(xiàng)目結(jié)構(gòu)

作為初學(xué)者時,總是規(guī)劃不好項(xiàng)目結(jié)構(gòu)。

首先 ,需要分析你的項(xiàng)目是不是足夠大?

你的應(yīng)用程序中有多少個頁面? 20?30?10?5?還是只有一個”Hello World”頁面?

我遇到并開始實(shí)施的***個結(jié)構(gòu)是這樣的:

 

圖0:[外文翻譯]我在使用React Native / Redux開發(fā)中犯過的11個錯誤

如果你的項(xiàng)目不超過10個頁面,使用上面這種結(jié)構(gòu)是沒有問題的。但是如果項(xiàng)目特別大 – 你可以考慮這樣規(guī)劃結(jié)構(gòu):

 

圖1:[外文翻譯]我在使用React Native / Redux開發(fā)中犯過的11個錯誤

區(qū)別在于,***種類型建議我們將actions和reducers與container分開存儲。第二種- 把它們存儲在一起。如果應(yīng)用程序很小 – 將redux模塊從container中分離出來會更加有用。

如果你有通用的樣式(如Header、Footer、Buttons) – 你可以單***建一個名為“styles”的文件夾,在那里設(shè)置一個index.js文件并在其中寫入通用樣式。然后在每個頁面上重復(fù)使用它們。

實(shí)際項(xiàng)目中會有很多不同的結(jié)構(gòu)。你應(yīng)該了解使用哪種結(jié)構(gòu)更適合你的需求。

6.錯誤的container結(jié)構(gòu)。沒有從一開始就使用smart/dumb組件

當(dāng)你開始使用RN并初始化項(xiàng)目時,index.ios.js文件中已經(jīng)有一些代碼,存儲在一個單獨(dú)的對象中。

在實(shí)際開發(fā)項(xiàng)目中,你將需要使用很多組件,不僅僅是由RN提供的,還有自己構(gòu)建的一些組件。構(gòu)建container時,可以重用它們。

考慮該組件:

 

  1. import React, { Component } from ‘react’; 
  2. import { 
  3.    Text, 
  4.    TextInput, 
  5.    View
  6.    TouchableOpacity 
  7. from ‘react-native’; 
  8. import styles from ‘./styles.ios’; 
  9.  
  10. export default class SomeContainer extends Component { 
  11.    constructor(props){ 
  12.        super(props);        
  13.        this.state = { 
  14.            username:null 
  15.        } 
  16.    } 
  17.    _usernameChanged(event){        
  18.        this.setState({ 
  19.            username:event.nativeEvent.text 
  20.        }); 
  21.     } 
  22.    _submit(){        
  23.        if(this.state.username){            
  24.            console.log(`Hello, ${this.state.username}!`); 
  25.        }        
  26.        else{            
  27.            console.log(‘Please, enter username’); 
  28.        } 
  29.     } 
  30.                 
  31.     render() {         
  32.       return (             
  33.         <View style={styles.container}>             
  34.             <View style={styles.avatarBlock}>                 
  35.                 <Image 
  36.                         source={this.props.image}  
  37.                         style={styles.avatar}/>             
  38.             </View>             
  39.             <View style={styles.form}> 
  40.                 <View style={styles.formItem}> 
  41.                    <Text>Username</Text>  
  42.                    <TextInput 
  43.                          onChange={this._usernameChanged.bind(this)} 
  44.                          value={this.state.username} /> 
  45.                  </View
  46.             </View
  47.             <TouchableOpacity onPress={this._submit.bind(this)}>                 
  48.                 <View style={styles.btn}>                     
  49.                     <Text style={styles.btnText}> 
  50.                             Submit                         
  51.                     </Text> 
  52.                 </View>  
  53.             </TouchableOpacity> 
  54.         </View
  55.         ); 
  56.     } 

所有樣式都存儲在一個單獨(dú)的模塊中。

包裹在TouchableOpacity中的button組件應(yīng)該單獨(dú)分離出來,這樣才能方便我們以后重復(fù)使用它。Image組件,以后也可能重復(fù)使用,所以也應(yīng)該把它分離出來。

做了一些改變之后的樣子:

 

  1. import React, { Component, PropTypes } from 'react'
  2. import { 
  3.     Text, 
  4.     TextInput, 
  5.     View
  6.     TouchableOpacity 
  7. from 'react-native'
  8. import styles from './styles.ios'
  9.  
  10. class Avatar extends Component{ 
  11.     constructor(props){ 
  12.         super(props); 
  13.     } 
  14.     render(){         
  15.         if(this.props.imgSrc){             
  16.             return(                 
  17.                 <View style={styles.avatarBlock}> 
  18.                     <Image 
  19.                         source={this.props.imgSrc} 
  20.                         style={styles.avatar}/> 
  21.                 </View
  22.              ) 
  23.          } 
  24.          return null
  25.     } 
  26. Avatar.propTypes = { 
  27.     imgSrc: PropTypes.object 
  28.  
  29. class FormItem extends Component{ 
  30.     constructor(props){ 
  31.         super(props); 
  32.     } 
  33.     render(){ 
  34.         let title = this.props.title; 
  35.         return(  
  36.             <View style={styles.formItem}> 
  37.                 <Text> 
  38.                     {title}                
  39.                 </Text> 
  40.                <TextInput 
  41.                    onChange={this.props.onChange} 
  42.                    value={this.props.value} /> 
  43.             </View
  44.         ) 
  45.     } 
  46. FormItem.propTypes = { 
  47.     title: PropTypes.string, 
  48.     value: PropTypes.string, 
  49.     onChange: PropTypes.func.isRequired 
  50.  
  51. class Button extends Component{ 
  52.     constructor(props){ 
  53.         super(props); 
  54.     } 
  55.     render(){ 
  56.         let title = this.props.title; 
  57.         return(             
  58.             <TouchableOpacity onPress={this.props.onPress}> 
  59.                 <View style={styles.btn}> 
  60.                     <Text style={styles.btnText}> 
  61.                         {title}                     
  62.                     </Text> 
  63.                 </View
  64.             </TouchableOpacity> 
  65.         ) 
  66.     } 
  67.              
  68. Button.propTypes = { 
  69.     title: PropTypes.string, 
  70.     onPress: PropTypes.func.isRequired 
  71. export default class SomeContainer extends Component { 
  72.     constructor(props){ 
  73.         super(props); 
  74.         this.state = { 
  75.             username:null 
  76.         } 
  77.     } 
  78.     _usernameChanged(event){ 
  79.         this.setState({ 
  80.             username:event.nativeEvent.text  
  81.         }); 
  82.     } 
  83.     _submit(){ 
  84.         if(this.state.username){ 
  85.             console.log(`Hello, ${this.state.username}!`); 
  86.         } 
  87.         else
  88.             console.log('Please, enter username'); 
  89.         } 
  90.     } 
  91.     render() { 
  92.         return (                                  
  93.         <View style={styles.container}> 
  94.                 <Avatar imgSrc={this.props.image} /> 
  95.                 <View style={styles.form}> 
  96.                     <FormItem 
  97.                       title={"Username"
  98.                       value={this.state.username} 
  99.                       onChange={this._usernameChanged.bind(this)}/> 
  100.                 </View
  101.                 <Button 
  102.                     title={"Submit"
  103.                     onPress={this._submit.bind(this)}/> 
  104.             </View
  105.         ); 
  106.     } 

現(xiàn)在的代碼看起來更多了 – 因?yàn)槲覀優(yōu)锳vatar,F(xiàn)ormItem和Button組件添加了包裝器,但現(xiàn)在我們可以在需要的地方重復(fù)使用這些組件。我們可以將這些組件移動到單獨(dú)的模塊中,并導(dǎo)入我們需要的任何地方。我們也可以添加其他一些Props,例如style,TextStyle,onLongPress,onBlur,onFocus。而且這些組件是完全可以定制的。

注意,一定不要深度定制一個小組件, 這樣會使組件過于繁瑣,代碼會變的很難閱讀。即使現(xiàn)在添加新屬性的想法看起來像是解決任務(wù)的最簡單的方法,將來這個小小的屬性在閱讀代碼時可能會引起困惑。

關(guān)于理想的smart/dumb組件,看一下這個:

 

  1. class Button extends Component{ 
  2.     constructor(props){ 
  3.         super(props); 
  4.     } 
  5.     _setTitle(){         
  6.         const { id } = this.props;         
  7.         switch(id){             
  8.             case 0:                 
  9.                 return 'Submit';             
  10.             case 1:                 
  11.                 return 'Draft';             
  12.             case 2:                 
  13.                 return 'Delete';             
  14.             default:                 
  15.                 return 'Submit'
  16.          } 
  17.     } 
  18.                  
  19.     render(){            
  20.         let title = this._setTitle();              
  21.         return(             
  22.             <TouchableOpacity onPress={this.props.onPress}> 
  23.                 <View style={styles.btn}> 
  24.                     <Text style={styles.btnText}> 
  25.                         {title}                     
  26.                     </Text> 
  27.                </View
  28.            </TouchableOpacity> 
  29.         ) 
  30.     } 
  31. Button.propTypes = { 
  32.     id: PropTypes.number, 
  33.     onPress: PropTypes.func.isRequired 
  34. export default class SomeContainer extends Component { 
  35.     constructor(props){ 
  36.         super(props); 
  37.         this.state = { 
  38.             username:null 
  39.         } 
  40.     } 
  41.     _submit(){ 
  42.         if(this.state.username){ 
  43.             console.log(`Hello, ${this.state.username}!`); 
  44.         } 
  45.         else
  46.             console.log('Please, enter username'); 
  47.         } 
  48.     } 
  49.     render() { 
  50.         return (             
  51.             <View style={styles.container}> 
  52.                 <Button 
  53.                     id={0} 
  54.                     onPress={this._submit.bind(this)}/> 
  55.             </View
  56.          
  57.     } 

我們已經(jīng)“升級”了Button組件。用一個叫做“id”的新屬性來替換屬性“title”?,F(xiàn)在Button組件就變的“靈活”了。傳0 – button組件會顯示“submit”。傳2 – 顯示“delete”。但這可能會有一些問題。

Button被創(chuàng)建為一個dumb組件 – 只是為了顯示數(shù)據(jù),傳遞數(shù)據(jù)這件事由它的更高一級的組件來完成。

如果我們將5作為id傳遞給這個組件,我們就需要更新組件,以便讓它適應(yīng)這個改動。dumb組件,就是細(xì)分的小組件,它只要接收props就好了,如果有state也應(yīng)該與全局的無關(guān)。

7.行內(nèi)樣式

在使用RN布局之后,我遇到了行內(nèi)樣式的寫作風(fēng)格問題。類似這樣:

 

  1. render() {     
  2.     return (         
  3.         <View style={{flex:1, flexDirection:'row', backgroundColor:'transparent'}}> 
  4.             <Button 
  5.                 title={"Submit"
  6.                 onPress={this._submit.bind(this)}/> 
  7.         </View
  8.     ); 

當(dāng)你這樣寫的時候,你會想:“暫時這樣寫,等我在模擬器中運(yùn)行之后 – 如果布局沒問題,再把樣式移動到單獨(dú)的模塊。”也許這是一個好的想法。但是..不幸的是,你往往會選擇性忽略行內(nèi)樣式…

一定要在獨(dú)立的模塊中編寫樣式,遠(yuǎn)離行內(nèi)樣式。

8.使用redux驗(yàn)證表單

要使用redux來驗(yàn)證表單,我需要在reducer中創(chuàng)建action,actionType單獨(dú)的字段,這樣做很麻煩。

所以我決定只借助state來完成驗(yàn)證。沒有reducers,types等等,只是在container級別上的純功能函數(shù)。從action和reducer文件中刪除不必要的函數(shù),這個策略對我?guī)椭艽蟆?/p>

9.過于依賴zIndex

很多人從web開發(fā)轉(zhuǎn)到RN開發(fā)。在web中有一個css屬性z-index,它可以幫助我們在需要的層級顯示我們想要的內(nèi)容。

在RN中,一開始沒有這樣的特性。但后來又被添加進(jìn)來了。起初,使用起來還挺簡單的。只需為元素設(shè)置zIndex屬性,它就會按照任何你想要的圖層順序來渲染。但是在Android上測試之后…現(xiàn)在我只用zIndex來設(shè)置展示層的結(jié)構(gòu)。

10.不仔細(xì)閱讀外部組件的源碼

你可以引入外部組件來節(jié)省你的開發(fā)時間。

但有時這個模塊可能會中斷,或者不像描述的那樣工作。閱讀源碼你才會明白哪里出現(xiàn)了錯誤。也許這個模塊本身就有問題,或者你只是用錯了。另外 – 如果你仔細(xì)閱讀其他模塊的源碼,你將會學(xué)習(xí)到如何構(gòu)建自己的組件。

11.要小心手勢操作和Animated API。

RN為我們提供了構(gòu)建完全原生應(yīng)用程序的能力。怎么讓用戶感覺是原生應(yīng)用?頁面布局,滑動手勢,還是展示動畫?

當(dāng)你使用View,Text,TextInput和其他RN提供的默認(rèn)模塊時,手勢和動畫應(yīng)該由PanResponder和Animated API來處理。

如果你是從web轉(zhuǎn)過來的rn開發(fā)工程師,獲取用戶的手勢操作可能會有些困難,你需要區(qū)分什么時候開始,什么時候結(jié)束,長按,短按。你可能還不夠清楚怎么在RN中模擬這些動畫操作。

這是我用PanResponder和Animated建立的Button組件。這個button是為了捕捉用戶手勢而構(gòu)建的。例如 – 用戶按下項(xiàng)目,然后將手指拖到一邊。在按下按鈕時,借助于動畫API,構(gòu)建button按壓下的不透明度的變化:

 

  1. 'use strict'
  2. import React, { Component, PropTypes } from 'react'
  3. import { Animated, View, PanResponder, Easing } from 'react-native'
  4. import moment from 'moment'
  5. export default class Button extends Component { 
  6.     constructor(props){ 
  7.         super(props);                 
  8.         this.state = { 
  9.             timestamp: 0 
  10.         };         
  11.         this.opacityAnimated = new Animated.Value(0);                 
  12.         this.panResponder = PanResponder.create({ 
  13.    onMoveShouldSetPanResponderCapture: (evt, gestureState) => true
  14.    onStartShouldSetResponder:() => true
  15.    onStartShouldSetPanResponder : () => true
  16.    onMoveShouldSetPanResponder:(evt, gestureState) => true
  17.    onPanResponderMove: (e, gesture) => {},  
  18.    onPanResponderGrant: (evt, gestureState) => {    
  19.         /**THIS EVENT IS CALLED WHEN WE PRESS THE BUTTON**/ 
  20.        this._setOpacity(1);        
  21.        this.setState({ 
  22.            timestamp: moment() 
  23.        });        
  24.        this.long_press_timeout = setTimeout(() => {             
  25.            this.props.onLongPress(); 
  26.        }, 1000); 
  27.    }, 
  28.    onPanResponderStart: (e, gestureState) => {}, 
  29.    onPanResponderEnd: (e, gestureState) => {}, 
  30.    onPanResponderTerminationRequest: (evt, gestureState) => true
  31.    onPanResponderRelease: (e, gesture) => {    
  32.        /**THIS EVENT IS CALLED WHEN WE RELEASE THE BUTTON**/ 
  33.        let diff = moment().diff(moment(this.state.timestamp));        
  34.        if(diff < 1000){            
  35.            this.props.onPress(); 
  36.        } 
  37.        clearTimeout(this.long_press_timeout);        
  38.        this._setOpacity(0);        
  39.        this.props.releaseBtn(gesture); 
  40.    } 
  41.      }); 
  42.     } 
  43.     _setOpacity(value){     
  44.      /**SETS OPACITY OF THE BUTTON**/ 
  45.         Animated.timing(         
  46.         this.opacityAnimated, 
  47.         { 
  48.             toValue: value, 
  49.             duration: 80, 
  50.         } 
  51.         ).start(); 
  52.     } 
  53.          
  54.     render(){         
  55.         let longPressHandler = this.props.onLongPress, 
  56.             pressHandler = this.props.onPress, 
  57.             image = this.props.image, 
  58.             opacity = this.opacityAnimated.interpolate({ 
  59.               inputRange: [0, 1], 
  60.               outputRange: [1, 0.5] 
  61.             });         
  62.          
  63.         return(             
  64.             <View style={styles.btn}> 
  65.                 <Animated.View 
  66.                    {...this.panResponder.panHandlers}                   style={[styles.mainBtn, this.props.style, {opacity:opacity}]}> 
  67.                     {image}                
  68.                 </Animated.View
  69.             </View
  70.         ) 
  71.     } 
  72. Button.propTypes = { 
  73.     onLongPress: PropTypes.func, 
  74.     onPressOut: PropTypes.func, 
  75.     onPress: PropTypes.func, 
  76.     style: PropTypes.object, 
  77.     image: PropTypes.object 
  78. }; 
  79. Button.defaultProps = { 
  80.     onPressOut: ()=>{ console.log('onPressOut is not defined'); }, 
  81.     onLongPress: ()=>{ console.log('onLongPress is not defined'); }, 
  82.     onPress: ()=>{ console.log('onPress is not defined'); }, 
  83.     style: {}, 
  84.     image: null 
  85. }; 
  86. const styles = { 
  87.     mainBtn:{ 
  88.         width:55, 
  89.         height:55, 
  90.         backgroundColor:'rgb(255,255,255)',   
  91.     } 
  92. }; 

首先,初始化PanResponder對象實(shí)例。它有一套不同的操作句柄。我感興趣的是onPanResponderGrand(當(dāng)用戶觸摸按鈕時觸發(fā))和onPanResponderRelease(當(dāng)用戶從屏幕上移開手指時觸發(fā))兩個句柄;

我還設(shè)置了一個動畫對象實(shí)例,幫助我們處理動畫。將其值設(shè)置為零;然后我們定義_setOpacity方法,調(diào)用時改變this.opacityAnimated的值。在渲染之前,我們插入this.opacityAnimated為正常的opacity值。我們不使用View而是使用Animated.View模塊為了使用動態(tài)變化的opacity值。

通過上面的例子,你會發(fā)現(xiàn)Animated API不難理解,你只需要閱讀相關(guān)的API文檔,以確保你的應(yīng)用程序***運(yùn)行。希望這個例子能幫你開個好頭。

在使用React Native開發(fā)時可能會遇到很多問題,希望這篇文章能幫助你避免一些錯誤。

責(zé)任編輯:未麗燕 來源: 前端工坊公眾號
相關(guān)推薦

2017-10-17 16:23:58

函數(shù)式編程ReduxReact

2021-12-16 06:52:33

Ceph分布式對象

2022-06-10 08:01:17

ReduxReact

2020-10-08 18:12:36

數(shù)據(jù)科學(xué)職位面試數(shù)據(jù)科學(xué)家

2016-08-12 13:55:06

2017-09-11 14:35:34

編輯器開發(fā)環(huán)境React

2020-04-20 18:15:46

開發(fā)自信技術(shù)

2021-07-25 21:36:24

Windows操作系統(tǒng)功能

2020-05-17 16:10:36

開發(fā)人員軟件開發(fā)開發(fā)

2016-10-31 11:26:13

ReactRedux前端應(yīng)用

2022-04-02 15:11:04

工具APIUI

2016-11-23 16:48:20

react-nativandroidjavascript

2015-03-02 15:30:11

2015-04-17 09:47:57

2018-07-06 15:00:50

碼農(nóng)科技開發(fā)

2015-08-06 17:15:28

2017-03-21 21:37:06

組件UI測試架構(gòu)

2020-12-22 13:49:23

開發(fā)編碼框架

2017-01-04 10:18:00

React NativScrollViewAndroid

2021-03-09 09:52:55

技術(shù)React Hooks'數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號