什么是 Render Props?

新的context api使用了render props

<ThemeContext.Consumer>{theme => (<button{...props}style={{backgroundColor: theme.background}}/>)}
</ThemeContext.Consumer>

第一次见到这个语法时,大多会很惊讶,因为日常代码里props.children必然是字符串或者元素。但事实上props.children可以是函数,只要最终生成的render的返回值是dom元素就行。例如:

// chilren props
const Test = props => props.children('hello world')
const App = () => (<Test>{text => <div>{text}</div>}</Test>
)ReactDOM.render((<App />, root)  // 返回<div>hello world</div>

虽然没有实际意义,但这即是一个 render props。当然render props最初的意思是:组件不自己定义render函数,而是通过一个名为renderprops将外部定义的render函数传入使用。 以上例来说,会是这样:

// render props
const Test = props => props.render('hello world')
const App = () => (<Testrender={text => <div>{text}</div>}/>
)ReactDOM.render((<App />, root)  // 返回<div>hello world</div>

因为现实中render函数很庞大,为了代码整洁多半会使用children而不是自定义的render来接收外部的render函数。所以这一技巧也可以称为children props(相对于render props更加不知所云的名称),但一般统称render props

为何要使用如此怪异的语法呢?

为了重用性。React的组件化就是为了方便重用。大多数场景我们需要重用的是UI(例如文章列表,侧栏),但也有少数情况需要重用的是功能和状态(例如context)。

如果说React的核心是State => UI, 普通的组件是UI重用,那么render props就是为了State重用而应运而生的。

Render Props 小史

在 Demo 展开前插播一段 render props 的趣史。

  • 最早引人关注是从 Facebook 的 Cheng Lou 写的 React Motion 动画库。
import { Motion, spring } from 'react-motion';<Motion defaultStyle={{x: 0}} style={{x: spring(10)}}>{value => <div>{value.x}</div>}
</Motion>

之后这一写法被各位大牛广泛接受,写过很多非常赞的前端教程的 Kent C. Dodds 就非常喜欢 render props, 他所任职的 PayPal 的输入框组件 downshift 也使用了 render props

大家熟知的 react-router 的作者 Michael Jackson 也是 render props 的极力推崇者。他twitter过一句很有争议的话:

Next time you think you need a HOC (higher-order component) in @reactjs, you probably don't.

翻译过来就是:下次你想使用HOC解决问题时,其实大半不需要。 在回复中他补充说明到,

I can do anything you're doing with your HOC using a regular component with a render prop. Come fight me.

即是说,所有用 HOC 完成的事,render props 都能搞定。 值得一提的是 react-router 4 里唯一的一个 HOC 是withRouter, 而它是用 render props 实现的,有兴趣的可以去看一下源代码。

  • HOC 虽然好用,但写一个“真正好用”的HOC却要经过一道道繁琐的工序(React的新api fowardRef 就几乎是为此而生的),是个用着舒服写着烦的存在。所以感觉最近大有“少写 HOC 推崇 render props”的思潮。至于新的 Context api 虽然思路上和react-redux如出一辙,却选择了 render props 的写法,在我看来也是顺理成章。

Render Props 使用场景

实例1: 一个日常的使用场景是弹窗。App的弹窗UI可能千奇百怪,但它们的功能却是类似的:无非有个显示隐藏的状态,和一个控制显隐的方法,以 antd 为例:

import { Modal, Button } from 'antd';class App extends React.Component {state = { visible: false }showModal = () => {this.setState({visible: true,});}handleOk = (e) => {// 做点什么this.setState({visible: false,});}handleCancel = (e) => {this.setState({visible: false,});}render() {return (<div><Button onClick={this.showModal}>Open</Button><Modaltitle="Basic Modal"visible={this.state.visible}onOk={this.handleOk}onCancel={this.handleCancel}><p>Some contents...</p><p>Some contents...</p><p>Some contents...</p></Modal></div>);}
}

上面是最简单的Modal使用实例,但大家心中理想的使用方式是如下的:

  <div><Button>Open</Button><Modaltitle="Basic Modal"onOk={this.handleOk //做点什么}><p>Some contents...</p><p>Some contents...</p><p>Some contents...</p></Modal></div>

我只想写业务逻辑的 onOK,其他部分不都是弹窗的实现细节吗,为啥不能封装起来?

答案是可以的。下面就使用 render props 来写一个Pop组件,封装所有逻辑。希望的最终使用方式是:

<Pop>{({ Button, Modal }) => (<div><Button>Open</Button><Modal title="Simple" onOK={() => alert("everything is OK")}><p>Some contents...</p><p>Some contents...</p><p>Some contents...</p></Modal></div>)}</Pop>

大家可以先尝试自己写一下。我写的如下:

import { Modal, Button } from 'antd';class Pop extends React.Component {state = { on: false };toggle = () => this.setState({ on: !this.state.on });// 将antd 组件包裹上状态和方法MyButton = props => <Button {...props} onClick={this.toggle} />;MyModal = ({ onOK, ...rest }) => (<Modal{...rest}visible={this.state.on}onOk={() => {onOK && onOK();this.toggle();}}onCancel={this.toggle}/>);render() {return this.props.children({on: this.state.on,toggle: this.toggle,Button: this.MyButton,Modal: this.MyModal});}
}

完整的Demo

简单的说,render props 将如何render组件的事代理给了使用它的组件,但同时以参数的形式提供了需要重用的状态和方法给外部。实现UI的自定义和功能的重用。不过这个例子有点激进,不仅提供了状态和方法,还提供了带状态的组件作为参数。如果大家有不同意见,请务必留言,互相学习。

实例2: 一般的render props只封装 “state”。React官方文档上 Dan Abromov 给出了一个很好的例子:鼠标跟踪的功能。这个功能有很多应用场景,也很好实现:

class Mouse extends React.Component {state = { x: 0, y: 0 }handleMouseMove = e => this.setState({ x: e.clientX, y: e.clientY })render() {return (<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}><p>鼠标位于 ({this.state.x}, {this.state.y})</p></div>)}
}

但如何封装和重用这个功能呢?比如要写一个猫的图案跟着鼠标走。大家可以先试试。 答案如下:

// 封装
class Mouse extends React.Component {state = { x: 0, y: 0 }handleMouseMove = (e) => this.setState({ x: e.clientX, y: e.clientY })render() {return (<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>{this.props.children(this.state)}</div>)}
}
// 重用
const Cat = () => <Mouse>{({x,y}) => <img src="/cat.jpg" style={{ position: 'absolute', left: x, top: y }} />}<Mouse>

结语

如果太长没看的话,只有一句是我最想分享的:当你写项目时碰到需要重用的是功能不是UI时,试着用render props封装一个组件吧。当然 HOC 也是解决方法,不过关于 HOC vs render props 的讨论,下篇再写。

原文发布时间为:2018年06月25日
原文作者:FateRiddle
本文来源: 掘金 如需转载请联系原作者

React拾遗:Render Props及其使用场景相关推荐

  1. TypeScript + React 学习render props

    ###前言 一直想学学TypeScript,尝试尝试带类型的js怎么写,有啥优点.正好这两天有时间结合着react写写小例子. 搭建环境 不想折腾webpack来自己配ts+react的环境就用typ ...

  2. React高级特性之Render Props

    render prop是一个技术概念.它指的是使用值为function类型的prop来实现React component之间的代码共享. 如果一个组件有一个render属性,并且这个render属性的 ...

  3. React组件常用设计模式之Render Props

    自己在总结最近半年的React开发最佳实践时,提到了Render Props,想好好写写,但感觉篇幅又太长,所以就有了此文.愿你看完,能有所收获,如果有什么不足或错误之处还请指正.文中所提到的所有代码 ...

  4. React Render props

    首先打个广告,系列文章: 古老的React mixins HOC(高阶组件) render props React Hooks 下面进入正题: 什么是Render props A render pro ...

  5. React 中使用 render props

    React 中使用 render props 前言 正文 結語 前言 這篇也是紀錄了關於學習 react 的過程,起因是因為開始學習 hooks,但是發現好像有些坑比較重要但卻被我跳過了,像是 ren ...

  6. React - children props 与 render props

    React - children props 与 render props 一. children props 1. 函数组件形式 2. 类组件形式 二. render props 1. 函数组件形式 ...

  7. web前端高级React - React从入门到进阶之Render Props

    第二部分:React进阶 系列文章目录 第一章:React从入门到进阶之初识React 第一章:React从入门到进阶之JSX简介 第三章:React从入门到进阶之元素渲染 第四章:React从入门到 ...

  8. React Render Props 模式

    为什么80%的码农都做不了架构师?>>>    概述 Render Props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件 ...

  9. React高阶组件以及应用场景

    什么是高阶组件 在解释什么是高阶组件之前,可以先了解一下什么是 高阶函数,因为它们的概念非常相似,下面是 高阶函数 的定义: 如果一个函数 接受一个或多个函数作为参数或者返回一个函数 就可称之为 高阶 ...

最新文章

  1. 维基链锚定行业缺口,定位发展一体化DeFi,持续开发出各类产品应用,包括去中心化抵押借贷系统Wayki-CDP(含稳定币WUSD)、去中心化交易所Wayki-DEX、去中心化合成资产协议Wayki-X
  2. ROS学习(九):ROS URDF-link
  3. C# 发送电子邮件(含附件)用到的类 system.web.mail
  4. 【数据结构与算法】之深入解析“石子游戏IX”的求解思路与算法示例
  5. 一辈子的礼物56ay长沙论坛
  6. JavaScript 调用后台事件和方法
  7. g6-editor 使用
  8. java 经纬度度分秒转度_用java实现经纬度坐标度分秒与度批量转换
  9. IT行业,应届生找工作遇到“招转培”怎么办?
  10. 利用word2vec创建中文主题词典——以网络暴力关键词为例
  11. 2020年叉车司机证考试题库及叉车司机试题解析
  12. java poodle,SSL 3.0 POODLE攻击信息泄露漏洞(CVE-2014-3566)
  13. 易语言文本比较特征码
  14. Regionals 2015 Asia - Daejeon acmliveoj7233 - Polynomial
  15. 奥迪A6(C5)遥控器钥匙更换电池后无法使用的适配(对码)方法
  16. Android 10.0 Launcher3 单层app列表页排序功能实现
  17. 百度笔经面经(Java)
  18. 【计算智能】模糊控制(一)模糊集合及其基本运算
  19. BI 如何让SaaS产品具有 “安全感”和“敏锐感”(上)
  20. 读书:冯唐的《金线》

热门文章

  1. 全球及中国医用敷料市场销售前景与竞争格局研究报告2022版
  2. 谋定技术加工领域 中国-巴对话(国际)农民丰收节贸易会
  3. 谋定政策经信研究扶持-万祥军:对话李玉庭跨界电商重整
  4. 并发之AQS原理(一) 原理介绍简单使用
  5. 位运算,处理前台多选值
  6. geotrellis使用(三十)使用geotrellis读取PostGIS空间数据
  7. CI框架 守护进程nohup让PHP以常驻内存的形式执行订阅消息
  8. POJ 1091 跳蚤
  9. Maven构建Struts2框架的注意事项
  10. 浏览器加载和渲染html的顺序