通过实例了解Render Props回调地狱解决方案

作者:酷儿q 时间:2024-04-22 13:04:36 

简而言之,只要一个组件中某个属性的值是函数,那么就可以说该组件使用了 Render Props 这种技术。听起来好像就那么回事儿,那到底 Render Props 有哪些应用场景呢,咱们还是从简单的例子讲起,假如咱们要实现一个展示个人信息的组件,一开始可能会这么实现:


const PersonInfo = props => (
<div>
 <h1>姓名:{props.name}</h1>
</div>
);
// 调用
<PersonInfo name='web前端'/>

如果,想要在 PersonInfo 组件上还需要一个年龄呢,咱们会这么实现:


const PersonInfo = props => (
<div>
 <h1>姓名:{props.name}</h1>
 <p>年龄:{props.age}</[>
</div>
);

// 调用
<PersonInfo name='web前端' age='18'/>

然后如果还要加上链接呢,又要在 PersonInfo 组件的内部实现发送链接的逻辑,很明显这种方式违背了软件开发六大原则之一的 开闭原则,即每次修改都要到组件内部需修改。

开闭原则:对修改关闭,对拓展开放。

那有什么方法可以避免这种方式的修改呢?

在原生 js 中,如果咱们调用函数后,还要做些骚操作,咱们一般使用回调函数来处理这种情况。

在 react 中咱们可以使用 Render Props,其实和回调一样:


const PersonInfo = props => {
return props.render(props);
}
// 使用

<PersonInfo
name='web前端' age = '18' link = 'link'
render = {(props) => {
 <div>
  <h1>{props.name}</h1>
  <p>{props.age}</p>
  <a href="props.link" rel="external nofollow" ></a>
 </div>
}}
/>

值得一提的是,并不是只有在 render 属性中传入函数才能叫 Render Props,实际上任何属性只要它的值是函数,都可称之为 Render Props,比如上面这个例子把 render 属性名改成 children 的话使用上其实更为简便:


const PersonInfo = props => {
 return props.children(props);
};

<PersonInfo name='web前端' age = '18' link = 'link'>
{(props) => (
 <div>
   <h1>{props.name}</h1>
   <p>{props.age}</p>
   <a href={props.link}></a>
 </div>
)}
</PersonInfo

这样就可以直接在 PersonInfo 标签内写函数了,比起之前在 render 中更为直观。

所以,React 中的 Render Props 你可以把它理解成 js 中的回调函数。

React 组件的良好设计是可维护且易于更改代码的关键。

从这个意义上说,React 提供了许多设计技术,比如组合、Hooks、高阶组件、Render Props等等。

Render props 可以有效地以松散耦合的方式设计组件。它的本质在于使用一个特殊的prop(通常称为render),将渲染逻辑委托给父组件。


import Mouse from 'Mouse';
function ShowMousePosition() {
return (
 <Mouse
  render = {
   ({ x, y }) => <div>Position: {x}px, {y}px</div>
  }
 />
)
}

使用此模式时,迟早会遇到在多个 render prop 回调中嵌套组件的问题: render props 回调地狱。

1. Render Props 的回调地狱

假设各位需要检测并显示网站访问者所在的城市。

首先,需要确定用户地理坐标的组件,像<AsyncCoords render={coords => ... } 这样的组件进行异步操作,使用 Geolocation API,然后调用Render prop 进行回调。。

然后用获取的坐标用来近似确定用户的城市:<AsyncCity lat={lat} long={long} render={city => ...} />,这个组件也叫Render prop。

接着咱们将这些异步组件合并到<DetectCity>组件中


function DetectCity() {
return (
 <AsyncCoords
  render={({ lat, long }) => {
   return (
    <AsyncCity
     lat={lat}
     long={long}
     render={city => {
      if (city == null) {
       return <div>Unable to detect city.</div>;
      }
      return <div>You might be in {city}.</div>;
     }}
    />
   );
  }}
 />
);
}
// 在某处使用
<DetectCity />

可能已经发现了这个问题:Render Prop回调函数的嵌套。嵌套的回调函数越多,代码就越难理解。这是Render Prop回调地狱的问题。

咱们换中更好的组件设计,以排除回调的嵌套问题。

2. Class 方法

为了将回调的嵌套转换为可读性更好的代码,咱们将回调重构为类的方法。


class DetectCity extends React.Component {
render() {
 return <AsyncCoords render={this.renderCoords} />;
}

renderCoords = ({ lat, long }) => {
 return <AsyncCity lat={lat} long={long} render={this.renderCity}/>;
}

renderCity = city => {
 if (city == null) {
  return <div>Unable to detect city.</div>;
 }
 return <div>You might be in {city}.</div>;
}
}

// 在某处使用
<DetectCity />

回调被提取到分开的方法renderCoords()和renderCity()中。这样的组件设计更容易理解,因为渲染逻辑封装在一个单独的方法中。

如果需要更多嵌套,类的方式是垂直增加(通过添加新方法),而不是水平(通过相互嵌套函数),回调地狱问题消失。

2.1 访问渲染方法内部的组件 props

方法renderCoors()和renderCity()是使用箭头函法定义的,这样可以将 this 绑定到组件实例,所以可以在<AsyncCoords>和<AsyncCity>组件中调用这些方法。

有了this作为组件实例,就可以通过 prop 获取所需要的内容:


class DetectCityMessage extends React.Component {
render() {
 return <AsyncCoords render={this.renderCoords} />;
}

renderCoords = ({ lat, long }) => {
 return <AsyncCity lat={lat} long={long} render={this.renderCity}/>;
}

renderCity = city => {
 // 看这
 const { noCityMessage } = this.props;
 if (city == null) {
  return <div>{noCityMessage}</div>;
 }
 return <div>You might be in {city}.</div>;
}
}
<DetectCityMessage noCityMessage="Unable to detect city." />

renderCity()中的this值指向<DetectCityMessage>组件实例。现在就很容易从this.props获取 noCityMessage 的值 。

3. 函数组合方法

如果咱们想要一个不涉及创建类的更轻松的方法,可以简单地使用函数组合。

使用函数组合重构 DetectCity 组件:


function DetectCity() {
return <AsyncCoords render={renderCoords} />;
}

function renderCoords({ lat, long }) {
return <AsyncCity lat={lat} long={long} render={renderCity}/>;
}

function renderCity(city) {
if (city == null) {
 return <div>Unable to detect city.</div>;
}
return <div>You might be in {city}.</div>;
}

// Somewhere
<DetectCity />

现在,常规函数renderCoors()和renderCity()封装了渲染逻辑,而不是用方法创建类。

如果需要更多嵌套,只需要再次添加新函数即可。代码垂直增长(通过添加新函数),而不是水平增长(通过嵌套),从而解决回调地狱问题。

这种方法的另一个好处是可以单独测试渲染函数:renderCoords()和renderCity()。

3.1 访问渲染函数内部组件的 prop

如果需要访问渲染函数中的 prop ,可以直接将渲染函数插入组件中


function DetectCityMessage(props) {
return (
 <AsyncCoords
  render={renderCoords}
 />
);

function renderCoords({ lat, long }) {
 return (
  <AsyncCity
   lat={lat}
   long={long}
   render={renderCity}
  />
 );
}

function renderCity(city) {
 const { noCityMessage } = props;
 if (city == null) {
  return <div>{noCityMessage}</div>;
 }
 return <div>You might be in {city}.</div>;
}
}

// Somewhere
<DetectCityMessage noCityMessage="Unknown city." />

虽然这种结构有效,但我不太喜欢它,因为每次<DetectCityMessage>重新渲染时,都会创建renderCoords()和renderCity()的新函数实例。

前面提到的类方法可能更适合使用。同时,这些方法不会在每次重新渲染时重新创建。

4. 实用的方法

如果想要在如何处理render props回调方面具有更大的灵活性,那么使用React-adopt是一个不错的选择。

使用 react-adopt 来重构 <DetectCity> 组件:


import { adopt } from 'react-adopt';

const Composed = adopt({
coords: ({ render }) => <AsyncCoords render={render} />,
city: ({ coords: { lat, long }, render }) => (
 <AsyncCity lat={lat} long={long} render={render} />
)
});

function DetectCity() {
return (
 <Composed>
  { city => {
   if (city == null) {
    return <div>Unable to detect city.</div>;
   }
   return <div>You might be in {city}.</div>;
  }}
 </Composed>
);
}
<DetectCity />

react-adopt需要一个特殊的映射器来描述异步操作的顺序。同时,库负责创建定制的渲染回调,以确保正确的异步执行顺序。

你可能会注意到的,上面使用react-adopt 的示例比使用类组件或函数组合的方法需要更多的代码。那么,为什么还要使用“react-adopt”呢?

不幸的是,如果需要聚合多个render props的结果,那么类组件和函数组合方法并不合适。

4.1 聚合多个渲染道具结果

想象一下,当咱们渲染3个render prop回调的结果时(AsyncFetch1、AsyncFetch2、AsyncFetch3)


function MultipleFetchResult() {
return (
 <AsyncFetch1 render={result1 => (
  <AsyncFetch2 render={result2 => (
   <AsyncFetch3 render={result3 => (
    <span>
     Fetch result 1: {result1}
     Fetch result 2: {result2}
     Fetch result 3: {result3}
    </span>
   )} />
  )} />
 )} />
);
}
<MultipleFetchResult />

<MultipleFetchResult>组件沉浸所有3个异步获取操作的结果,这是一个阔怕回调地狱的情况。

如果尝试使用类组件或函数的组合方法,它会很麻烦。 回调地狱转变为参数绑定地狱:


class MultipleFetchResult extends React.Component {
render() {
 return <AsyncFetch1 render={this.renderResult1} />;
}

renderResult1(result1) {
 return (
  <AsyncFetch2
   render={this.renderResult2.bind(this, result1)}
  />
 );
}

renderResult2(result1, result2) {
 return (
  <AsyncFetch2
   render={this.renderResult3.bind(this, result1, result2)}
  />
 );
}

renderResult3(result1, result2, result3) {
 return (
  <span>
   Fetch result 1: {result1}
   Fetch result 2: {result2}
   Fetch result 3: {result3}
  </span>
 );
}
}
// Somewhere
<MultipleFetchResult />

咱们必须手动绑定render prop回调的结果,直到它们最终到达renderResult3()方法。

如果不喜欢手工绑定,那么采用react-adopt可能会更好:


mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) => <AsyncFetch1 render={render} />,
result2: ({ render }) => <AsyncFetch2 render={render} />,
result3: ({ render }) => <AsyncFetch3 render={render} />
});
function MultipleFetchResult() {
return (
 <Composed>
  {({ result1, result2, result3 }) => (
   <span>
    Fetch result 1: {result1}
    Fetch result 2: {result2}
    Fetch result 3: {result3}
   </span>
  )}
 </Composed>
);
}

// Somewhere
<MultipleFetchResult />

在函数({result1, result2, result3}) =>{…}提供给<Composed>。因此,咱们不必手动绑定参数或嵌套回调。

当然,react-adopt的代价是要学习额外的抽象,并略微增加应用程序的大小。

来源:https://www.cnblogs.com/Qooo/p/13859648.html

标签:Render,Props,回调
0
投稿

猜你喜欢

  • MySQL Order By索引优化方法

    2024-01-18 10:34:38
  • Python漏洞验证程序Poc利用入门到实战编写

    2022-05-08 00:00:31
  • 详解pandas DataFrame的查询方法(loc,iloc,at,iat,ix的用法和区别)

    2022-01-24 04:44:33
  • 微信小程序如何修改本地缓存key中单个数据的详解

    2024-05-09 10:34:57
  • python读取html中指定元素生成excle文件示例

    2021-04-08 19:51:11
  • python函数与方法的区别总结

    2021-08-21 00:07:51
  • Pandas DataFrame中的tuple元素遍历的实现

    2023-12-21 09:47:00
  • 详解如何在vue项目中使用layui框架及采坑

    2024-04-09 10:58:35
  • pandas按行按列遍历Dataframe的几种方式

    2023-07-04 15:36:05
  • pytorch快速搭建神经网络_Sequential操作

    2023-01-06 01:47:44
  • python 删除excel表格重复行,数据预处理操作

    2023-04-19 06:10:33
  • python使用百度或高德地图获取地理位置并转换

    2021-10-26 16:28:55
  • python中enumerate的用法实例解析

    2023-03-10 19:11:18
  • 在 Python 应用中使用 MongoDB的方法

    2023-09-13 21:53:11
  • ionic在开发ios系统微信时键盘挡住输入框的解决方法(键盘弹出问题)

    2024-05-02 16:18:12
  • python定时按日期备份MySQL数据并压缩

    2024-01-22 11:39:22
  • Vue 服务端渲染SSR示例详解

    2024-05-28 15:50:39
  • Python之dict(或对象)与json之间的互相转化实例

    2023-05-14 04:26:00
  • 学习Django知识点分享

    2021-07-18 03:42:00
  • asp动态页面生成html页面

    2008-10-24 09:03:00
  • asp之家 网络编程 m.aspxhome.com