首页 >> js开发 >> js通过实例了解Render Props回调地狱解决方案js大全
js通过实例了解Render Props回调地狱解决方案js大全
发布时间: 2021年1月13日 | 浏览:
| 分类:js开发
简而言之,只要一个组件中某个属性的值是函数,那么就可以说该组件使用了 Render Props 这种技术。听起来好像就那么回事儿,那到底 Render Props 有哪些应用场景呢,咱们还是从简单的例子讲起,假如咱们要实现一个展示个人信息的组件,一开始可能会这么实现:
const PersonInfo = props => (
);
// 调用
const PersonInfo = props => (
);
// 调用
如果,想要在 PersonInfo 组件上还需要一个年龄呢,咱们会这么实现:
const PersonInfo = props => (
);
// 调用
const PersonInfo = props => (
);
// 调用
然后如果还要加上链接呢,又要在 PersonInfo 组件的内部实现发送链接的逻辑,很明显这种方式违背了软件开发六大原则之一的 开闭原则,即每次修改都要到组件内部需修改。开闭原则:对修改关闭,对拓展开放。
那有什么方法可以避免这种方式的修改呢?那有什么方法可以避免这种方式的修改呢?在原生 js 中,如果咱们调用函数后,还要做些骚操作,咱们一般使用回调函数来处理这种情况。在 react 中咱们可以使用 Render Props,其实和回调一样:
const PersonInfo = props => {
return props.render(props);
}
// 使用
name='web前端' age = '18' link = 'link'
render = {(props) => {
}}
/>
const PersonInfo = props => {
return props.render(props);
}
// 使用
name='web前端' age = '18' link = 'link'
render = {(props) => {
}}
/>值得一提的是,并不是只有在 render 属性中传入函数才能叫 Render Props,实际上任何属性只要它的值是函数,都可称之为 Render Props,比如上面这个例子把 render 属性名改成 children 的话使用上其实更为简便:
const PersonInfo = props => {
return props.children(props);
};
{(props) => (
)}
const PersonInfo = props => {
return props.children(props);
};
{(props) => (
)}
import Mouse from 'Mouse';
function ShowMousePosition() {
return (
render = {
({ x, y }) =>
}
/>
)
}
import Mouse from 'Mouse';
function ShowMousePosition() {
return (
render = {
({ x, y }) =>
}
/>
)
}使用此模式时,迟早会遇到在多个 render prop 回调中嵌套组件的问题: render props 回调地狱。1. Render Props 的回调地狱
1. Render Props 的回调地狱假设各位需要检测并显示网站访问者所在的城市。首先,需要确定用户地理坐标的组件,像 ... } 这样的组件进行异步操作,使用 Geolocation API,然后调用Render prop 进行回调。。然后用获取的坐标用来近似确定用户的城市: ...} />,这个组件也叫Render prop。接着咱们将这些异步组件合并到组件中
function DetectCity() {
return (
render={({ lat, long }) => {
return (
lat={lat}
long={long}
render={city => {
if (city == null) {
return
}
return
}}
/>
);
}}
/>
);
}
// 在某处使用
function DetectCity() {
return (
render={({ lat, long }) => {
return (
lat={lat}
long={long}
render={city => {
if (city == null) {
return
}
return
}}
/>
);
}}
/>
);
}
// 在某处使用
可能已经发现了这个问题:Render Prop回调函数的嵌套。嵌套的回调函数越多,代码就越难理解。这是Render Prop回调地狱的问题。咱们换中更好的组件设计,以排除回调的嵌套问题。2. Class 方法
2. Class 方法为了将回调的嵌套转换为可读性更好的代码,咱们将回调重构为类的方法。
class DetectCity extends React.Component {
render() {
return ;
}
renderCoords = ({ lat, long }) => {
return;
}
renderCity = city => {
if (city == null) {
return
}
return
}
}
// 在某处使用
class DetectCity extends React.Component {
render() {
return ;
}
renderCoords = ({ lat, long }) => {
return;
}
renderCity = city => {
if (city == null) {
return
}
return
}
}
// 在某处使用
回调被提取到分开的方法renderCoords()和renderCity()中。这样的组件设计更容易理解,因为渲染逻辑封装在一个单独的方法中。如果需要嵌套,类的方式是垂直增加(通过添加新方法),而不是水平(通过相互嵌套函数),回调地狱问题消失。2.1 访问渲染方法内部的组件 props
2.1 访问渲染方法内部的组件 props
方法renderCoors()和renderCity()是使用箭头函法定义的,这样可以将 this 绑定到组件实例,所以可以在和组件中调用这些方法。有了this作为组件实例,就可以通过 prop 获取所需要的内容:
class DetectCityMessage extends React.Component {
render() {
return ;
}
renderCoords = ({ lat, long }) => {
return;
}
renderCity = city => {
// 看这
const { noCityMessage } = this.props;
if (city == null) {
return
}
return
}
}
class DetectCityMessage extends React.Component {
render() {
return ;
}
renderCoords = ({ lat, long }) => {
return;
}
renderCity = city => {
// 看这
const { noCityMessage } = this.props;
if (city == null) {
return
}
return
}
}
renderCity()中的this值指向组件实例。现在就很容易从this.props获取 noCityMessage 的值 。3. 函数组合方法
3. 函数组合方法如果咱们想要一个不涉及创建类的更轻松的方法,可以简单地使用函数组合。使用函数组合重构 DetectCity 组件:
function DetectCity() {
return ;
}
function renderCoords({ lat, long }) {
return;
}
function renderCity(city) {
if (city == null) {
return
}
return
}
// Somewhere
function DetectCity() {
return ;
}
function renderCoords({ lat, long }) {
return;
}
function renderCity(city) {
if (city == null) {
return
}
return
}
// Somewhere
现在,常规函数renderCoors()和renderCity()封装了渲染逻辑,而不是用方法创建类。如果需要嵌套,只需要再次添加新函数即可。代码垂直增长(通过添加新函数),而不是水平增长(通过嵌套),从而解决回调地狱问题。这种方法的另一个好处是可以单独测试渲染函数:renderCoords()和renderCity()。3.1 访问渲染函数内部组件的 prop
3.1 访问渲染函数内部组件的 prop
如果需要访问渲染函数中的 prop ,可以直接将渲染函数插入组件中
function DetectCityMessage(props) {
return (
render={renderCoords}
/>
);
function renderCoords({ lat, long }) {
return (
lat={lat}
long={long}
render={renderCity}
/>
);
}
function renderCity(city) {
const { noCityMessage } = props;
if (city == null) {
return
}
return
}
}
// Somewhere
function DetectCityMessage(props) {
return (
render={renderCoords}
/>
);
function renderCoords({ lat, long }) {
return (
lat={lat}
long={long}
render={renderCity}
/>
);
}
function renderCity(city) {
const { noCityMessage } = props;
if (city == null) {
return
}
return
}
}
// Somewhere
虽然这种结构有效,但我不太喜欢它,因为每次重新渲染时,都会创建renderCoords()和renderCity()的新函数实例。前面提到的类方法可能更适合使用。同时,这些方法不会在每次重新渲染时重新创建。4. 实用的方法
4. 实用的方法如果想要在如何处理render props回调方面具有更大的灵活性,那么使用React-adopt是一个不错的选择。使用 react-adopt 来重构 组件:
import { adopt } from 'react-adopt';
const Composed = adopt({
coords: ({ render }) => ,
city: ({ coords: { lat, long }, render }) => (
)
});
function DetectCity() {
return (
{ city => {
if (city == null) {
return
}
return
}}
);
}
import { adopt } from 'react-adopt';
const Composed = adopt({
coords: ({ render }) => ,
city: ({ coords: { lat, long }, render }) => (
)
});
function DetectCity() {
return (
{ city => {
if (city == null) {
return
}
return
}}
);
}
react-adopt需要一个特殊的映射器来描述异步操作的顺序。同时,库负责创建定制的渲染回调,以确保正确的异步执行顺序。你可能会注意到的,上面使用react-adopt 的示例比使用类组件或函数组合的方法需要的代码。那么,为什么还要使用“react-adopt”呢?不幸的是,如果需要聚合多个render props的结果,那么类组件和函数组合方法并不合适。4.1 聚合多个渲染道具结果
4.1 聚合多个渲染道具结果
想象一下,当咱们渲染3个render prop回调的结果时(AsyncFetch1、AsyncFetch2、AsyncFetch3)
function MultipleFetchResult() {
return (
(
(
(
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)} />
)} />
)} />
);
}
function MultipleFetchResult() {
return (
(
(
(
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)} />
)} />
)} />
);
}
组件沉浸所有3个异步获取操作的结果,这是一个阔怕回调地狱的情况。如果尝试使用类组件或函数的组合方法,它会很麻烦。 回调地狱转变为参数绑定地狱:
class MultipleFetchResult extends React.Component {
render() {
return ;
}
renderResult1(result1) {
return (
render={this.renderResult2.bind(this, result1)}
/>
);
}
renderResult2(result1, result2) {
return (
render={this.renderResult3.bind(this, result1, result2)}
/>
);
}
renderResult3(result1, result2, result3) {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
);
}
}
// Somewhere
class MultipleFetchResult extends React.Component {
render() {
return ;
}
renderResult1(result1) {
return (
render={this.renderResult2.bind(this, result1)}
/>
);
}
renderResult2(result1, result2) {
return (
render={this.renderResult3.bind(this, result1, result2)}
/>
);
}
renderResult3(result1, result2, result3) {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
);
}
}
// Somewhere
咱们必须手动绑定render prop回调的结果,直到它们最终到达renderResult3()方法。如果不喜欢手工绑定,那么采用react-adopt可能会更好:
mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) => ,
result2: ({ render }) => ,
result3: ({ render }) =>
});
function MultipleFetchResult() {
return (
{({ result1, result2, result3 }) => (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)}
);
}
// Somewhere
mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) => ,
result2: ({ render }) => ,
result3: ({ render }) =>
});
function MultipleFetchResult() {
return (
{({ result1, result2, result3 }) => (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)}
);
}
// Somewhere
在函数({result1, result2, result3}) =>{…}提供给。因此,咱们不必手动绑定参数或嵌套回调。当然,react-adopt的代价是要学习额外的抽象,并略微增加应用程序的大小。以上就是本文的全部内容,希望对大家的学习有所帮助。
const PersonInfo = props => (
姓名:{props.name}
);
// 调用
const PersonInfo = props => (
姓名:{props.name}
);
// 调用
const PersonInfo = props => (
姓名:{props.name}
年龄:{props.age}[>
);
// 调用
const PersonInfo = props => (
姓名:{props.name}
年龄:{props.age}[>
);
// 调用
那有什么方法可以避免这种方式的修改呢?那有什么方法可以避免这种方式的修改呢?在原生 js 中,如果咱们调用函数后,还要做些骚操作,咱们一般使用回调函数来处理这种情况。在 react 中咱们可以使用 Render Props,其实和回调一样:
const PersonInfo = props => {
return props.render(props);
}
// 使用
render = {(props) => {
{props.name}
{props.age}
}}
/>
const PersonInfo = props => {
return props.render(props);
}
// 使用
render = {(props) => {
{props.name}
{props.age}
}}
/>值得一提的是,并不是只有在 render 属性中传入函数才能叫 Render Props,实际上任何属性只要它的值是函数,都可称之为 Render Props,比如上面这个例子把 render 属性名改成 children 的话使用上其实更为简便:
const PersonInfo = props => {
return props.children(props);
};
{(props) => (
{props.name}
{props.age}
)}
return props.children(props);
};
{(props) => (
{props.name}
{props.age}
)}
function ShowMousePosition() {
return (
render = {
({ x, y }) =>
Position: {x}px, {y}px
}
/>
)
}
import Mouse from 'Mouse';
function ShowMousePosition() {
return (
render = {
({ x, y }) =>
Position: {x}px, {y}px
}
/>
)
}使用此模式时,迟早会遇到在多个 render prop 回调中嵌套组件的问题: render props 回调地狱。1. Render Props 的回调地狱
1. Render Props 的回调地狱假设各位需要检测并显示网站访问者所在的城市。首先,需要确定用户地理坐标的组件,像
function DetectCity() {
return (
render={({ lat, long }) => {
return (
lat={lat}
long={long}
render={city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}}
/>
);
}}
/>
);
}
// 在某处使用
function DetectCity() {
return (
render={({ lat, long }) => {
return (
lat={lat}
long={long}
render={city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}}
/>
);
}}
/>
);
}
// 在某处使用
2. Class 方法为了将回调的嵌套转换为可读性更好的代码,咱们将回调重构为类的方法。
class DetectCity extends React.Component {
render() {
return
}
renderCoords = ({ lat, long }) => {
return
}
renderCity = city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}
}
// 在某处使用
class DetectCity extends React.Component {
render() {
return
}
renderCoords = ({ lat, long }) => {
return
}
renderCity = city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}
}
// 在某处使用
2.1 访问渲染方法内部的组件 props
方法renderCoors()和renderCity()是使用箭头函法定义的,这样可以将 this 绑定到组件实例,所以可以在
class DetectCityMessage extends React.Component {
render() {
return
}
renderCoords = ({ lat, long }) => {
return
}
renderCity = city => {
// 看这
const { noCityMessage } = this.props;
if (city == null) {
return
{noCityMessage}
;}
return
You might be in {city}.
;}
}
class DetectCityMessage extends React.Component {
render() {
return
}
renderCoords = ({ lat, long }) => {
return
}
renderCity = city => {
// 看这
const { noCityMessage } = this.props;
if (city == null) {
return
{noCityMessage}
;}
return
You might be in {city}.
;}
}
3. 函数组合方法如果咱们想要一个不涉及创建类的更轻松的方法,可以简单地使用函数组合。使用函数组合重构 DetectCity 组件:
function DetectCity() {
return
}
function renderCoords({ lat, long }) {
return
}
function renderCity(city) {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}
// Somewhere
function DetectCity() {
return
}
function renderCoords({ lat, long }) {
return
}
function renderCity(city) {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}
// Somewhere
3.1 访问渲染函数内部组件的 prop
如果需要访问渲染函数中的 prop ,可以直接将渲染函数插入组件中
function DetectCityMessage(props) {
return (
render={renderCoords}
/>
);
function renderCoords({ lat, long }) {
return (
lat={lat}
long={long}
render={renderCity}
/>
);
}
function renderCity(city) {
const { noCityMessage } = props;
if (city == null) {
return
{noCityMessage}
;}
return
You might be in {city}.
;}
}
// Somewhere
function DetectCityMessage(props) {
return (
render={renderCoords}
/>
);
function renderCoords({ lat, long }) {
return (
lat={lat}
long={long}
render={renderCity}
/>
);
}
function renderCity(city) {
const { noCityMessage } = props;
if (city == null) {
return
{noCityMessage}
;}
return
You might be in {city}.
;}
}
// Somewhere
4. 实用的方法如果想要在如何处理render props回调方面具有更大的灵活性,那么使用React-adopt是一个不错的选择。使用 react-adopt 来重构
import { adopt } from 'react-adopt';
const Composed = adopt({
coords: ({ render }) =>
city: ({ coords: { lat, long }, render }) => (
)
});
function DetectCity() {
return (
{ city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}}
);
}
import { adopt } from 'react-adopt';
const Composed = adopt({
coords: ({ render }) =>
city: ({ coords: { lat, long }, render }) => (
)
});
function DetectCity() {
return (
{ city => {
if (city == null) {
return
Unable to detect city.
;}
return
You might be in {city}.
;}}
);
}
4.1 聚合多个渲染道具结果
想象一下,当咱们渲染3个render prop回调的结果时(AsyncFetch1、AsyncFetch2、AsyncFetch3)
function MultipleFetchResult() {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)} />
)} />
)} />
);
}
function MultipleFetchResult() {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)} />
)} />
)} />
);
}
class MultipleFetchResult extends React.Component {
render() {
return
}
renderResult1(result1) {
return (
render={this.renderResult2.bind(this, result1)}
/>
);
}
renderResult2(result1, result2) {
return (
render={this.renderResult3.bind(this, result1, result2)}
/>
);
}
renderResult3(result1, result2, result3) {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
);
}
}
// Somewhere
class MultipleFetchResult extends React.Component {
render() {
return
}
renderResult1(result1) {
return (
render={this.renderResult2.bind(this, result1)}
/>
);
}
renderResult2(result1, result2) {
return (
render={this.renderResult3.bind(this, result1, result2)}
/>
);
}
renderResult3(result1, result2, result3) {
return (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
);
}
}
// Somewhere
mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) =>
result2: ({ render }) =>
result3: ({ render }) =>
});
function MultipleFetchResult() {
return (
{({ result1, result2, result3 }) => (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)}
);
}
// Somewhere
mport { adopt } from 'react-adopt';
const Composed = adopt({
result1: ({ render }) =>
result2: ({ render }) =>
result3: ({ render }) =>
});
function MultipleFetchResult() {
return (
{({ result1, result2, result3 }) => (
Fetch result 1: {result1}
Fetch result 2: {result2}
Fetch result 3: {result3}
)}
);
}
// Somewhere