您的位置:首页 » 分类: 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

关注WEB前端开发官方公众号

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

2 条评论 - 关于 “React 教程:函数作为子组件(Function as Child Components)

  1. 写的蛮好的,学习到了,
    没有楼主这么牛,但业余时间也整理了些东西,有兴趣的可以看下:https://github.com/meibin08

发表评论

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