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 (
      <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>
    );
  }
}

开始:

// 把和鼠标移动相关的东西抽出来
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}>
         <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}


// 此时 mouseTracker 变成了这样
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse />
      </div>
    );
  }
}

完成:

//  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 (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
         {this.props.render(state)}
      </div>
    );
  }
}

// 定义一个 UI 组件,也就是我们希望替换 p 标签的那一段
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 }} />
    );
  }
}

// 改写后的 MouseTracker
class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

额外提及

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

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse>
          {mouse => (
            <p>The mouse position is {mouse.x}, {mouse.y}</p>
        )}
        </Mouse>
      </div>
    );
  }
}

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

class Header extends React.Component {
  render() {
    return <div className='header'>{this.props.children}</div>
  }
}

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

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

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

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        //  因为每次传进去的render是个新的匿名函数,如果 Mouse 是 PureComponent, 那么只会浅比较
       //   每次新的匿名函数和上次永远不会相等,shouldComponentUpdate 永远为 false  
       //   所以 MouseTracker 每次重新render, Cat 里面会重新执行 this.props.render()
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

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

class MouseTracker extends React.Component {
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} /> // render 函数不会发生变化哦
      </div>
    );
  }
}

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

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

<Route /> 组件支持 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 = () => <div>home</div>
<Route component={Home}/>  // route1
<Route render={() => <div>home</div>}/> // route2

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

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

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

if (component)
  // We already know the differences:
  // React.createElement(component)
  // React.createElement(() => <component/>)
  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,如果我们这么写:

<Route component={props => 
      <Home name={this.state.name} {...props} /> }
/>  // route3
<Route render={props => 
      <Home  name={this.state.name} {...props} />}
/> // 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

并附上官方解释:

 

官网文档

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



作者:dpm
链接:https://www.jianshu.com/p/5081d46798a3
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值