基本使用
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 (
<TodoListThree
todos={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>
<TodoListWithConditionalRendering
todos={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