React Native / Redux开发中,犯过的11个错误

原文链接:http://mp.weixin.qq.com/s/38y5YnFVurkqHsqe7L_NHw

在使用React Native近一年之后,是时候分享一下我刚开始用RN开发项目时犯过的错误了。

1.错误的估计

有可能你对第一个React Native(RN)应用程序的预估是完全错误的!

1)你需要分别考虑iOS和Android版本的布局!在布局的时候,有很多组件可以重复使用;如果ios和Android的页面结构不同,就需要对他们分开单独布局。

2)对form进行评估时,最好也考虑一下数据层验证。

3)了解数据库结构,有助于正确地规划redux

2.尽量使用基础组件(buttons,footers,headers,inputs,texts)

google搜索RN的基础组件,你会发现有很多现有组件可以方便的用到项目中,如buttons,footers等。如果项目中没有特别的布局设计,只需要使用这些基础组件就可以构建一个页面。如果有一些特殊的设计,例如,特殊的button样式,你就需要为每个按钮设置自定义样式。你可以封装已经构建好的组件,并为它们定制样式。但是我觉得使用View,Text,TouchableOpacity和RN的其他组件来构建自己的组件更加有意义。因为你会有更多的rn实践,并且深刻理解如何使用RN。最重要的一点,你可以确定你自己构建的组件版本不会被改变。

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

如果iOS和Android布局大致一样,只有一小部分不同,你可以简单地使用RN提供的Platform API根据设备平台进行区分。

如果布局完全不同 - 最好分散在不同的文件中单独布局。

如果你给一个文件命名为index.ios.js - 程序打包时,在iOS中将使用这个文件显示iOS布局。 index.android.js也是一样的道理。

你可能会问:“代码怎么复用呢?” 你可以将重复的代码移动到助手函数中。需要的时候只复用这些助手函数。

4.错误的redux store规划。

初学者经常会犯的一个很大的错误就是,当你在规划你的应用程序时,你可能会考虑很多布局相关的问题,却很少考虑关于数据的处理。

Redux能够帮助我们正确地存储数据。如果redux规划的好 - 它将是管理应用程序数据的强大工具。

当我刚刚开始构建RN应用程序时,我曾考虑将reducers作为每个container的数据存储。所以,如果你有登录,忘记密码,待办事项列表页面 - 使用他们的缩写比较简单:SignIn, Forgot, ToDoList.

在进行了一段工作后,我发现管理数据没有想象中的容易。

当我从ToDo列表中选择项目时 - 我需要将数据传递给ToDoDetails reducer。这意味着使用了额外的操作来发送数据到reducer。

在做了一些调查之后,我决定以不同的方式规划结构。一个例子:

1.Auth

2.Todos

3.Friends

Auth用于存储认证的token。

而ToDos和Friends reducers用于存储实体,当我去ToDo Detail页面时 - 我只需要通过ID搜索所有的ToDos。

对于更复杂的结构,我推荐使用这种规划,你可以快速的定位到你想找到的。

5.错误的项目结构

作为初学者时,总是规划不好项目结构。

首先 ,需要分析你的项目是不是足够大?

你的应用程序中有多少个页面? 20?30?10?5?还是只有一个"Hello World"页面?

我遇到并开始实施的第一个结构是这样的:

如果你的项目不超过10个页面,使用上面这种结构是没有问题的。但是如果项目特别大 - 你可以考虑这样规划结构:

区别在于,第一种类型建议我们将actions和reducers与container分开存储。第二种- 把它们存储在一起。如果应用程序很小 - 将redux模块从container中分离出来会更加有用。

如果你有通用的样式(如Header、Footer、Buttons) - 你可以单独创建一个名为“styles”的文件夹,在那里设置一个index.js文件并在其中写入通用样式。然后在每个页面上重复使用它们。

实际项目中会有很多不同的结构。你应该了解使用哪种结构更适合你的需求。

6.错误的container结构。没有从一开始就使用smart/dumb组件

当你开始使用RN并初始化项目时,index.ios.js文件中已经有一些代码,存储在一个单独的对象中。

在实际开发项目中,你将需要使用很多组件,不仅仅是由RN提供的,还有自己构建的一些组件。构建container时,可以重用它们。

考虑该组件:

import React, { Component } from ‘react’;import {   Text,   TextInput,   View,   TouchableOpacity} from ‘react-native’;import styles from ‘./styles.ios’;

exportdefaultclassSomeContainer extends Component {   constructor(props){       super(props);

this.state = {           username:null}   }   _usernameChanged(event){

this.setState({           username:event.nativeEvent.text       });    }   _submit(){

if(this.state.username){

console.log(`Hello, ${this.state.username}!`);       }

else{

console.log(‘Please, enter username’);       }    }

render() {

return(

                       source={this.props.image}style={styles.avatar}/>

Username

Submit

);    }}

所有样式都存储在一个单独的模块中。

包裹在TouchableOpacity中的button组件应该单独分离出来,这样才能方便我们以后重复使用它。Image组件,以后也可能重复使用,所以也应该把它分离出来。

做了一些改变之后的样子:

import React, { Component, PropTypes } from'react';import {    Text,    TextInput,    View,    TouchableOpacity} from'react-native';import styles from'./styles.ios';

classAvatar extends Component{    constructor(props){        super(props);    }

render(){if(this.props.imgSrc){return()         }         return null;    }}

Avatar.propTypes = {    imgSrc: PropTypes.object}class FormItem extends Component{    constructor(props){        super(props);    }    render(){        let title = this.props.title;        return({title})    }}FormItem.propTypes = {    title: PropTypes.string,    value: PropTypes.string,    onChange: PropTypes.func.isRequired}class Button extends Component{    constructor(props){        super(props);    }    render(){        let title = this.props.title;        return({title})    }}

Button.propTypes = {    title: PropTypes.string,    onPress: PropTypes.func.isRequired}export default class SomeContainer extends Component {    constructor(props){        super(props);        this.state = {            username:null        }    }    _usernameChanged(event){        this.setState({            username:event.nativeEvent.text         });    }    _submit(){        if(this.state.username){            console.log(`Hello, ${this.state.username}!`);        }        else{            console.log('Please, enter username');        }    }    render() {        return ();    }}

现在的代码看起来更多了 - 因为我们为Avatar,FormItem和Button组件添加了包装器,但现在我们可以在需要的地方重复使用这些组件。我们可以将这些组件移动到单独的模块中,并导入我们需要的任何地方。我们也可以添加其他一些Props,例如style,TextStyle,onLongPress,onBlur,onFocus。而且这些组件是完全可以定制的。

注意,一定不要深度定制一个小组件, 这样会使组件过于繁琐,代码会变的很难阅读。即使现在添加新属性的想法看起来像是解决任务的最简单的方法,将来这个小小的属性在阅读代码时可能会引起困惑。

关于理想的smart/dumb组件,看一下这个:

classButton extends Component{    constructor(props){        super(props);    }    _setTitle(){

const{ id } =this.props;

switch(id){

case0:

return'Submit';

case1:

return'Draft';

case2:

return'Delete';

default:

return'Submit';         }    }

render(){lettitle =this._setTitle();return({title}

)    }}Button.propTypes = {    id: PropTypes.number,    onPress: PropTypes.func.isRequired}export default class SomeContainer extends Component {    constructor(props){        super(props);        this.state = {            username:null        }    }    _submit(){        if(this.state.username){            console.log(`Hello, ${this.state.username}!`);        }        else{            console.log('Please, enter username');        }    }    render() {        return (

}}

我们已经“升级”了Button组件。用一个叫做“id”的新属性来替换属性“title”。现在Button组件就变的“灵活”了。传0 - button组件会显示“submit”。传2 - 显示“delete”。但这可能会有一些问题。

Button被创建为一个dumb组件 - 只是为了显示数据,传递数据这件事由它的更高一级的组件来完成。

如果我们将5作为id传递给这个组件,我们就需要更新组件,以便让它适应这个改动。dumb组件,就是细分的小组件,它只要接收props就好了,如果有state也应该与全局的无关。

7.行内样式

在使用RN布局之后,我遇到了行内样式的写作风格问题。类似这样:

render() {

return(

);}

当你这样写的时候,你会想:“暂时这样写,等我在模拟器中运行之后 - 如果布局没问题,再把样式移动到单独的模块。”也许这是一个好的想法。但是..不幸的是,你往往会选择性忽略行内样式...

一定要在独立的模块中编写样式,远离行内样式。

8.使用redux验证表单

要使用redux来验证表单,我需要在reducer中创建action,actionType单独的字段,这样做很麻烦。

所以我决定只借助state来完成验证。没有reducers,types等等,只是在container级别上的纯功能函数。从action和reducer文件中删除不必要的函数,这个策略对我帮助很大。

9.过于依赖zIndex

很多人从web开发转到RN开发。在web中有一个css属性z-index,它可以帮助我们在需要的层级显示我们想要的内容。

在RN中,一开始没有这样的特性。但后来又被添加进来了。起初,使用起来还挺简单的。只需为元素设置zIndex属性,它就会按照任何你想要的图层顺序来渲染。但是在Android上测试之后…现在我只用zIndex来设置展示层的结构。

10.不仔细阅读外部组件的源码

你可以引入外部组件来节省你的开发时间。

但有时这个模块可能会中断,或者不像描述的那样工作。阅读源码你才会明白哪里出现了错误。也许这个模块本身就有问题,或者你只是用错了。另外 - 如果你仔细阅读其他模块的源码,你将会学习到如何构建自己的组件。

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

RN为我们提供了构建完全原生应用程序的能力。怎么让用户感觉是原生应用?页面布局,滑动手势,还是展示动画?

当你使用View,Text,TextInput和其他RN提供的默认模块时,手势和动画应该由PanResponder和Animated API来处理。

如果你是从web转过来的rn开发工程师,获取用户的手势操作可能会有些困难,你需要区分什么时候开始,什么时候结束,长按,短按。你可能还不够清楚怎么在RN中模拟这些动画操作。

这是我用PanResponder和Animated建立的Button组件。这个button是为了捕捉用户手势而构建的。例如 - 用户按下项目,然后将手指拖到一边。在按下按钮时,借助于动画API,构建button按压下的不透明度的变化:

'use strict';import React, { Component, PropTypes } from'react';import { Animated, View, PanResponder, Easing } from'react-native';import moment from'moment';

exportdefaultclassButton extends Component {    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) => {/**THIS EVENT IS CALLED WHEN WE PRESS THE BUTTON**/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) => {

/**THIS EVENT IS CALLED WHEN WE RELEASE THE BUTTON**/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){

/**SETS OPACITY OF THE BUTTON**/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('onPressOut is not defined'); },    onLongPress: ()=>{ console.log('onLongPress is not defined'); },    onPress: ()=>{ console.log('onPress is not defined'); },    style: {},    image: null};const styles = {    mainBtn:{        width:55,        height:55,        backgroundColor:'rgb(255,255,255)',      }};

首先,初始化PanResponder对象实例。它有一套不同的操作句柄。我感兴趣的是onPanResponderGrand(当用户触摸按钮时触发)和onPanResponderRelease(当用户从屏幕上移开手指时触发)两个句柄;

我还设置了一个动画对象实例,帮助我们处理动画。将其值设置为零;然后我们定义_setOpacity方法,调用时改变this.opacityAnimated的值。在渲染之前,我们插入this.opacityAnimated为正常的opacity值。我们不使用View而是使用Animated.View模块为了使用动态变化的opacity值。

通过上面的例子,你会发现Animated API不难理解,你只需要阅读相关的API文档,以确保你的应用程序完美运行。希望这个例子能帮你开个好头。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容