React - Render Props

可组合组件

我们先回顾一下可组合组件。在 props 对象中有一个常用的 children 属性。通过它你可以将元素从上层传递到你的组件中,这些元素对你的组件来说是未知的,但是却为组件相互组合提供了可能性。children 非常灵活,可以是字符串,组件,甚至他们的集合,就像写html一样:

const A= ({ children }) => (
  <div>
    <p> A logic</p>
    { children }
  </div>
)
const App = () => (
  <>
    <p> children 示例:</p>
    <A>
      组件B:<B />
      组件C:<C />
    </A>
  </>
);

上面的组件A就是一个可组合组件。

可共享的可组合组件

上面示例中 children 是最基本的使用方式,只是在组合层面,还没到达共享层面。如果 B C 组件要使用 A 组件的属性,即 A 组件要共享自己的属性给B C 使用。通过 通过callBack 实现 A > APP > B&C 这种传递显然太low。这里就有一个技巧:把 children 函数化 这样便可以通过传递实现数据共享。
我们来看一个实例:一个检测浏览器在线/离线状态的共享组件,很多组件都需要通过这个组件获取浏览器是否在线:

import React from "react";

// 共享组件
class BrowserIfOnline extends React.Component {
  state = {
    isOnline: window.navigator.onLine
  };
  onOffline = () => {
    this.setState({ isOnline: false });
  };
  onOnline = () => {
    this.setState({ isOnline: true });
  };
  componentDidMount() {
    // 事件监听
    window.addEventListener("offline", this.onOffline);
    window.addEventListener("online", this.onOnline);
  }
  componentWillUnmount() {
    window.removeEventListener("offline", this.onOffline);
    window.removeEventListener("online", this.onOnline);
  }

  render() {
    return (
      <>
        {this.state.isOnline ? (
          <div>网断已连接 ...</div>
        ) : (
          <div>网络已断开 ...</div>
        )}
        {this.props.children(this.state.isOnline)}
      </>
    );
  }
}
// 子组件
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;

// 通过 函数化的 children 使用共享组件
class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline>
        {isOnline => (
          <>
            <B isOnline={isOnline} />
            <C isOnline={isOnline} />
          </>
        )}
      </BrowserIfOnline>
    );
  }
}
export default A

Render props 技巧

render propsReact 的 一个高级特性。指一种在 React 组件之间使用一个值为函数的 prop 共享代码/逻辑的简单技术。是一个用于告知组件需要渲染什么内容的函数。

上述可共享的可组合组件的实现,使用的便是 children + render props 技巧。在官方文档中介绍的 render props 使用的是另一种方式:把一个函数作为render 属性传递,而非 children

// 共享组件 
// 通过props.render 渲染其他组件,并共享数据
class BrowserIfOnline extends React.Component {

  // ......
  // 这里省略其他逻辑 同上
  // ......

  render() {
    return (
      <>
        {this.state.isOnline ? (
          <div>网断已连接 ...</div>
        ) : (
          <div>网络已断开 ...</div>
        )}
        {this.props.render(this.state.isOnline)}
      </>
    );
  }
}
//通过`render props` 的 属性(render)方式使用共享组件
class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline
        render={isOnline => (
          <>
            <B isOnline={isOnline} />
            <C isOnline={isOnline} />
          </>
        )}
      />
    );
  }
}

render 属性是国际惯例,你也可以自定义,但最好不要。复杂场景下,你可能需要多个 render 函数。这个时候必须要重命名了,但依然建议加render前缀,比如renderOne renderTwo

render props 优势

很多时候,React Hoc 可替代 render props 来实现逻辑共享。比如上面的功能用React Hoc 实现如下:

import React from "react";
// 把BrowserIfOnline组件封装成一个高阶组件 withBrowserIfOnline
const withBrowserIfOnline = comps =>
  class BrowserIfOnline extends React.Component {
    state = {
      isOnline: window.navigator.onLine
    };
    onOffline = () => {
      this.setState({ isOnline: false });
    };
    onOnline = () => {
      this.setState({ isOnline: true });
    };
    componentDidMount() {
      // 事件监听
      window.addEventListener("offline", this.onOffline);
      window.addEventListener("online", this.onOnline);
    }
    componentWillUnmount() {
      window.removeEventListener("offline", this.onOffline);
      window.removeEventListener("online", this.onOnline);
    }

    render() {
      return (
        <>
          {this.state.isOnline ? (
            <div>网断已连接 ...</div>
          ) : (
            <div>网络已断开 ...</div>
          )}
          {comps.map((Comp) => <Comp isOnline={this.state.isOnline}/>)}
        </>
      );
    }
  }
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;

const D = withBrowserIfOnline([B, C]);
export default class A extends React.Component {
  render() {
    return (
      <D/>
    );
  }
}

我们使用 render props 的优点主要体现在它的灵活性。比如上述示例,我们是可以在组件 B C 前后增加一些零碎的代码逻辑,而React Hoc就没那么便利。

class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline
        render={isOnline => (
          <>
            <p>组件B:</p>
            <B isOnline={isOnline} />
            <p>组件C:</p>
            <C isOnline={isOnline} />
            <p>其他信息</p>
          </>
        )}
      />
    );
  }
}

参考资料

官网 - Render Props
React Render Props