您的位置:首页 » 分类: JavaScript & ES2015 (ES6) & React » 文章: React 教程:函数作为子组件(Function as Child Components)

React 教程:函数作为子组件(Function as Child Components)

小编推荐:掘金是一个面向程序员的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。

愚人码头注:这种模式也叫 渲染回调(Render Callback) , 现在最新的叫法为:渲染属性(Render Props),请参阅官方文档。这是一篇老文章,主要讲解该模式简洁和灵活,对于我们理解这种模式非常有帮助。

我最近在推特上就 高阶组件 和 函数作为子组件 进行了调查,结果让我感到惊讶。

虽然我非常感谢我的朋友 Ryan Florence 的回复:每次都使用函数作为子组件。 我想不出 高阶组件(HOC) 更强大的场景。

每个人都应该 “重新思考最佳实践”?如果你不知道 “函数作为子组件” 模式是什么,这篇文章我要阐述的内容:

  1. 告诉你什么是函数作为子组件。
  2. 说服你为什么它很有用。

什么是函数作为子组件?

“函数作为子组件” 是一个组件,这个组件接收一个函数作为其子组件。由于 React 的属性(property) 类型,该模式可以简单地实现并且值得推广。

class MyComponent extends React.Component { 
  render() {
    return (
      <div>
        {this.props.children('Scuba Steve')}
      </div>
    );
  }
}
MyComponent.propTypes = {
  children: React.PropTypes.func.isRequired,
};

愚人码头注: 从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,我们建议使用 prop-types 库 来定义contextTypes。 即你需要手动引入 import PropTypes from 'prop-types';

这就函数作为子组件!通过使用函数作为子组件,我们将父组件和子组件分离,让使用者决定如何将参数应用于子组件。例如:

<MyComponent>
  {(name) => (
    <div>{name}</div>
  )}
</MyComponent>

而使用相同组件的其他人可能决定以不同的方式应用 name ,也许是用于属性:

<MyComponent>
  {(name) => (
    <img src="/scuba-steves-picture.jpg" alt={name} />
  )}
</MyComponent>

这里真正干净的是 MyComponent,作为子组件的函数可以代表它所组成的组件管理状态,函数作为子组件可以代表它组成的组件管理状态,而无需知道子组件如何利用 state(状态)。让我们继续讨论一个更现实的例子。

Ratio Component

Ratio 组件将使用当前设备宽度,监听 resize 事件并子组件中调用宽度,高度和一些关于它是否已经计算出尺寸的信息。

首先,我们从函数作为子组件的片段开始,这在所有函数作为子组件的模式中很常见,它只是让使用者知道我们期望一个函数作为我们的子组件,而不是React节点。

class Ratio extends React.Component {
  render() {
    return (
        {this.props.children()}
    );
  }
}
Ratio.propTypes = {
 children: React.PropTypes.func.isRequired,
};

接下来让我们设计我们的API,我们想要按 X 和 Y 轴提供的一个比率,然后我们将使用当前宽度和高度来计算,让我们设置一些内部 state(状态) 来管理宽度和高度,我们是否还计算过,以及一些 propTypes 和 defaultProps 。这些对于使用我们组件的人来说是好东西。

class Ratio extends React.Component {
  constructor() {
    super(...arguments);
    this.state = {
      hasComputed: false,
      width: 0,
      height: 0, 
    };
  }
  render() {
    return (
      {this.props.children()}
    );
  }
}
Ratio.propTypes = {
  x: React.PropTypes.number.isRequired,
  y: React.PropTypes.number.isRequired,
  children: React.PropTypes.func.isRequired,
};
Ratio.defaultProps = {
  x: 3,
  y: 4
};

我们还没有做任何有趣的事情,让我们添加一些事件监听器并实际计算宽度(适应我们的比率变化时):

class Ratio extends React.Component {
  constructor() {
    super(...arguments);
    this.handleResize = this.handleResize.bind(this);
    this.state = {
      hasComputed: false,
      width: 0,
      height: 0, 
    };
  }
  getComputedDimensions({x, y}) {
    const {width} = this.container.getBoundingClientRect();
return {
      width,
      height: width * (y / x), 
    };
  }
  componentWillReceiveProps(next) {
    this.setState(this.getComputedDimensions(next));
  }
  componentDidMount() {
    this.setState({
      ...this.getComputedDimensions(this.props),
      hasComputed: true,
    });
    window.addEventListener('resize', this.handleResize, false);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize, false);
  }
  handleResize() {
    this.setState({
      hasComputed: false,
    }, () => {
      this.setState({
        hasComputed: true,
        ...this.getComputedDimensions(this.props),
      });
    });
  }
render() {
    return (
      <div ref={(ref) => this.container = ref}>
        {this.props.children(this.state.width, this.state.height, this.state.hasComputed)}
      </div>
    );
  }
}
Ratio.propTypes = {
  x: React.PropTypes.number.isRequired,
  y: React.PropTypes.number.isRequired,
  children: React.PropTypes.func.isRequired,
};
Ratio.defaultProps = {
  x: 3,
  y: 4
};

我在这里做了很多事情。我们添加了一些事件监听器来监听 resize 事件以及使用提供的比率实际计算宽度和高度。所以我们有一个宽度和高度在我们的内部状态,我们如何将他共享给其他组件呢?

这是难以理解的事情之一,因为它很简单,当你看到它时,你会想到,“这不可能就是全部。”但这就是它的全部。

子组件实际上只是一个 JavaScript 函数。

这意味着为了将计算出的宽度和高度向下传递,我们只需将它们作为参数提供:

render() {
    return (
      <div ref='container'>
        {this.props.children(this.state.width, this.state.height, this.state.hasComputed)}
      </div>
    );
}

现在任何人都可以使用 Ratio 组件以他们想要的任何方式提供全屏的宽度和正确计算的高度!例如,有人可以使用 Ratio 组件在 img 上设置宽高比:

<Ratio>
  {(width, height, hasComputed) => (
    hasComputed
      ? <img src='/scuba-steve-image.png' width={width} height={height} />
      : null
  )}
</Ratio>

同时,在另一个文件中,有人决定使用它来设置CSS属性。

<Ratio>
  {(width, height, hasComputed) => (
    <div style={{width, height}}>Hello world!</div>
  )}
</Ratio>

在另一个应用程序中,有人使用基于计算高度有条件地渲染不同的子组件:

<Ratio>
  {(width, height, hasComputed) => (
    hasComputed && height > TOO_TALL
      ? <TallThing />
      : <NotSoTallThing />
  )}
</Ratio>

优势

  1. 组合组件的开发人员可以快速传递和使用这些属性。
  2. 函数作为子组件的模式不强制使用者如何利用其值,可以非常灵活的使用。
  3. 使用者不需要创建另一个组件来决定如何应用从 “高阶组件” 传入的属性。高阶组件通常在它们所组成的组件上强制要求属性名。为了解决这个问题,许多“高阶组件”提供者提供了一个选择器函数,允许使用者选择需要的属性名称(想想 redux-connects 选择函数)。在函数作为子组件的模式不存在这个问题。
  4. 不会污染 “props” 的命名空间,这允许您使用 “Ratio” 组件结合 “Pinch to Zoom” 组件使用,无论它们是否都有计算宽度。高阶组件带有他们对组成的组件施加了隐式约定,不幸的是,这可能意味着 prop 名称冲突。
  5. 高阶组件在您的开发工具和组件本身中创建一个间接层,例如,一旦包含在高阶组件中,高阶组件中的设置常量将无法访问。例如:MyComponent.SomeContant = ‘SCUBA’;,然后由高阶组件包裹,export default connect(...., MyComponent);。你的常数就好像死了。如果没有高阶组件提供访问底层组件类的函数,则再也访问不到它。伤心。

概要

大多数时候,您会认为“我需要一个更高阶的组件来实现这个共享功能!”我希望我已经说服你,函数作为子组件是抽象UI问题的更好选择,根据我的经验,这钟模式总是适用。除非你的子组件真正耦合到由它组成的高阶组件。

关于高阶组件的一个不幸的真相

作为一个辅助点,我认为高阶组件的名称不正确,尽管尝试更改其名称可能比较遥远。高阶函数是至少符合以下操作之一的函数:

  1. 将n个函数作为参数。
  2. 返回一个函数作为结果。

事实上,高阶组件做了与此类似的事情,即将一个组件作为参数并返回另一个组件,但我认为将高阶组件视为工厂函数更容易,它是一个动态创建允许组件的函数,用于组件的运行时组合。 但是,他们在组合时不知道你的 React state(状态) 和 props(属性) !

函数作为子组件的模式允许组件类似组合,以便在进行组合决策时可以访问 state(状态) , props(属性)和上下文。 因为函数作为子组件:

  1. 把函数作为一个参数。
  2. 渲染函数的返回结果。

使用 PropTypes 进行类型检查

我不禁觉得函数作为子组件的模式应该叫做“高阶组件”才对,因为它很像高阶函数,只使用组件组合技术而不是函数组合。

示例

  1. Pinch to Zoom — Function as Child Component
  2. react-motion这个项目经过很长一段时间,将高阶组件转换为我介绍了这个概念。

缺点

由于 函数作为子组件 在渲染时为您提供是函数,因此通常无法使用 shouldComponentUpdate 优化它们。

原文链接:https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9

关注亚洲城线上娱乐官方公众号

关注国内外最新最好的前端开发技术干货,获取最新前端开发资讯,致力于打造高质量的前端技术分享公众号

发表评论

电子邮件地址不会被公开。 必填项已用*标注