基本使用
Higher order components usually take a component and optional arguments as input and return an enhanced component of the input component.
高阶组件是函数【组件】,参数为组件,返回值为新组件的函数,高阶组件是将组件转换为另一个组,形如:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
下面我们一步一步探究HOC,
import React from "react";import "./style.css";function TodoList({ todos, isLoadingTodos }) {if (isLoadingTodos) {return (<div><p>Loading todos ...</p></div>);}if (!todos) {return null;}if (!todos.length) {return (<div><p>You have no Todos.</p></div>);}return (<div><ul>{todos.map(todo => (<li>{todo.todo}</li>))}</ul></div>);}export default function App() {const todoList = [{ id: 1, todo: "react" }, { id: 2, todo: "webpack" }];return (<div><TodoList isLoadingTodos={false} todos={todoList} /></div>);}
这是我们应用中经常遇到的,会有很多边界条件的判断,loading、空判断……
这些边界条件的判断会经常在其他的组件中使用,如何复用?
我们来写一个HOC的雏形
function withTodosNull(Component) {return function (props) {...}}
HOC实际上是一个函数,参数是一个组件,返回一个组件。
一般情况下HOC是以with开头来标明是HOC,但这不是强制的。
function withTodosNull(Component) {return function (props) {return !props.todos? null: <Component { ...props } />}}或者箭头函数形式const withTodosNull = (Component) => (props) =>!props.todos? null: <Component { ...props } />******** 在应用中使用const withTodosNull = (Component) => (props) =>...function TodoList({ todos }) {...}const TodoListWithNull = withTodosNull(TodoList);function App(props) {return (<TodoListWithNull todos={props.todos} />);}
上面的HOC根据三元运算符来判断是否返回Component,这里的props是通过组件树传递到Component中
其实我们可以理解TodoListWithNull就是一个组件,接收了一个todos的props
同理其他情况的边界条件HOC:
const withTodosEmpty = (Component) => (props) =>!props.todos.length? <div><p>You have no Todos.</p></div>: <Component { ...props } />const withLoadingIndicator = (Component) => ({ isLoadingTodos, ...others }) =>isLoadingTodos? <div><p>Loading todos ...</p></div>: <Component { ...others } />
const withTodosNull = (Component) => (props) =>...const withTodosEmpty = (Component) => (props) =>...const withLoadingIndicator = (Component) => ({ isLoadingTodos, ...others }) =>...function TodoList({ todos }) {...}const TodoListOne = withTodosEmpty(TodoList);const TodoListTwo = withTodosNull(TodoListOne);const TodoListThree = withLoadingIndicator(TodoListTwo);function App(props) {return (<TodoListThreetodos={props.todos}isLoadingTodos={props.isLoadingTodos}/>);}
第14-16行,因为每个HOC都是一个组件,所以可以链式调用。
const TodoListWithConditionalRendering = withLoadingIndicator(withTodosNull(withTodosEmpty(TodoList)));
全部代码见: https://stackblitz.com/edit/react-l4xhx7
import React from 'react';import './style.css';function TodoList({ todos, isLoadingTodos }) {if (isLoadingTodos) {return (<div><p>Loading todos ...</p></div>);}if (!todos.length) {return (<div><p>You have no Todos.</p></div>);}return (<div><ul>{todos.map(todo => (<li>{todo.todo}</li>))}</ul></div>);}const withTodosNull = Component => props =>!props.todos ? null : <Component {...props} />;const withTodosEmpty = Component => props =>!props.todos.length ? (<div><p>You have no Todos.</p></div>) : (<Component {...props} />);const withLoadingIndicator = Component => ({ isLoadingTodos, ...others }) =>isLoadingTodos ? (<div><p>Loading todos ...</p></div>) : (<Component {...others} />);// const TodoListOne = withTodosEmpty(TodoList);// const TodoListTwo = withTodosNull(TodoListOne);// const TodoListWithConditionalRendering = withLoadingIndicator(TodoListTwo);const TodoListWithConditionalRendering = withLoadingIndicator(withTodosNull(withTodosEmpty(TodoList)));export default function App() {const todoList = [{ id: 1, todo: 'react' }, { id: 2, todo: 'webpack' }];return (<div><TodoListWithConditionalRenderingtodos={todoList}isLoadingTodos={false}/></div>);}
彩蛋:
用HOC 改写Render Props中的例子
const withAmount = currencyComponents =>class Amount extends Component {constructor(props) {super(props);this.state = {amount: 0,};}onIncrement = () => {this.setState(state => ({ amount: state.amount + 1 }));};onDecrement = () => {this.setState(state => ({ amount: state.amount - 1 }));};render() {return (<div><span>US Dollar: {this.state.amount} </span><button type="button" onClick={this.onIncrement}>+</button><button type="button" onClick={this.onDecrement}>-</button>{currencyComponents.map(CurrencyComponent => (<CurrencyComponent amount={this.state.amount} />))}</div>);}};const CurrenciesWithAmount = withAmount([Euro, Pound]);
参考链接
https://www.robinwieruch.de/react-higher-order-components
https://mp.weixin.qq.com/s/mb97LGzHT9t7Md2-w3z0Cg
