React 之 Render Props 的设计模式

很多人应该看到官方的高阶指南, 新加了 “render props” 的设计模式, 注意这只是一个设计模式, 不是新的 api, 而且有意思的是,2016年年底的时候, 我看到一个 sortable 的列表的 react 开源库代码时候, 就见识了这种写法,当时很不理解。因为 react 灵活性很大,code resusable 的方式有很多种,最开始的 mixin,cloneElement,props.children,HOC,可能会有细微的区别,这里我们讲讲 render props 的设计模式的特别的地方。(匆匆写完,如有错误请指出)

基本使用

官网的例子解释有一点啰嗦,我们来个精简版,我们需要一个显示鼠标位置的 MouseTracker:

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 (
      

Move the mouse around!

The current mouse position is ({this.state.x}, {this.state.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 (
      

The current mouse position is ({this.state.x}, {this.state.y})

); } } // 此时 mouseTracker 变成了这样 class MouseTracker extends React.Component { render() { return (

Move the mouse around!

); } }

完成:

//  p 标签的部分用 this.props.render() 代替
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 (
      
{this.props.render(state)}
); } } // 定义一个 UI 组件,也就是我们希望替换 p 标签的那一段 class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( ); } } // 改写后的 MouseTracker class MouseTracker extends React.Component { render() { return (

Move the mouse around!

( )}/>
); } }

额外提及

注意的是 render props 如上面所说是个设计模式, 所以没有必要一定要取名叫 render,官网举例说你可以用 children 作为 prop 的名字,这样你甚至都不用显示去声明它,如下:

class MouseTracker extends React.Component {
  render() {
    return (
      

Move the mouse around!

{mouse => (

The mouse position is {mouse.x}, {mouse.y}

)}
); } }

说实话,我觉得不举这个例子也许不会困惑,举了反倒。。。
这和普通的 children 有什么区别?先回忆下普通 children 的使用,如下:

class Header extends React.Component {
  render() {
    return 
{this.props.children}
} }

可以看到通常的 children 其实是个 react element(不需要函数调用),但 render props 模式中的 children 是个函数,所以如果你要用这种特殊技巧,最好在 propTypes 里面把 children 声明成一个函数:

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

另外一点就是,官网提到一个坑就是 pureComponent,也是一行注释能说清楚的:

class MouseTracker extends React.Component {
  render() {
    return (
      

Move the mouse around!

// 因为每次传进去的render是个新的匿名函数,如果 Mouse 是 PureComponent, 那么只会浅比较 // 每次新的匿名函数和上次永远不会相等,shouldComponentUpdate 永远为 false // 所以 MouseTracker 每次重新render, Cat 里面会重新执行 this.props.render() ( )}/>
); } }

如果要避免,那当然是把render方法用变量存一下:

class MouseTracker extends React.Component {
  renderTheCat(mouse) {
    return ;
  }

  render() {
    return (
      

Move the mouse around!

// render 函数不会发生变化哦
); } }

Props render 模式在 React Router 里面的使用

render props 这种设计模式在 react-router 4.0 里面使用到了, 之所以要写这个,我是觉得看明白这个,会在你理解 render props 时少走弯路。(贡献 render props 官方文档的和 react-router 的开发者就是同一个人,有兴趣可以查一下,有亮点~)

组件支持 component 和 render ,children 三种(https://reacttraining.com/react-router/web/api/Route) , render 和 children 都是一个函数,差别不大,我们比较下 component 和 render: component vs. children

说实话我之前在用的时候一直以为 render 既然是传一个函数,那么就是一个 stateless component 嘛, 而 component 传入就是传一个 stateful component 嘛,真是 too young too simple。首先我们看一个 demo:

const Home = () => 
home
// route1
home
}/> // route2

这样写是有差别,但是如果你把 render 里面的传参也指向 Home,route1 和 route2 有区别吗??

============= think about it ===========================

如果你答不上来,我把有关源码以及注释贴一下,你可能就明白了。

if (component)
  // We already know the differences:
  // React.createElement(component)
  // React.createElement(() => )
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null   

render 并不是一个 component,虽然这个函数看上去很像 stateless component,但是并不是走 React.createElement 组件逻辑,而是单纯的返回一个 element。所以第一个 route 的 render 的 element 的 type 是 Home,而第二个是 div。在这个例子里面看不出什么差别,但是如果我们要传入自己的 props,如果我们这么写:

 
       }
/>  // route3
 
      }
/> // route4

如之前提到的,每次 Route 所在的组件 render 一次,会形成一个新的匿名函数。所以针对 route3, component 传参每次都发生变化,type 也会发生变化。但是 route4 由于只是执行函数返回 Home,render 的 element 的 type 永远是 Home。而 React 虚拟节点比较根据之一就是 type 和之前的 type 是不是一样。所以 route3 里面的 Home 会随着父组件 render 经历 unmount 和 mount 生命周期,而 route4 里面的 Home 不会

有点绕? 请看代码:https://codesandbox.io/s/m72rolk4mx

并附上官方解释:


React 之 Render Props 的设计模式_第1张图片
官网文档

所以在有些库里面 render props 的入参很像一个 stateless 组件,比如 Formik, 但实际上走的是函数调用的逻辑。从我个人来讲,虽然 render props 灵活性很大,但是有一点隐性。以上就是 render props 的总结吧!

你可能感兴趣的:(React 之 Render Props 的设计模式)