React拾遗:Render Props及其使用场景
什么是 Render Props?
新的context api使用了render props:
第一次見到這個語法時,大多會很驚訝,因為日常代碼里props.children必然是字符串或者元素。但事實上props.children可以是函數,只要最終生成的render的返回值是dom元素就行。例如:
雖然沒有實際意義,但這即是一個?render props。當然render props最初的意思是:組件不自己定義render函數,而是通過一個名為render的props將外部定義的render函數傳入使用。?以上例來說,會是這樣:
因為現實中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組件,封裝所有邏輯。希望的最終使用方式是:
大家可以先嘗試自己寫一下。我寫的如下:
完整的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 的討論,下篇再寫。
原文作者:FateRiddle
本文來源:?掘金?如需轉載請聯系原作者 與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的React拾遗:Render Props及其使用场景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: selenium作业题
- 下一篇: AutoScaling 生命周期挂钩功能