基本使用

Higher order components usually take a component and optional arguments as input and return an enhanced component of the input component.

高阶组件是函数【组件】,参数为组件,返回值为新组件的函数,高阶组件是将组件转换为另一个组,形如:

  1. const EnhancedComponent = higherOrderComponent(WrappedComponent);

下面我们一步一步探究HOC,

  1. import React from "react";
  2. import "./style.css";
  3. function TodoList({ todos, isLoadingTodos }) {
  4. if (isLoadingTodos) {
  5. return (
  6. <div>
  7. <p>Loading todos ...</p>
  8. </div>
  9. );
  10. }
  11. if (!todos) {
  12. return null;
  13. }
  14. if (!todos.length) {
  15. return (
  16. <div>
  17. <p>You have no Todos.</p>
  18. </div>
  19. );
  20. }
  21. return (
  22. <div>
  23. <ul>
  24. {todos.map(todo => (
  25. <li>{todo.todo}</li>
  26. ))}
  27. </ul>
  28. </div>
  29. );
  30. }
  31. export default function App() {
  32. const todoList = [{ id: 1, todo: "react" }, { id: 2, todo: "webpack" }];
  33. return (
  34. <div>
  35. <TodoList isLoadingTodos={false} todos={todoList} />
  36. </div>
  37. );
  38. }

这是我们应用中经常遇到的,会有很多边界条件的判断,loading、空判断……
这些边界条件的判断会经常在其他的组件中使用,如何复用?
我们来写一个HOC的雏形

  1. function withTodosNull(Component) {
  2. return function (props) {
  3. ...
  4. }
  5. }

HOC实际上是一个函数,参数是一个组件,返回一个组件。
一般情况下HOC是以with开头来标明是HOC,但这不是强制的。

  1. function withTodosNull(Component) {
  2. return function (props) {
  3. return !props.todos
  4. ? null
  5. : <Component { ...props } />
  6. }
  7. }
  8. 或者箭头函数形式
  9. const withTodosNull = (Component) => (props) =>
  10. !props.todos
  11. ? null
  12. : <Component { ...props } />
  13. ******** 在应用中使用
  14. const withTodosNull = (Component) => (props) =>
  15. ...
  16. function TodoList({ todos }) {
  17. ...
  18. }
  19. const TodoListWithNull = withTodosNull(TodoList);
  20. function App(props) {
  21. return (
  22. <TodoListWithNull todos={props.todos} />
  23. );
  24. }

上面的HOC根据三元运算符来判断是否返回Component,这里的props是通过组件树传递到Component中
其实我们可以理解TodoListWithNull就是一个组件,接收了一个todos的props
同理其他情况的边界条件HOC:

  1. const withTodosEmpty = (Component) => (props) =>
  2. !props.todos.length
  3. ? <div><p>You have no Todos.</p></div>
  4. : <Component { ...props } />
  5. const withLoadingIndicator = (Component) => ({ isLoadingTodos, ...others }) =>
  6. isLoadingTodos
  7. ? <div><p>Loading todos ...</p></div>
  8. : <Component { ...others } />
  1. const withTodosNull = (Component) => (props) =>
  2. ...
  3. const withTodosEmpty = (Component) => (props) =>
  4. ...
  5. const withLoadingIndicator = (Component) => ({ isLoadingTodos, ...others }) =>
  6. ...
  7. function TodoList({ todos }) {
  8. ...
  9. }
  10. const TodoListOne = withTodosEmpty(TodoList);
  11. const TodoListTwo = withTodosNull(TodoListOne);
  12. const TodoListThree = withLoadingIndicator(TodoListTwo);
  13. function App(props) {
  14. return (
  15. <TodoListThree
  16. todos={props.todos}
  17. isLoadingTodos={props.isLoadingTodos}
  18. />
  19. );
  20. }

第14-16行,因为每个HOC都是一个组件,所以可以链式调用。

  1. const TodoListWithConditionalRendering = withLoadingIndicator(withTodosNull(withTodosEmpty(TodoList)));

全部代码见: https://stackblitz.com/edit/react-l4xhx7

  1. import React from 'react';
  2. import './style.css';
  3. function TodoList({ todos, isLoadingTodos }) {
  4. if (isLoadingTodos) {
  5. return (
  6. <div>
  7. <p>Loading todos ...</p>
  8. </div>
  9. );
  10. }
  11. if (!todos.length) {
  12. return (
  13. <div>
  14. <p>You have no Todos.</p>
  15. </div>
  16. );
  17. }
  18. return (
  19. <div>
  20. <ul>
  21. {todos.map(todo => (
  22. <li>{todo.todo}</li>
  23. ))}
  24. </ul>
  25. </div>
  26. );
  27. }
  28. const withTodosNull = Component => props =>
  29. !props.todos ? null : <Component {...props} />;
  30. const withTodosEmpty = Component => props =>
  31. !props.todos.length ? (
  32. <div>
  33. <p>You have no Todos.</p>
  34. </div>
  35. ) : (
  36. <Component {...props} />
  37. );
  38. const withLoadingIndicator = Component => ({ isLoadingTodos, ...others }) =>
  39. isLoadingTodos ? (
  40. <div>
  41. <p>Loading todos ...</p>
  42. </div>
  43. ) : (
  44. <Component {...others} />
  45. );
  46. // const TodoListOne = withTodosEmpty(TodoList);
  47. // const TodoListTwo = withTodosNull(TodoListOne);
  48. // const TodoListWithConditionalRendering = withLoadingIndicator(TodoListTwo);
  49. const TodoListWithConditionalRendering = withLoadingIndicator(
  50. withTodosNull(withTodosEmpty(TodoList))
  51. );
  52. export default function App() {
  53. const todoList = [{ id: 1, todo: 'react' }, { id: 2, todo: 'webpack' }];
  54. return (
  55. <div>
  56. <TodoListWithConditionalRendering
  57. todos={todoList}
  58. isLoadingTodos={false}
  59. />
  60. </div>
  61. );
  62. }

彩蛋:
用HOC 改写Render Props中的例子

  1. const withAmount = currencyComponents =>
  2. class Amount extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. amount: 0,
  7. };
  8. }
  9. onIncrement = () => {
  10. this.setState(state => ({ amount: state.amount + 1 }));
  11. };
  12. onDecrement = () => {
  13. this.setState(state => ({ amount: state.amount - 1 }));
  14. };
  15. render() {
  16. return (
  17. <div>
  18. <span>US Dollar: {this.state.amount} </span>
  19. <button type="button" onClick={this.onIncrement}>
  20. +
  21. </button>
  22. <button type="button" onClick={this.onDecrement}>
  23. -
  24. </button>
  25. {currencyComponents.map(CurrencyComponent => (
  26. <CurrencyComponent amount={this.state.amount} />
  27. ))}
  28. </div>
  29. );
  30. }
  31. };
  32. const CurrenciesWithAmount = withAmount([Euro, Pound]);

参考链接

https://www.robinwieruch.de/react-higher-order-components
https://mp.weixin.qq.com/s/mb97LGzHT9t7Md2-w3z0Cg