Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

组件封装 #1

Open
ljunb opened this issue Aug 24, 2017 · 1 comment
Open

组件封装 #1

ljunb opened this issue Aug 24, 2017 · 1 comment
Labels

Comments

@ljunb
Copy link
Owner

ljunb commented Aug 24, 2017

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 ljunb added the 入门 label Aug 24, 2017
@ljunb
Copy link
Owner Author

ljunb commented Sep 6, 2017

最新版本:

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

Release 0.2.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant