rn-relates icon indicating copy to clipboard operation
rn-relates copied to clipboard

组件封装

Open ljunb opened this issue 8 years ago • 1 comments

props & state

在进行组件封装之前,需要先对 propsstate 有所了解,这里是官方文档说明。简单地讲,props 是作为参数传递到子组件的,子组件通过接收不同的 props ,结合组件已有的通用功能或是样式,达到组件复用效果。当组件内部需要更新某些状态的时候,这时候则需要使用到 state

无状态组件(Stateless Functional Component)

在大部分情况下,封装的组件都不需要自己维护内部状态,只当做 UI 展示组件,由外部传入的 props 来决定不同的渲染效果。在这种情况下,我们应当优先选择无状态组件,这里只用 ES6 语法作为示例:

/*
 * StaticCell.js 
 * Stateless Functional Component
 */
// import ...
const StaticCell = ({ title, icon }) => {
  return (
    <View style={styles.cell}>
      <Text style={styles.title}>{title}</Text>
      <Image source={icon} style={styles.icon}/>
    </View>
  )
}

StaticCell.propTypes = {
  title: React.PropTypes.string,
  icon: React.PropTypes.any
};

StaticCell.defaultProps = {
  title: '个人信息',
  icon: require('./img/arrow.png')
};

export default StaticCell;

在封装无状态组件时,建议配合 ES6 的解构、箭头函数语法,无论从编写到阅读,这种方式都比较简洁。不过无状态组件不支持ref引用,没有生命周期方法和内部状态管理,在组件渲染的时候直接挂载,省略了一些生命周期方法中的检查处理,也没有了新建类实例操作,所以开销是比较小的。该方式应当做组件封装的优先方式。

类组件(Class Component)

当然,并不是所有的组件都是无状态组件,比如计时器组件。该例子主要基于自己封装的组件 rn-coundown,在一个获取验证码的计时器组件中,我们可以根据其行为表现来判断 propsstate 的组成:

  • 计时状态:一个计时器组件,其状态应包括初始、计时中和计时结束三种状态。当外界改变组件初始状态后,计时中、计时结束都应由组件自己维护,因此在 state 中设置 status 变量,赋值范围为 IdleCountingOver ,分别代表不同状态
  • 秒数:毫无疑问需要在 state 中保存秒数变量,这里命名 second

state 中保存以上变量即可。标题、计时中标题、计时结束标题,这三个文案实际上基于计时器状态,所以当外界通过 props 提供相应值后,组件内部通过自己的 status 来设置不同文案即可。在计时的不同状态中,文案样式可能不同,所以这些设置都可以列入 props 里面,由外界提供。示例代码主要演示不同状态下的标题设置:

render() {
  const { status, second } = this.state;
  const { title, countingTitleTemplate, overTitle } = this.props;
  // 初始标题,如:获取验证码
  let promptTitle = title;
  if (status === CountdownStatus.Counting) {
    // 计时中标题,如:60s后重新获取
    promptTitle = countingTitleTemplate.replace('{time}', second);
  } else if (status === CountdownStatus.Over) {
    // 计时结束标题,如:重新获取
    promptTitle = overTitle;
  }
  return <View>...</View>
}

在发起一个验证码请求之前,一般都会做一些逻辑处理,比如是否输入手机号码,或者手机号码是否合法,在未满足某些业务逻辑之前,组件是不应开始计时操作的。那么,如何来捕捉组件外这些行为呢?

在当前组件的最新版本中,提供了 shouldStartCountdown 函数作为参数,该函数会返回一个 bool 值,当点击组件时,组件内部会针对该返回值来控制计时器的开启时机:

handlePress = () => {
  if (this.isNetworkFailed() || !this.canStartTimer()) return;

  this.setState({status: CountdownStatus.Counting}, this.startTimer);
  this.shouldShowWarningInfo();
};

isNetworkFailed = () => {
  const {onNetworkFailed} = this.props;
  const {isConnected} = this.state;
  // network is failed
  if (!isConnected) {
    onNetworkFailed && onNetworkFailed();
  }
  return !isConnected;
};

canStartTimer = () => {
  const {shouldHandleBeforeCountdown, shouldStartCountdown} = this.props;

  let canStartTimer = shouldStartCountdown();
  if (shouldHandleBeforeCountdown !== undefined && typeof shouldHandleBeforeCountdown === 'function') {
    canStartTimer = shouldHandleBeforeCountdown();
    console.warn(`[rn-countdown] Warning: "shouldHandleBeforeCountdown" is deprecated, use "shouldStartCountdown" instead.`);
  }
  return canStartTimer;
};

以上部分代码对老版本组件的参数做了兼容处理,同时也给出了相应提示,以方便开发人员跟进这些变更。因同事使用反馈,最新版本中也添加了对网络状态的处理,当网络连接失败时,则提供 onNetworkFailed 回调,开发人员可在该回调中做一些提示处理,并且倒计时不会开始。

外部使用:

shouldStartCountdown = () => {
  // 可以开始计时
  if (this.phoneNumber) return true;

  // 不满足业务逻辑,返回false表示不能开始计时
  alert('电话号码不能为空!');
  return false;
};

除此之外,当计时器运行过程中遇到网络请求失败,或是其他情况,需要手动停止计时器,显示计时结束文案,那么基于这种需求,可以向外提供一个 stopCountdown 方法,然后外界通过 ref 引用来结束计时器。

总结:

  • 封装一个类组件,记住大部分的变量都可以作为 props,只有影响内部状态的,才需要放在 state
  • 在实际项目中去使用组件,然后借助业务逻辑,不断完善组件功能
  • 按需在 shouldComponentUpdate(nextProps, nextState) 方法中做优化处理
  • 组件的参数为简单数据类型时,使用 PureComponent

附类组件基本代码规范:

export default class MyComponent extends Component {
  static displayName = 'MyComponent';
  
  static propTypes = {};

  static defaultProps = {};

  constructor(props) {
    super(props);
    this.state = {}
  }
  // React 16.3 将移除该生命周期方法
  componentWillMount() {}

  componentDidMount() {}

  // React 16.3 将移除该生命周期方法
  componentWillReceiveProps(nextProps) {}

  shouldComponentUpdate(nextProps, nextState) {}

  componentWillUnmount() {}

  render() {}
}

ljunb avatar Aug 24 '17 07:08 ljunb

最新版本:

  • 已经移除旧的shouldHandleBeforeCountdown ,不再对该props做判断和信息提示
  • startCountdown中不再重复shouldStartCountdown的逻辑,只关注网络状态

Release 0.2.1

ljunb avatar Sep 06 '17 12:09 ljunb