- Container and presentational components
- Compound compoents
- Render props pattern
- Higher-order components
container and presentational components
容器和表述组件。就是将复杂组件的属性内容进行抽取成为一个新的组件。
(略)
Compound components
合成组件,就是将一系列组件一起工作。
(略)
Higher-order components
A higher-order component(HOC) 是一个函数组件,接收一个组件参数,返回该组件的增强版本。这样看起来没什么意义,下面通过一个例子withLoader
组件来阐述。最终效果类似延迟加载动态圈。
Adding asynchronous data fetching
下面构造一份延迟数据来模拟真实的网络环境,
1 2 3 4 5 6 7 8 9
| const wait = (ms: number): Promise<void> => { return new Promise(resolve => setTimeout(resolve, ms)); };
export const getProduct = async (id: number): Promise<IProduct | null> => { await wait(1000); const foundProducts = products.filter(customer => customer.id === id); return foundProducts.length === 0 ? null : foundProducts[0]; };
|
接着在原来的ProductPage
页面导入getProduct
函数,
1
| import { getProduct, IProduct } from "./ProductsData";
|
在ProductPage
状态中加入一个新的属性loading
,表示数据是否已经加载,
1 2 3 4 5
| interface IState { product?: IProduct; added: boolean; loading: boolean; }
|
在构造函数中初始化状态属性,
1 2 3 4 5 6 7
| public constructor(props: Props) { super(props); this.state = { added: false, loading: true }; }
|
在组件加载时使用getProduct
函数,
1 2 3 4 5 6 7 8 9
| public async componentDidMount() { if (this.props.match.params.id) { const id: number = parseInt(this.props.match.params.id, 10); const product = await getProduct(id); if (product !== null) { this.setState({ product, loading: false }); } } }
|
这里使用了await
关键字异步调用getProduct
。另外还要修改生命周期方法componentDidMount
带上async
关键字。
Implementing the withLoader HOC
我们将会创建withLoader
加载组件,被用于指示组件处于繁忙状态。
- 创建一个新文件,
withLoader.tsx
,内容如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import * as React from "react";
interface IProps { loading: boolean; }
const withLoader = <P extends object>( Component: React.ComponentType<P> ): React.SFC<P & IProps> => (props: P & IProps) => props.loading ? ( <div className="loader-overlay"> <div className="loader-circle-wrap"> <div className="loader-circle" /> </div> </div> ) : ( <Component {...props} /> );
export default withLoader;
|
其中,
withLoader
是一个函数,接收一个类型是P
的组件
withLoader
调用一个函数组件
- 函数组件的属性定义是
P & IProps
,它是一个交集类型
- 组件的所有属性会通过SFC传入,并带上一个新的属性
loading
- props被解构为一个
loading
变量,剩余的其它属性作为rest参数
- 添加加载转轮的CSS样式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| .loader-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: black; opacity: 0.3; z-index: 10004; }
.loader-circle-wrap { position: fixed; top: 0; right: 0; bottom: 0; left: 0; height: 100px; width: 100px; margin: auto; }
.loader-circle { border: 4px solid #ffffff; border-top: 4px solid #899091; border-radius: 50%; width: 100px; height: 100px; animation: loader-circle-spin 0.7s linear infinite; }
|
Consuming the withLoader HOC
要消费这个高阶组件,只需要简单包装原来的组件即可。
原来的Product.tsx
文件修改为,
1 2 3 4 5
| import withLoader from "./withLoader";
...
export default withLoader(Product);
|
在引用的页面部分修改为,即ProductPage
页面,
1 2 3 4 5 6 7 8 9 10
| {product || this.state.loading ? ( <Product loading={this.state.loading} product={product} inBasket={this.state.added} onAddToBasket={this.handleAddClick} /> ) : ( <p>Product not found!</p> )}
|
修改原来Props的属性选项为可选的,
1 2 3 4 5
| interface IState { product?: IProduct; added: boolean; loading: boolean; }
|
另外需要处理空值的情况,修改
1 2 3 4 5 6 7 8 9 10 11
| const handleAddClick = () => { props.onAddToBasket(); }; if (!product) { return null; } return ( <React.Fragment> ... </React.Fragment> );
|
HOC非常适用于对原来组件的增强处理。比较常见的是React Router中使用了非常多这种HOC模式。React Router自身也实现了withRouter
组件函数。