React高階用法之Render Props

最近會陸續分享我在使用React的過程總結的一些比較高階的使用方法,這些方法能夠提高代碼的可複用性,也讓代碼看起來更加簡潔明晰。今天要講的是Render Props,不少人可能都知道react的這個特性,但在實際項目中不知道如何用起來。恰好這兩天的一個項目中用到了它,因此藉機分享一下。node

什麼是Render Props

The term 「render prop」 refers to a technique for sharing code between React components using a prop whose value is a function.

這個概念聽上去有點拗口,咱們拆開了看它。react

  1. 首先它本質上是一個prop,是用來父子組件之間傳遞數據用的
  2. 其次這個prop傳遞的值是一個函數
  3. 最後它取名render props,是由於它一般是用來render(渲染)某個元素或組件

好比官網給出的示例: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

image

明確了產品需求之後,咱們就開始來實現,首先看下初版: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的位置信息,算出如今哪一個ProductItemScrollView容器中所佔比例最高,從而得出如今應該高亮的activeIndicatorrest

不過如今有個問題哦,給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>
        )
    }
  1. 仔細看業務代碼demo組件,咱們一共給ScrollViewWithIndicator組件傳遞了多少個props?答案是三個!分別是horizontal, list, children,你們千萬別忘了this.props.children也是一個props哦
  2. 再仔細看第(*)這句話,咱們給ScrollViewWithIndicator組件傳遞一個叫children的prop,同時這個prop是一個函數,返回了一個組件(元素),這就是咱們所說的render props啊
  3. 爲何list.map這個數組的遍歷要寫在ScrollViewWithIndicator組件內部,而不是業務組件demo裏呢?由於咱們在onScroll事件回調函數裏要計算每個商品item的位置,也就是要拿到商品item的ref屬性,因此把數組的遍歷寫在ScrollViewWithIndicator組件內部方便咱們顯性給每個商品item聲明ref屬性

改造完畢,下期再會。


React系列其餘文章

相關文章
相關標籤/搜索