使用ReactNative快一年了,是时候分享一下我刚开始用RN开发项目时犯的错误了。1.错误估计您对第一个ReactNative(RN)应用程序的估计可能是完全错误的!1)你需要分别考虑iOS和Android版本的布局!在布局方面,有很多组件可以重复使用;如果ios和android的页面结构不同,需要分开布局。2)评估表单时,最好同时考虑数据层验证。3)了解数据库结构有助于正确规划redux2。尝试使用基本组件(按钮、页脚、页眉、输入、文本)谷歌搜索RN的基本组件,你会发现有很多现有的组件可以很容易地用在项目中,比如按钮、页脚等。如果项目中没有特殊的版面设计,只需要使用这些基本组件搭建一个页面即可。如果有一些特殊的设计,比如特殊的按钮样式,则需要为每个按钮设置自定义样式。您可以包装已经构建的组件并为它们自定义样式。但我认为使用View、Text、TouchableOpacity和RN的其他组件构建我自己的组件更有意义。因为你会有更多的RN实践,对如何使用RN有深刻的理解。最重要的是,您可以确定您自己构建的组件版本不会被更改。3.不要将iOS和Android的布局分开。如果iOS和Android的布局大致相同,只有一小部分不同,可以简单的使用RN提供的PlatformAPI来根据设备平台进行区分。如果布局完全不同-***单独文件中的单独布局。如果您将文件命名为index.ios.js-它将用于在打包应用程序时在iOS中显示iOS布局。index.android.js也是如此。你可能会问:“代码如何复用?”您可以将重复的代码移动到辅助函数中。仅在需要时重用这些辅助函数。4.reduxstore规划错误。初学者经常犯的一个大错误是,当你规划你的应用程序时,你可能会考虑很多与布局相关的问题,但很少考虑数据处理。Redux可以帮助我们正确存储数据。如果redux规划得当——它将成为管理应用程序数据的强大工具。当我第一次开始构建RN应用程序时,我考虑过将reducers作为每个容器的数据存储。因此,如果您有登录、忘记密码、待办事项列表页面-使用它们的缩写更容易:SignIn、Forgot、ToDoList。做了一段时间后,我发现管理数据并没有我想象的那么容易。当我从ToDo列表中选择项目时-我需要将数据传递给ToDoDetailsreducer。这意味着使用额外的操作将数据发送到reducer。在做了一些研究之后,我决定以不同的方式规划结构。示例:AuthTodosFriendsAuth用于存储身份验证令牌。ToDos和Friendsreducers用于存储实体,当我转到ToDo详细信息页面时-我只需要按ID搜索所有ToDos。对于比较复杂的结构,我推荐使用这种规划,可以快速定位到你想找的东西。5.错误的项目结构当你是初学者的时候,你总是计划错误的项目结构。首先,你需要分析你的项目是否足够大?您的应用程序中有多少页?20?30?10?5?或者只有一个“HelloWorld”页面?结构是这样的:如果你的项目不超过10页,使用上面的结构是没有问题的。但是如果项目特别大——可以考虑这样规划结构:不同的是,第一种建议我们把actions和reducers和container分开存放。第二-将它们存储在一起。如果应用程序很小——将redux模块与容器分开会更有用。如果您有通用样式(例如页眉、页脚、按钮)-您可以创建一个名为“styles”的文件夹,在其中设置一个index.js文件并在其中编写通用样式。然后在每个页面上重复使用它们。实际项目中有很多不同的结构。您应该了解使用哪种结构更适合您的需要。6.错误的容器结构。不要从一开始就使用智能/哑组件当您开始使用RN并初始化项目时,index.ios.js文件中已经有一些代码,存储在一个单独的对象中。在实际的开发项目中,会需要用到很多组件,不仅有RN提供的,还有一些自己搭建的组件。它们可以在构建容器时重复使用。思考该组件:importReact,{Component}from'react';import{Text,TextInput,View,TouchableOpacity}from'react-native';importstylesfrom'./styles.ios';exportdefaultclassSomeContainerextendsComponent{constructor(props){super(props));this.state={username:null}}_usernameChanged(event){this.setState({username:event.nativeEvent.text});}_submit(){if(this.state.username){console.log(`你好,${this.state.username}!`);}else{console.log('Please,enterusername');}}render(){return(用户名Submit);}}所有的样式都存放在一个单独的模块中TouchableOpacity包裹的按钮组件应该单独分开,这样我们可以稍后重用它。Image组件以后可能会被复用,所以也应该分离出来。做了一些修改之后的样式:importReact,{Component,PropTypes}from'react';import{Text,TextInput,View,TouchableOpacity}from'react-native';importstylesfrom'./styles.ios';classAvatareextendsComponent{constructor(props){super(props);}render(){if(this.props.imgSrc){return()}returnnull;}}Avatar.propTypes={imgSrc:PropTypes.object}classFormItemextendsComponent{constructor(props){super(props);}render(){lettitle=this.props.title;return({title})}}FormItem.propTypes={title:PropTypes.string,value:PropTypes.string,onChange:PropTypes.func.isRequired}classButtonextendsComponent{constructor(props){super(props);}render(){lettitle=this.props.title;return({title})}}Button.propTypes={title:PropTypes.string,onPress:PropTypes.func.isRequired}exportdefaultclassSomeContainerextendsComponent{构造函数(props){super(props);this.state={username:null}}_usernameChanged(事件){this.setState({username:event.nativeEvent.text});}_submit(){if(this.state.username){console.log(`Hello,${this.state.username}!`);}else{console.log('Please,enterusername');}}render(){return();}}现在代码看起来更多了——因为我们添加了包装器对于Avatar、FormItem和Button组件,但现在我们可以在需要的地方重用这些组件。我们可以将这些组件移动到单独的模块中,并在需要的地方导入它们。我们还可以添加一些其他的Prop,如style、TextStyle、onLongPress、onBlur、onFocus。这些组件是完全可定制的。注意一定不要深度定制一个小组件,这样会使组件过于笨重,代码也变得难以阅读。即使添加新属性的想法现在看起来是解决任务的最简单方法,但将来这个小属性可能会在阅读代码时造成混淆。对于一个理想的智能/哑组件,看看这个:classButtonextendsComponent{constructor(props){super(props);}_setTitle(){const{id}=this.props;switch(id){case0:return'Submit';case1:return'Draft';case2:return'Delete';默认:return'Submit';}}render(){lettitle=this._setTitle();return({title})}}Button.propTypes={id:PropTypes.number,onPress:PropTypes.func.isRequired}exportdefaultclassSomeContainerextendsComponent{constructor(props){super(props);this.state={username:null}}_submit(){if(this.state.username){console.log(`Hello,${this.state.username}!`);}else{console.log('请输入用户名');}}render(){return(}}我们已经“更新”了Button组件。将属性“title”替换为名为“id”的新属性。现在Button组件变得“灵活”。传递0-按钮组件将显示“提交”。通过2-显示“删除”。但这可能会产生一些问题。Button被创建为一个哑组件-只是为了显示数据,传递数据由其更高级别的组件完成。如果我们将5作为id传递给此组件,则需要更新组件以适应此更改。dumb组件是一个细分的小组件。它只需要接收道具。如果有一个状态,它应该与全局无关。7、inlinestyle使用RN布局后遇到了inlinestyle的写法问题。像这样的东西:render(){return();}当你写这个的时候,你在想,“现在就写这个,直到我在模拟器中运行它-如果布局没问题,然后将样式移动到一个单独的模块。“也许这是个好主意。但是……不幸的是,您倾向于选择性地忽略内联样式……一定要在单独的模块中编写样式,远离内联样式。8、使用redux验证表单要使用redux验证表单,我需要在reducer中创建action和actionType分开的字段,非常麻烦。所以我决定只使用状态来完成验证。没有减速器、类型等,只有容器级别的纯功能。这种从action和reducer文件中删除不必要函数的策略对我帮助很大。9.过度依赖zIndex许多人从web开发转向RN开发。web中有一个css属性z-index,可以帮助我们在需要的层次上显示我们想要的内容。在RN中,一开始是没有这个功能的。不过是后来加的。起初,它使用起来非常简单。只需在元素上设置zIndex属性,它将以您想要的任何层顺序呈现。但是在Android上测试之后......现在我只使用zIndex来构建表示层。10、不仔细阅读外部组件的源代码可以引入外部组件来节省开发时间。但有时此模块可能会损坏,或无法按描述工作。只有阅读源码才能明白哪里出错了。可能模块本身有问题,或者你只是用错了。此外-如果您仔细阅读其他模块的源代码,您将学习如何构建您自己的组件。11.小心手势和动画API。RN为我们提供了构建完全原生应用程序的能力。如何让用户感觉像一个原生应用程序?页面布局、滑动手势或显示动画?当你使用RN提供的View、Text、TextInput等默认模块时,手势和动画应该由PanResponder和AnimatedAPI处理。如果你是从web转过来的rn开发工程师,可能很难获取到用户的手势操作。你需要区分什么时候开始,什么时候结束,长按,短按。你可能还不够了解如何在RN中模拟这些动画操作。这是我使用PanResponder和Animated构建的Button组件。这个按钮是用来捕捉用户手势的。例如-用户按下一个项目,然后将手指拖到一边。在按下按钮时,借助于动画API,构造按钮按下压力下的不透明程度的变化:'usestrict';importReact,{Component,PropTypes}from'react';import{Animated,View,PanResponder,Easing}from'react-native';importmomentfrom'moment';exportdefaultclassButtonextendsComponent{constructor(props){super(props);this.state={timestamp:0};this.opacityAnimated=newAnimated.Value(0);this.panResponder=PanResponder.create({onMoveShouldSetPanResponderCapture:(evt,gestureState)=>true,onStartShouldSetResponder:()=>true,onStartShouldSetPanResponder:()=>true,onMoveShouldSetPanResponder:(evt,gestureState)=>true,onPanResponderMove:(e,gesture)=>{},onPanResponderGrant:(evt,gestureState)=>{/**THISEVENTISCALLEDWHENWEPRESSTHEBUTTON**/this._setOpacity(1);this.setState({timestamp:moment()});this.long_press_timeout=setTimeout(()=>{this.props.onLongPress();},1000);},onPanResponderStart:(e,gestureState)=>{},onPanResponderEnd:(e,gestureState)=>{},onPanResponderTerminationRequest:(evt,gestureState)=>true,onPanResponderRelease:(e,gesture)=>{/**THISEVENTISCALLEDWHENWERELEASETHEBUTTON**/letdiff=moment().diff(moment(this.state.timestamp));if(diff<1000){this.props.onPress();}clearTimeout(this.long_press_timeout);this._setOpacity(0);this.props.releaseBtn(gesture);}});}_setOpacity(value){/**SETSOPACITYOFTHEBUTTON**/Animated.timing(this.opacityAnimated,{toValue:value,duration:80,}).start();}render(){letlongPressHandler=this.props.onLongPress,pressHandler=this.props.onPress,image=this.props.image,opacity=this.opacityAnimated.interpolate({inputRange:[0,1],outputRange:[1,0.5]});return({image})}}Button.propTypes={onLongPress:PropTypes.func,onPressOut:PropTypes。func,onPress:PropTypes.func,style:PropTypes.object,image:PropTypes.object};Button.defaultProps={onPressOut:()=>{console.log('onPressOutisnotdefined');},onLongPress:()=>{console.log('onLongPressisnotdefined');},onPress:()=>{console.log('onPressisnotdefined');},style:{},image:null};conststyles={mainBtn:{width:55,height:55,backgroundColor:'rgb(255,255,255)',}};首先,初始化具有一组不同操作句柄的PanResponder对象实例。我感兴趣的是两个处理程序onPanResponderGrand(当用户触摸按钮时触发)和onPanResponderRelease(当用户从??屏幕上移开手指时触发);我还设置了一个动画对象实例来帮助我们处理动画。将其值设置为零;然后我们定义_setOpacity方法,它会在调用时更改this.opacityAnimated的值。在渲染之前,我们插入带有正常不透明度值的this.opacityAnimated。我们不使用View,而是使用Animated.View模块,以便使用动态变化的不透明度值。通过上面的例子,你会发现AnimatedAPI并不难理解,你只需要阅读相关的API文档就可以保证你的应用程序完美运行。希望这个例子能给你一个好的开始。在使用ReactNative进行开发时,您可能会遇到很多问题,希望本文能帮助您避免一些错误。