广告位联系
返回顶部
分享到

React RenderProps模式超详细介绍

JavaScript 来源:互联网 作者:佚名 发布时间:2022-11-12 17:33:42 人浏览
摘要

render prop是一个技术概念。它指的是使用值为function类型的prop来实现React component之间的代码共享。 如果一个组件有一个render属性,并且这个render属性的值为一个返回React element的函数,并

render prop是一个技术概念。它指的是使用值为function类型的prop来实现React component之间的代码共享。

如果一个组件有一个render属性,并且这个render属性的值为一个返回React element的函数,并且在组件内部的渲染逻辑是通过调用这个函数来完成的。那么,我们就说这个组件使用了render props技术。

1

2

3

<DataProvider render={data => (

  <h1>Hello {data.target}</h1>

)}/>

不少类库都使用了这种技术,比如说:React Router和Downshift。

在这个文档里面,我们将会讨论为什么render props是如此有用,你该如何编写自己的render props组件。

正文

使用Render Props来完成关注点分离

在React中,组件是代码复用的基本单元(又来了,官方文档不断地在强调这个准则)。到目前为止,在React社区里面,关于共享state或者某些相似的行为(比如说,将一个组件封装进另一拥有相同state的组件)还没有一个明朗的方案。

举个例子,下面这个组件是用于在web应用中追踪鼠标的位置:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

class MouseTracker extends React.Component {

  constructor(props) {

    super(props);

    this.handleMouseMove = this.handleMouseMove.bind(this);

    this.state = { x: 0, y: 0 };

  }

  handleMouseMove(event) {

    this.setState({

      x: event.clientX,

      y: event.clientY

    });

  }

  render() {

    return (

      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        <h1>Move the mouse around!</h1>

        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>

      </div>

    );

  }

}

随着光标在屏幕上面移动,这个组件将会在文档的<p>标签里面显示当前光标在x,y轴上的坐标值。

那么问题来了: 我们该如何在别的组件复用这种行为(指的是监听mouseMove事件,获取光标的坐标值)呢?换句话说,如果别的组件也需要知道目前光标的坐标值,那我们能不能将这种行为封装好,然后在另外一个组件里面开箱即用呢?

因为,在React中,组件是代码复用的基本单元(again)。那好,我们一起来重构一下代码,把我们需要复用的行为封装到<Mouse>组件当中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

// The <Mouse> component encapsulates the behavior we need...

class Mouse extends React.Component {

  constructor(props) {

    super(props);

    this.handleMouseMove = this.handleMouseMove.bind(this);

    this.state = { x: 0, y: 0 };

  }

  handleMouseMove(event) {

    this.setState({

      x: event.clientX,

      y: event.clientY

    });

  }

  render() {

    return (

      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

 

        {/* ...but how do we render something other than a <p>? */}

        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>

      </div>

    );

  }

}

class MouseTracker extends React.Component {

  render() {

    return (

      <div>

        <h1>Move the mouse around!</h1>

        <Mouse />

      </div>

    );

  }

}

现在,<Mouse>组件看似把所有跟监听mousemove事件,保存光标的坐标值等相关的行为封装在一起了。实际上,它还不能达到真正的可复用。

假设,我们需要实现这么一个组件。它需要渲染出一只用图片表示的猫去追逐光标在屏幕上移动的视觉效果。我们可能会通过向<Cat>组件传递一个叫mouse(它的值为{{x,y}})的prop来获得当前光标所在位置。参考React实战视频讲解:进入学习

首先,我们会在<Mouse>组件的render方法里面插入这个<Cat>组件,像这样子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

class Cat extends React.Component {

  render() {

    const mouse = this.props.mouse;

    return (

      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />

    );

  }

}

class MouseWithCat extends React.Component {

  constructor(props) {

    super(props);

    this.handleMouseMove = this.handleMouseMove.bind(this);

    this.state = { x: 0, y: 0 };

  }

  handleMouseMove(event) {

    this.setState({

      x: event.clientX,

      y: event.clientY

    });

  }

  render() {

    return (

      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

 

        {/*          We could just swap out the <p> for a <Cat> here ... but then          we would need to create a separate <MouseWithSomethingElse>          component every time we need to use it, so <MouseWithCat>          isn't really reusable yet.        */}

        <Cat mouse={this.state} />

      </div>

    );

  }

}

class MouseTracker extends React.Component {

  render() {

    return (

      <div>

        <h1>Move the mouse around!</h1>

        <MouseWithCat />

      </div>

    );

  }

}

这种方式的实现可能对个别的场景有用,但是,我们还是没有达成通过封装让这种行为真正地复用的目标。在别的应用场景下,每一次当我们需要获取光标在屏幕上的坐标的时候,我们都需要重新创建一个组件(例如,一个跟<MouseWithCat>相似组件)来完成这个业务场景所对应的渲染任务。

这个时候,就轮到render props 出场啦:相比直接把<Cat>这个组件硬编码到<Mouse>组件当中,刻意地去改变<Mouse>组件的UI输出(也就是我们重新定义一个<MouseWithCat>组件的原因)。更好的做法是,我们可以给<Mouse>组件定义一个值为函数类型的prop,让这个prop自己来动态地决定要在Mouse组件的render方法要渲染东西。这个值为函数类型的prop就是我们所说的render prop了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

class Cat extends React.Component {

  render() {

    const mouse = this.props.mouse;

    return (

      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />

    );

  }

}

class Mouse extends React.Component {

  constructor(props) {

    super(props);

    this.handleMouseMove = this.handleMouseMove.bind(this);

    this.state = { x: 0, y: 0 };

  }

  handleMouseMove(event) {

    this.setState({

      x: event.clientX,

      y: event.clientY

    });

  }

  render() {

    return (

      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

 

        {/*          Instead of providing a static representation of what <Mouse> renders,          use the `render` prop to dynamically determine what to render.        */}

        {this.props.render(this.state)}

      </div>

    );

  }

}

class MouseTracker extends React.Component {

  render() {

    return (

      <div>

        <h1>Move the mouse around!</h1>

        <Mouse render={mouse => (

          <Cat mouse={mouse} />

        )}/>

      </div>

    );

  }

}

现在,相比每一次都要重复地将<Mouse>组件的代码复制一遍,然后将我们要渲染的东西硬编码到<Mouse>的render方法中去,我们采取了一个更省力的办法。那就是给Mouse新增了一个render属性,让这个属性来决定要在<Mouse>组件中渲染什么。

更加具体和直白地说,一个render prop(这里不是代指技术,而是组件属性) 就是一个值为函数类型的prop。通过这个函数,我们让挂载了这个prop的组件知道自己要去渲染什么。

这种技术使得我们之前想要共享的某些行为(的实现)变得非常之可移植(portable)。假如你想要得到这种行为,你只需要渲染一个带render属性的类<Mouse>组件到你的组件树当中就可以了。剩下的就让这个render prop来获取相关的数据(通过函数形参被实例化时得到。拿上述例子来说,就是(mouse)=> <Cat mouse={mouse}>的mouse),然后决定如何干预这个组件的渲染。

一个很有意思的,并值得我们注意的事情是,你完全可以通过一个带render属性的普通组件来实现大部分的HOC。举个例子,假如你在共享行为(监听mousemove事件,获得光标在屏幕上的坐标)时不想通过<Mouse>组件来完成,而是想通过高阶组件withMouse来完成的话,那么就可以很简单地通过创建一个带render prop的<Mouse>组件来达成:

1

2

3

4

5

6

7

8

9

10

11

12

13

// If you really want a HOC for some reason, you can easily

// create one using a regular component with a render prop!

function withMouse(Component) {

  return class extends React.Component {

    render() {

      return (

        <Mouse render={mouse => (

          <Component {...this.props} mouse={mouse} />

        )}/>

      );

    }

  }

}

可以这么说,render props(指技术)让HOC技术与其他技术(在这里,指它自己)的组合使用成为了可能。

render prop的prop名不一定叫render

如上面的标题,你要牢牢记住,这种技术虽然叫render props,但是prop属性的名称不一定非得叫“render”。实际上,只要组件上的某个属性值是函数类型的,并且这个函数通过自己的形参实例化时获取了这个组件的内部数据,参与到这个组件的UI渲染中去了,我们就说这个组件应用了render props这种技术。

在上面的例子当中,我们一直在使用“render”这个名称。实际上,我们也可以轻易地换成children这个名称!

1

2

3

<Mouse children={mouse => (

  <p>The mouse position is {mouse.x}, {mouse.y}</p>

)}/>

同时,我们也要记住,这个“children”prop不一定非得罗列在在JSX element的“属性”列表中。它实际上就是我们平时用JSX声明组件时的children,因此你也可以像以前一样把它放在组件的内部。

1

2

3

4

5

<Mouse>

  {mouse => (

    <p>The mouse position is {mouse.x}, {mouse.y}</p>

  )}

</Mouse>

在react-motion这个库的API中,你会看到这种写法的应用。

因为这种写法比较少见,所以假如你这么做了,为了让看你代码的人不产生疑惑的话,你可能需要在静态属性propTypes中显式地声明一下children的数据类型必须为函数。

1

2

3

Mouse.propTypes = {

  children: PropTypes.func.isRequired

};

注意点

当跟React.PureComponent结合使用时,要当心

如果你在组件的render方法里面创建了一个函数的话,然后把这个函数赋值给这个组件的prop的话,那么得到的结果很有可能是违背了你初衷的。怎么说呢?因为一旦你这么做了,React在作shallow prop comparison的时候,new props都会被判断为不等于old props的。现实是,这么做恰恰会导致在每一次render的调用的时候生成一个新的值给这个属性。

我们继续拿上面的<Mouse>组件作为例子。假如<Mouse>组件继承了React.PureComponent的话,我们的代码应该是像下面这样的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

class Mouse extends React.PureComponent {

  // Same implementation as above...

}

class MouseTracker extends React.Component {

  render() {

    return (

      <div>

        <h1>Move the mouse around!</h1>

 

        {/*          This is bad! The value of the `render` prop will          be different on each render.        */}

        <Mouse render={mouse => (

          <Cat mouse={mouse} />

        )}/>

      </div>

    );

  }

}

在上面的代码例子当中,每一次<MouseTracker>组件的render方法被调用的时候,它都会生成一个新的函数实例给<Mouse>组件,作为“render”属性的值。然而,我们之所以继承React.PureComponent,就是想减少<Mouse>组件被渲染的次数。如此一来,<Mouse>因为一个新的函数实例被判定为props已经发生改变了,于是乎进行了不必要的渲染。这与我们的让<Mouse>组件继承React.PureComponent的初衷是相违背的。

为了避开(To get around)这个问题,你可以把render prop的值赋值为<MouseTracker>组件实例的一个方法,这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class MouseTracker extends React.Component {

  // Defined as an instance method, `this.renderTheCat` always

  // refers to *same* function when we use it in render

  renderTheCat(mouse) {

    return <Cat mouse={mouse} />;

  }

  render() {

    return (

      <div>

        <h1>Move the mouse around!</h1>

        <Mouse render={this.renderTheCat} />

      </div>

    );

  }

}

在某些场景下,你可能无法把prop的值静态地赋值为组件实例的某个方法(例如,你需要覆盖组件的props值或者state值,又两者都要覆盖)。那么,在这种情况下,你只能老老实实地让<Mouse>组件去继承React.Component了。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/grooyo/article/details/127767359
相关文章
  • js中如何对url进行编码和解码
    js 对url进行编码和解码 三种编码和解码函数 encodeURI和 decodeURI 它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中
  • React RenderProps模式超详细介绍
    render prop是一个技术概念。它指的是使用值为function类型的prop来实现React component之间的代码共享。 如果一个组件有一个render属性,并且这个
  • Vue中ref、reactive、toRef、toRefs、$refs的基本用法总

    Vue中ref、reactive、toRef、toRefs、$refs的基本用法总
    一、ref reactive 1.1.为什么需要ref、reactive ??? setup函数中默认定义的变量并不是响应式的(即数据变了以后页面不会跟着变),如果想让变量变
  • Javascript如何实现对象扁平化实例介绍

    Javascript如何实现对象扁平化实例介绍
    数组扁平化相信大家已经耳熟能详了,在被面试官问到如何实现数组扁平化你就偷着乐吧,但是相信有不少大佬在面试一些国内顶尖的大厂
  • Immer功能最佳实践示例教程

    Immer功能最佳实践示例教程
    一、前言 Immer 是mobx的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体
  • Ant Design 的Bug修复示例
    我在工作中大量使用Ant Design,它为我省去了很多重复劳动,所以有空的时候,我也会为Ant Design做一些微小的贡献。 本文详细描述了我处理
  • AntDesignPro使用electron构建桌面应用示例
    注意事项声明 所有node包必须使用npm安装不可使用cnpm, 使用cnpm安装的node包会导致打包时间无限可能 具体区别查看使用npm和cnpm安装的包结构
  • vue el-switch初始值(默认值)不能正确显示状态问题

    vue el-switch初始值(默认值)不能正确显示状态问题
    el-switch 初始值(默认值)不能正确显示状态 先去检查一下接口返回格式 如果是字符串 不需要加冒号 1 active-value=1 inactive-value=2 如果是数字
  • 微信小程序跳转外部链接的实现方法

    微信小程序跳转外部链接的实现方法
    微信小程序跳转外部链接 在开发小程序过程中,我们可能会有这样的需求,在小程序中打开H5或者外部链接 实现方法如下: 1、配置业务域
  • 自己写一个uniapp全局弹窗(APP端)

    自己写一个uniapp全局弹窗(APP端)
    效果图: uniapp自带的提示框不符合我们的要求,需要自己写一个提示框,且全局通用。 解决思路: 使用 plus.nativeObj 来绘制窗口以及事件监听
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计