最近會陸續分享我在使用React的過程總結的一些比較高階的使用方法,這些方法能夠提高代碼的可複用性,也讓代碼看起來更加簡潔明晰。今天要講的是Render Props,不少人可能都知道react的這個特性,但在實際項目中不知道如何用起來。恰好這兩天的一個項目中用到了它,因此藉機分享一下。node
The term 「render prop」 refers to a technique for sharing code between React components using a prop whose value is a function.
這個概念聽上去有點拗口,咱們拆開了看它。react
好比官網給出的示例:segmentfault
<DataProvider render={data => ( <h1>Hello {data.target}</h1> )}/>
咱們給<DataProvider>
這個子組件傳遞了一個叫render
的prop,這個prop的值是一個函數,它返回了一個h1
元素。而後咱們能夠僞裝實現一下這個<DataProvider>
組件:數組
class DataProvider extends React.Component { state = { data: { target: 'World' } } render() { return this.props.render(this.state) } }
最終咱們的DataProvider
組件渲染的結果就是<h1>Hello World</h1>
。有同窗可能會有疑問,爲何要費這麼大週摺?直接把h1
元素寫在DataProvider
組件裏不也能夠嗎?ide
這裏就要講到代碼的可複用性了,假以下次咱們但願DataProvider
組件渲染的結果就是<span>Hello World</span>
呢?難道又去修改DataProvider
組件嗎?有了render props,咱們就能夠動態地決定DataProvider
組件內部要渲染的元素,同時這個元素還能夠使用到DataProvider
組件內部的數據。函數
下面講一個實際的項目案例,下圖中咱們有一個橫向滾動的ScrollView
組件,這個組件自己是個很普通的<div>
元素, 只不過樣式上加了overflow-x: scroll
因此能夠橫向滾動起來。產品同窗說滾動區域的下方要有進度點指示,從而告訴用戶總共有幾個產品,已經如今滾到第幾個產品了。this
明確了產品需求之後,咱們就開始來實現,首先看下初版:spa
class demo extends Component { state = { activeIndicator: 0, list: [] } onScroll = () => { const { list } = this.state; const container = findDOMNode(this.refs.container); ... const itemVisibleLengthInContainer = list.map((item, index) => { const node = findDOMNode(this.refs[`item-${index}`]); ... }); this.setState({ activeIndicator: active, }); }; render() { const { list, activeIndicator } = this.state; return ( <ScrollView ref="container" horizontal={true} onScroll={this.onScroll} > {list.map((item,i) => ( <ProductItem ref={`item-${i}`} data={item} /> ))} </ScrollView> <Indicator list={list} active={activeIndicator} /> ) } }
ok,需求咱們已經實現了。實現邏輯就是給ScrollView
組件添加一個onScroll
事件,每當滾動的時候,會先計算ScrollView
容器的位置信息,和每個ProductItem
的位置信息,算出如今哪一個ProductItem
在ScrollView
容器中所佔比例最高,從而得出如今應該高亮的activeIndicator
。rest
不過如今有個問題哦,給ScrollView
組件增長進度指示器這個功能,更像是ScrollView
組件應該支持的一個功能,而不是直接寫在業務代碼裏。因此咱們應該提供一個新組件ScrollViewWithIndicator
,讓它去處理進度指示器的問題,從而跟業務解耦。code
class ScrollViewWithIndicator extends Component { state = { activeIndicator: 0, } onScroll = () => { const { list } = this.props; const container = findDOMNode(this.refs.container); ... const itemVisibleLengthInContainer = list.map((item, index) => { const node = findDOMNode(this.refs[`item-${index}`]); ... }); this.setState({ activeIndicator: active, }); }; render() { const [{ list, children, ...restProps } , { activeIndicator }] = [this.props, this.state]; return ( <ScrollView ref="container" {...restProps} onScroll={this.onScroll} > {list.map((item,i) => ( <div ref={`item-${i}`}> {children(item} </div> ))} </ScrollView> <Indicator list={list} active={activeIndicator} /> ) } }
而後咱們的業務代碼就能夠簡化了:
class demo extends Component { state = { list: [] } render() { const { list } = this.state; return ( <ScrollViewWithIndicator horizontal={true} list={list} > {child => <ProductItem {...child} />} //(*) </ScrollViewWithIndicator> ) }
demo
組件,咱們一共給ScrollViewWithIndicator
組件傳遞了多少個props?答案是三個!分別是horizontal
, list
, children
,你們千萬別忘了this.props.children
也是一個props哦ScrollViewWithIndicator
組件傳遞一個叫children
的prop,同時這個prop是一個函數,返回了一個組件(元素),這就是咱們所說的render props啊list.map
這個數組的遍歷要寫在ScrollViewWithIndicator
組件內部,而不是業務組件demo
裏呢?由於咱們在onScroll
事件回調函數裏要計算每個商品item的位置,也就是要拿到商品item的ref
屬性,因此把數組的遍歷寫在ScrollViewWithIndicator
組件內部方便咱們顯性給每個商品item聲明ref
屬性改造完畢,下期再會。
React系列其餘文章