自己在总结最近半年的React开发最佳实践时,提到了Render Props,想好好写写,但感觉篇幅又太长,所以就有了此文。愿你看完,能有所收获,如果有什么不足或错误之处还请指正。文中所提到的所有代码都可以在示例项目中找到,并npm i,npm start跑起来:
Github:示例项目

react官方文档中是这样描述Render Props的:术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 在 React 组件间共享代码的简单技术。带有render prop 的组件带有一个返回一个React元素的函数并调用该函数而不是实现自己的渲染逻辑,并配上了这样的示例:

<DataProvider render={data => (<h1>Hello {data.target}</h1>
)}/>// DataProvider 内部的渲染逻辑类似于下面这样
<div><p>这是一行伪代码</p>{this.props.render(data)}
</div>

看到这里,有可能你已经恍然大悟,原来所谓的Render Props设计模式不过如此,就是可以用react组件的render属性实现动态化的渲染逻辑。首先需要澄清两点:

  • 不是利用react组件的render属性,像我们class组件拥有的render函数,而是给他自定义一个render函数,属性名可以叫child,也可以叫what,可以是除react固有属性(key,className)以外的任何名字,比如用一个generate属性传递渲染逻辑,然后渲染this.props.generate(data);

    <DataProviderrender={data => (<h1>Hello {data.target}</h1>)}
    />
  • 上面这种把渲染逻辑写在prop属性中,如果子组件渲染代码多时,看着机会让人感觉有点凌乱,所以更多的时候我们是利用react组件固有的children属性来实现这种设计模式,所以render props更多的时候被称之为使用children prop。
 <DataProvider>{data => (<h1>Hello {data.target}</h1>)}</DataProvider>

Render Props设计模式与我们平常使用的通用组件(传递不同属性,渲染不同结果)相比,后者只是常规的React组件编写方式,用于同一个组件在不同的组件下调用,方便重用,拥有相同的渲染逻辑,更多用于展示型组件,而前者与高阶组件(Hoc)一样,是React组件的一种设计模式,用于方法的公用,渲染逻辑由调用者决定,更多用于容器型组件,当然强调点还是方法的重用

初识render props

在公司刚用react不久,我自己封装了一款组件,是在antd的AutoComplete组件上进行封装的,在我的一篇文章《antd组件使用进阶及踩过的坑》提到过,动态效果如下:

而在具体实现里我有这样的一段代码:

// 搜索过程代码处理
this.setState({ loading: true, options: null });
// 获取数据,并格式化数据
fetchData(params).then((list) => {let options;if (isEmpty(list)) {options = [DefaultOption];} else {// **用户自定义数据格式转换;**options = format(list).map(({ label, value }, key) => (<Option key={key} value={String(value)}>{label}</Option>));}!options.length && options.push(DefaultOption);this.setState({ options, loading: false, seachRes: list });
});
// render实现部分代码:<AutoCompleteautoFocusclassName="certain-category-search"dropdownClassName="certain-category-search-dropdown"dropdownMatchSelectWidthsize={size}onBlur={this.handleCloseSearch}onSearch={this.handleChange}onSelect={this.handleSelect}style={{ width: '100%' }}optionLabelProp="value">{loading ?[<Option key="loading" disabled><Spin spinning={loading} style={{ paddingLeft: '45%', textAlign: 'center' }} /></Option>] : options}</AutoComplete>// 调用代码const originProps = {searchKey: 'keyword',fetchData: mockFetch,format: datas => datas.map(({ id, name }, index) => ({label: `${name}(${id})`,value: name,key: index}))};<OriginSearch {...originProps} />

当时自己对render props并不了解,现在分析一下代码,会发现格式化数据的format属性与render Props有一点像,只不过他调用的位置有点曲折(在远程搜索函数中,根据搜索后的数据执行了渲染逻辑,然后更新到state中,在render函数中再从state中取出来渲染),从性能上来讲也多了一丁点的消耗。这个format函数已经实现了我们想要的,但是为了对比,用children prop来实现了一下:

    // 设置loading状态,清空optionthis.setState({ loading: true, options: null });// 获取数据fetchData(params).then((list) => {if (fetchId !== this.lastFethId) { // 异步程序,保证回调是按顺序进行return;}this.setState({ loading: false, seachRes: list });});
// render实现部分代码: <AutoCompleteautoFocusclassName="certain-category-search"dropdownClassName="certain-category-search-dropdown"dropdownMatchSelectWidthsize={size}onBlur={this.handleCloseSearch}onSearch={this.handleChange}onSelect={this.handleSelect}style={{ width: '100%' }}optionLabelProp="value">{loading ?[<Option key="loading" disabled><Spin spinning={loading} style={{ paddingLeft: '45%', textAlign: 'center' }} /></Option>] : this.props.children(seachRes)}</AutoComplete>// 调用代码const originProps = {searchKey: 'keyword',fetchData: mockFetch };<OriginSearchWithRenderProp {...originProps}>{(datas) => datas.map(({id, name}, index) => (<Option key={index}>{`${name}(${id})`}</Option>))}</OriginSearchWithRenderProp>

可以看到,当我用children prop来重写这个组件时也是可以的,而且内部逻辑看起来似乎变得更简单。但是不是就完美了,还是值得推敲的,接着往下看。

children prop正确打开方式

上一节那一个示例的改写,从render props定义去看,以前的写法对于一个展示型的组件来说,其实更合适,调用者可以编写更少的逻辑,而改写后对调用者就显得有点麻烦,因为虽然调用者可以自己定义渲染逻辑,但是AutoComplete这个组件能接收的子组件类型很有限,对于我这个功能来说,Option是唯一可以接收的子组件类型,所以意义不大。而render props这种模式定义的组件更着重于方法的重用,以及渲染逻辑可以由调用者自定义。来看一看下面这一种需求:

这个需求是我司的一个优惠券运营定制需求,产品想实现各种动态(可增减,数目不定)规则的定义。这种表单是最让人头疼的,不过写表单本来就是一件很让人头疼的事,最近看了篇文章《再也不想写表单了》图文并茂,让我深有感触。我在上一篇文章《React文档,除了主要概念,高级指引其实更好用》总结了怎样用配置的写antd表单。
回到正题,这个需求大致来看,第一: 需要自定义表单,常用的表单输入,为一个下拉框或一个输入框什么的,这里都是一个字段有多个输入,或则有多个选择。第二: 每个表单项要实现动态增减。所以如果不用设计模式的话,可能这四个表单项,你需要写4个自定义的组件,何况我那个需求这种表单有8个。但如果仔细观察,可以发现,这4个表单有着相似的行为,就是动态增减,每个表单每一项数据结构相似,只是四个表单的渲染不同。所以这就是最适合render props这种设计模式来定义这个容器,被这个容器包裹的组件他们拥有一个增加和一个减少的方法,然后他们相似的数据结构大概是这样:

const datas = [{key: 0,value1: '',value2: '',}, {key: 1,value1: '',value2: '',}]  

基于以上的总结我们可以这样实现组件(看代码):

export default class DaynamicForm extends React.Component {constructor(props) {super(props);const { value = [{ key: 0 }] } = props;this.state = {rules: value.map((ele, key) => ({ ...ele, key })),};this.handlMinus = this.handlMinus.bind(this);this.handlAdd = this.handlAdd.bind(this);this.handleChange = this.handleChange.bind(this);}// 处理减项,逻辑删除handlMinus(index) {const { rules } = this.state;rules[index].deleteFlag = true;this.setState({rules: [...rules]});this.trigger(rules);}
// 处理增项handlAdd() {let { rules } = this.state;rules = rules.concat([{ value: undefined, key: rules.length }]);this.setState({ rules: [...rules] });this.trigger(rules);}// 处理表单值的变化handleChange(val, index, key) {const { rules } = this.state;rules[index][key] = val;this.setState({rules: [...rules]});this.trigger(rules);}// 触发外部订阅trigger(res) {const { onChange } = this.props;onChange && onChange(res.filter(e => !e.deleteFlag));}render() {const { children } = this.props;const { rules } = this.state;const actions = {handlAdd: this.handlAdd,handlMinus: this.handlMinus,handleChange: this.handleChange,};return (<div>{rules.filter(rule => !rule.deleteFlag).map(rule => (<div key={rule.key}>{children(rule, actions)}{rule.key === 0 ?<Buttonstyle={{ marginLeft: 10 }}onClick={this.handlAdd}type="primary"shape="circle"icon="plus"/> :<Buttonstyle={{ marginLeft: 10 }}type="primary"shape="circle"icon="minus"onClick={() => this.handlMinus(rule.key)}/>}</div>))}</div>);}
}

而调用的代码,以满减规则为例:

    <FormItem {...formItemLayout} label="满减规则"><DaynamicForm key="fullRules">{(rule, actions) => <span key={rule.key}><span>满</span><InputNumberstyle={{ margin: '0 5px', width: 100 }}value={rule.full}min={0.01}step={0.01}precision={2}placeholder=">0, 2位小数"onChange={e => actions.handleChange(e, rule.key, 'full')}/>元,减<InputNumberstyle={{ margin: '0 5px', width: 100 }}value={rule.reduction}min={0.01}step={0.01}precision={2}placeholder=">0, 2位小数"onChange={e => actions.handleChange(e, rule.key, 'reduction')}/>元</span>}</DaynamicForm></FormItem>

从上面的组件实现代码和调用代码可以看出,render props很好的实现了这一切,并很好的诠释了方法公用,渲染逻辑自定义的概念。详细代码可参见示例项目

推荐

render props这种设计模式,我在使用两种库时见过:react-motion与apollo-graphql。react-motion是一个react动画库, 常用于个性化动画的定义,比如实现一个平移动画,先看效果:

实现代码:

<Motion style={{x: spring(this.state.open ? 400 : 0)}}>{({x}) =>// children is a callback which should accept the current value of// `style`<div className="demo0"><div className="demo0-block" style={{WebkitTransform: `translate3d(${x}px, 0, 0)`,transform: `translate3d(${x}px, 0, 0)`,}} /></div>}
</Motion>

apollo-graphql同时兼容了高阶组件和render props的写法,render props模式调用时如下面所示:

  <Query query={GET_DOGS}>{({ loading, error, data }) => {if (loading) return "Loading...";if (error) return `Error! ${error.message}`;return (<select name="dog" onChange={onDogSelected}>{data.dogs.map(dog => (<option key={dog.id} value={dog.breed}>{dog.breed}</option>))}</select>);}}</Query>

以上,就是Render Props,希望你看完能在自己的开发中用起来,如果你发现本文有什么错误或不足之处,欢迎指正。

React组件常用设计模式之Render Props相关推荐

  1. React高级特性之Render Props

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

  2. React - children props 与 render props

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

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

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

  4. 使用react实现select_使用 Hooks 优化 React 组件

    奇技指南 本文内容主要是我之前分享的文字版,若想看重点的话可以看之前的Slide: https://ppt.baomitu.com/d/75fc979a 本文作者奇舞团前端开发工程师李喆明. 需求描述 ...

  5. react 显示隐藏组件的方法_10种React组件之间通信的方法

    来源 | https://segmentfault.com/a/1190000023585646 这两天被临时抽调到别的项目组去做一个小项目的迭代.这个项目前端是用React,只是个小型项目所以并没有 ...

  6. react 组件遍历】_从 Context 源码实现谈 React 性能优化

    (给前端大全加星标,提升前端技能) 转自:魔术师卡颂 学完这篇文章,你会收获: 了解Context的实现原理 源码层面掌握React组件的render时机,从而写出高性能的React组件 源码层面了解 ...

  7. React组件复用的方式

    React组件复用的方式 现前端的工程化越发重要,虽然使用Ctrl+C与Ctrl+V同样能够完成需求,但是一旦面临修改那就是一项庞大的任务,于是减少代码的拷贝,增加封装复用能力,实现可维护.可复用的代 ...

  8. 【前端框架】react框架 第四章 react组件进阶

    目录 1. 组件的props 特点 2. 组件通迅的三种方式 父组件传递数据给子组件 子组件传递数据组父组件 兄弟组件 3. Context Context使用步骤 4. props深入 props校 ...

  9. pyqt 获取 UI 中组件_你想知道的React组件设计模式这里都有(上)

    本文梳理了容器与展示组件.高阶组件.render props这三类React组件设计模式 往期回顾:HBaseCon Asia 2019 Track 3 概要回顾 随着 React 的发展,各种组件设 ...

最新文章

  1. awk命令中执行多条shell命令
  2. 蓝盾股份成立安全教育公司
  3. 步进电机加减速算法介绍和基于AVR446_Linear speed control of stepper motor的步进电机加减速实现
  4. php之工作积累 (一)
  5. python中的抽象含义_Python中下划线的5种含义你都知道吗?
  6. 电气接线+线号管正确方向=电工接线好习惯!你有吗?
  7. DHCP服务_学习笔记
  8. mysql stdistance_postgis的geography_columns和geometry_columns有什么区别
  9. JS判断日期是否在同一个星期内,和同一个月内
  10. mysql事务的ACID属性:
  11. TensorFlow可以“预装”数据集了,新功能Datasets出炉
  12. java循环隔行变色_c:foreach标签详解----(隔行换背景颜色的问题)
  13. OpenCascade一些常用的API
  14. php 删除前后空格,php怎么去除前面空格
  15. 偏微分方程数值解法pdf_数值模拟偏微分方程的三种方法:FDM、FEM及FVM
  16. 在线抓娃娃 html,萤石云开放平台提供在线抓娃娃解决方案
  17. 一年工作经验,两周的面试,拿到几个offer的面试经验总结
  18. [linux] Linux网络之TCP协议详解
  19. c语言写一个电脑程序,C语言实现电脑关机程序
  20. 性能测试指南 | 一些实用的排查命令(未完待续)

热门文章

  1. Android 手机卫士--自定义组合控件构件布局结构
  2. LVS Nginx HAProxy 优缺点
  3. Const *ptr ptr
  4. 元素序列几个常用排序算法:一
  5. Asterisk cli模块分析
  6. (转自PHPer)成长的选择
  7. java future用法_Java中的多线程知识点
  8. 一位资深程序员面试Python工程师的岗位心得和历程
  9. html 打印时隐藏,html 打印相关操作与实现详解
  10. Redis之单线程 Reactor 模型