web前端高级React - React从入门到进阶之Render Props

第二部分:React进阶

系列文章目录

第一章:React从入门到进阶之初识React
第一章:React从入门到进阶之JSX简介
第三章:React从入门到进阶之元素渲染
第四章:React从入门到进阶之JSX虚拟DOM渲染为真实DOM的原理和步骤
第五章:React从入门到进阶之组件化开发及Props属性传值
第六章:React从入门到进阶之state及组件的生命周期
第七章:React从入门到进阶之React事件处理
第八章:React从入门到进阶之React条件渲染
第九章:React从入门到进阶之React中的列表与key
第十章:React从入门到进阶之表单及受控组件和非受控组件
第十一章:React从入门到进阶之组件的状态提升
第十二章:React从入门到进阶之组件的组合使用
第十三章:React从入门到进阶之组件的组件的懒加载及上下文Context
第十四章:React从入门到进阶之Refs&DOM以及Refs转发
第十五章:React从入门到进阶之高阶组件
第十六章:React从入门到进阶之Render Props

Render Props术语

Render Props是指使用一个值为函数的 prop在 React 组件之间共享代码的一种简单技术。
比如:一个组件具有一个render属性,该属性的值为一个函数,而该函数返回的是一个React元素,并且在组件的内部获取到render属性对应的值(方法)并调用它同时将返回的结果直接渲染,从而代替在组件内部写一些业务逻辑

<DataProvider render={
    
    data=>{
    
    
	<h1>Hello {
    
    data.target}</h1>
}} />

使用render prop的库有React Router、Downshift 和Formik

为什么要使用Render Props

我们都知道:在react中组件是实现代码复用的最主要单元,但是如何在不同组件之间共享相同state状态或行为却不是一件很容易的事。
下面我们还是以一个案例开始:假如我们现在有一个跟踪鼠标位置的组件:MouseTracker
当光标在屏幕上移动时,在组件中就会显示鼠标当前位置的x,y坐标

class MouseTracker extends React.Component{
    
    
	constructor(props){
    
    
		super(props);
		this.state = {
    
    x:0, y:0}
	}
	handleMouseMove = event =>{
    
    
		this.setState({
    
    
			x: event.clientX,
			y: event.clientY
		});
	}
	render(){
    
    
		return (
			<div style={
    
    {
    
    height:"300px"}} onMouseMove={
    
    this.handleMouseMove}>
				<h1>鼠标在移动!</h1>
				<p>鼠标的当前为位置为:({
    
    `x:${
      
      this.state.x}`},{
    
    `y:${
      
      this.state.y}`})</p>
			</div>
		)
	}
}

现在有个问题,我们想在另外一个组件中复用这种行为,或者换个说法:有另外一个组件也想要知道鼠标的位置,那么我们应该来如何复用呢?我们能否封装这一行为,以便轻松的与其它组件共享它?
上面已经提到react中组件是代码复用的最基本单元,那么要想实现复用,我们先来尝试将上面的代码进行一下重构,把需要共享的状态和行为封装为一个Mouse组件以便共享

// <Mouse> 组件封装了我们需要的行为...
class Mouse extends React.Component{
    
    
	constructor(props){
    
    
		super(props);
		this.state = {
    
    x:0, y:0}
	}
	handleMouseMove = event =>{
    
    
		this.setState({
    
    
			x: event.clientX,
			y: event.clientY
		});
	}
	render(){
    
    
		return (
			<div style={
    
    {
    
    height:"300px"}} onMouseMove={
    
    this.handleMouseMove}>				
				<p>鼠标的当前为位置为:({
    
    `x:${
      
      this.state.x}`},{
    
    `y:${
      
      this.state.y}`})</p>
			</div>
		)
	}
}

class MouseTracker extends React.Component{
    
    
	render(){
    
    
		return(
			<>
				<h1>鼠标在移动!</h1>
				<Mouse />
			</>
		)
	}
}
  • 现在我们已经把所有关于监听鼠标移动事件和获取鼠标位置的操作封装到了Mouse组件中,但貌似还是无法实现真正的复用和状态共享
  • 假如现在有一个cat组件,它可以呈现一张在屏幕上追逐鼠标的猫的图片。然后我们也想让这个Cat组件获取并使用当前鼠标的位置坐标,从而让它知道图片应该显示在屏幕上的哪个位置
  • 当前情况下,或许我们能想到的最简单办法就是把Cat作为Mouse的子组件,然后通过prop将鼠标的x和y传递给Cat,这样Cat就成获取到当前鼠标的位置了。
import {
    
    catImg} from './cat.jpg'
class Cat extends React.Component{
    
    
	render(){
    
    
		const {
    
    x, y} = this.props.mouse;
		return (
			<img src={
    
    catImg} style={
    
    {
    
    position:'absolute', left:x, top:y}} />
		)
	}
}
//我们将上面封装的Mouse组件名称改为MouseWithCat,因为这里面要用到Cat组件
class MouseWithCat extends React.Component{
    
    
	constructor(props){
    
    
		super(props);
		this.state = {
    
    x:0, y:0}
	}
	handleMouseMove = event =>{
    
    
		this.setState({
    
    
			x: event.clientX,
			y: event.clientY
		});
	}
	render(){
    
    
		return (
			<div style={
    
    {
    
    height:"300px"}} onMouseMove={
    
    this.handleMouseMove}>				
				//这里换成了Cat组件并将state通过mouse prop传递给Cat
				<Cat mouse={
    
    this.state}/>
			</div>
		)
	}
}

class MouseTracker extends React.Component{
    
    
	render(){
    
    
		return(
			<>
				<h1>猫在追老鼠!</h1>
				<MouseWithCat />
			</>
		)
	}
} 

现在我们通过组件嵌套及属性props传值的方式实现了我们的需求。但问题又来了:这种方式仅适用于特定的用例,而不是通用的,这并没有达到我们想要的目的。
比如现在又来了一只狗,它也想管管闲事,追一下Mouse并且它还不想跟猫一起。那么按照我们现有的逻辑,我们是不是又得重新封装一个新的组件MouseWithDog了呢?这显然不是我们想要的,这个时候 render prop就应运而生了。

Render Prop 的使用

  • 文章的开始已经提到:render prop就是利用一个以函数作为值的prop来实现组件间的状态共享。
  • 那么我们可以提供一个带有函数prop的Mouse组件;这种模式的优点就在于:它可以动态的决定需要渲染哪些内容,而不是将Cat或Dog硬编码到Mouse组件里面。
  • 下面我们就用render prop来实现一下我们的案例:
import {
    
    catImg} from './cat.jpg'
class Cat extends React.Component{
    
    
	render(){
    
    
		const {
    
    x, y} = this.props.mouse;
		return (
			<img src={
    
    catImg} style={
    
    {
    
    position:'absolute', left:x, top:y}} />
		)
	}
}

class Mouse extends React.Component{
    
    
	constructor(props){
    
    
		super(props);
		this.state = {
    
    x:0, y:0}
	}
	handleMouseMove = event =>{
    
    
		this.setState({
    
    
			x: event.clientX,
			y: event.clientY
		});
	}
	render(){
    
    
		return (
			<div style={
    
    {
    
    height:"300px"}} onMouseMove={
    
    this.handleMouseMove}>				
				//注意: 这里不再是直接渲染Cat组件,而是调用props属性中的render方法
				//该render方法是通过props属性传递过来的
				// 这也正是render prop的优势所在,它可以在不改变组件内部渲染逻辑的情况下实现状态共享和组件复用
				{
    
    this.props.render(this.state)}
			</div>
		)
	}
}

class MouseTracker extends React.Component{
    
    
	render(){
    
    
		return(
			<>
				<h1>猫在追老鼠!</h1>
				//这里我们给Mouse组件传递一个render属性,而它的值是一个函数
				//在函数的内部可以返回React 元素或者是我们自己封装的React组件
				<Mouse render={
    
    mouse =>{
    
    
					<Cat mouse={
    
    mouse} />
				}} />
			</>
		)
	}
} 
  • 现在,我们提供了一个render方法,让Mouse能够动态的决定该渲染什么内容,而不是每次都要克隆一份Mouse然后通过硬编
    码来解决特定用例的问题。
  • 更具体的说:render prop 是一个用于告知组件需要渲染什么内容的函数prop
  • 这项技术使我们共享行为非常容易,要获取这个行为,只需要渲染一个带有render prop的Mouse组件即可
  • 另外:利用render prop还能做一些更有趣的事情,上一章中我们将了高阶组件(HOC),我们可以利用带有render prop的常规组件来实现大多数高阶组件(HOC)。比如上面的例子我们还可以利用高阶组件(HOC)来实现
function withMouse(Component){
    
    
	return class extends React.Component{
    
    
		render(){
    
    
			return (
				<Mouse render={
    
    mouse=>{
    
    
					<Component {
    
    ...this.props} mouse={
    
    mouse} />
				}} />
			)
		}
	}
}

使用 Props 而非 render

  • 什么意思呢?就是说我们在使用prop传递方法是不一定非要命名为render,我们可以使用任何我们想要的名字都可以。
  • render prop是因为使用了props传值并且实现了元素的渲染这种模式,所以才被称为:render prop
  • 事实上:任何被用于告知组件需要渲染什么内容的函数prop在技术上都可以被称为“render prop”

尽管前面的例子都是使用render,其实我们也可以直接简单的使用children prop:

<Mouse children={
    
    mouse => (
  <p>鼠标的位置是 {
    
    mouse.x}{
    
    mouse.y}</p>
)}/>

另外:prop属性会默认自带一个children属性,所以:children prop并不真正需要添加到JSX元素的attributes列表中,相反,我们可以直接放置到元素的内部,然后通过props.children同样也是可以获取到的

<Mouse>
  {
    
    mouse => (
    <p>鼠标的位置是 {
    
    mouse.x}{
    
    mouse.y}</p>
  )}
</Mouse>

使用render prop的注意事项

将 Render Props 与 React.PureComponent 一起使用时要小心
如果在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。
例如,继续我们之前使用的 < Mouse> 组件,如果 Mouse 继承自 React.PureComponent 而不是 React.Component,我们的例子看起来就像这样:

class Mouse extends React.PureComponent {
    
    
  // 与上面相同的代码......
}

class MouseTracker extends React.Component {
    
    
  render() {
    
    
    return (
      <div>
        <h1>Move the mouse around!</h1>

        {
    
    /*
          这是不好的!
          每个渲染的 `render` prop的值将会是不同的。
        */}
        <Mouse render={
    
    mouse => (
          <Cat mouse={
    
    mouse} />
        )}/>
      </div>
    );
  }
}

在这样例子中,每次 < MouseTracker> 渲染,它会生成一个新的函数作为 < Mouse render> 的 prop,因而在同时也抵消了继承自 React.PureComponent 的 < Mouse> 组件的效果!
为了绕过这一问题,我们可以定义一个 prop 作为实例方法,类似这样:

class MouseTracker extends React.Component {
    
    
  // 定义为实例方法,`this.renderTheCat`始终
  // 当我们在渲染中使用它时,它指的是相同的函数
  renderTheCat(mouse) {
    
    
    return <Cat mouse={
    
    mouse} />;
  }

  render() {
    
    
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={
    
    this.renderTheCat} />
      </div>
    );
  }
}

以上就是render prop相关的介绍了。
下一章节我们将学习React家族中的使用PropTypes类型检查

猜你喜欢

转载自blog.csdn.net/lixiaosenlin/article/details/113268568