最近会陆续分享我在使用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系列其余文章