简而言之,只要一个组件中某个属性的值是函数,那么就可以说该组件使用了 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”呢"htmlcode">
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的代价是要学习额外的抽象,并略微增加应用程序的大小。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]