如何使用React的高阶组件
原文地址
伴随着React开始进入公众视野,它带来了新的开发前端架构的方式。它被认为是MVC模式的“View”层。
一直以来,container/presentational模式都倍受React生态的核心贡献者推崇。
在这篇文章,你将学习更多关于container/presentational模式的知识,以及如何使用它来创建React组件。
在开始前,让我们先阐明一些术语:
一个container是一个获取数据并且(或者)转换数据的组件。
一个presentational 组件展示来自props的数据。没有其他的了。
一个container充当一个高阶组件。这里结合使用高阶组件可以将这两个层隔离开来。这可以防止你在presentational层混入不需要的逻辑。
如果你有意愿,你可以一边用ESNextbin进行演练。
跳转到ESNextbin页面后,在它顶部你可以看到3个Tab:CODE,HTML和PACKAGE。点击PACKAGE,切换到该Tab后添加依赖react和react-dom。
现在点击CODE,然后引入react以及react-dom的render。
1 2
| import { default as React } from ‘react’ import { render } from ‘react-dom’
|
现在让我们创建一个只渲染“hello,world!”的stateless functional组件。
1
| const Presentational = () => <div>Hello, world!</div>
|
之后,你仅需使用render将该组件挂载到DOM上。
1
| render(<Presentational />, document.querySelector(‘#root’))
|
当然现在你还没在HTML里面创建root节点,让我们现在进行。所有你需要做的就是添加一个id是root的div。
1 2 3 4 5 6 7 8 9 10 11 12
| <!doctype html> <html> <head> <meta charset=”utf-8"> <title>ESNextbin Sketch</title> <! — put additional styles and scripts here → </head> <body> // Notice something different <div id=‘root’></div> </body> </html>
|
现在是见证真理的时刻。点击页面右上角的EXECUTE。你应当看到屏幕上显示了Hello,world!。
现在你已经有了一个presentational组件,但你还需要一个container组件用于获取数据。
让我们在presentational组件前面创建一个container组件。为了创建改container组件,你需要使用React的Component类。因此,你需要在页面顶部引入它。
1
| import { default as React, Component } from ‘react’
|
现在开始你可以着手创建container组件了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Container extends Component { state = { data: [] } componentDidMount() { fetch(‘https: .then(response => response.json()) .then(data => this.setState({ data })) } render() { return <Presentational data={this.state.data} /> } }
|
在顶部,你有一个state,它是个空数组。你还使用了componentDidMount
这个生命周期函数用于获取数据。
在得到响应后你用它返回的数据更新state。接着这个数据作为prop传到了presentational组件。这个意味着你需要升级presentational组件的代码。
1 2
| const Presentational = ({ data }) => <div>{JSON.stringify(data)}</div>
|
这里我使用解构从props对象取得data字段。否则它看起来将是这样的:
1 2
| const Presentational = props => <div>{JSON.stringify(props.data)}</div>
|
这些改动迫使你要修改挂载的组件。现在你需要渲染container组件。
1
| render(<Container />, document.querySelector(‘#root’))
|
当你点击EXECUTE后你会看到一大堆的数据填满了你的屏幕。天啊!
这里你所做的事是抽出了container组件以及应当由它完成的事。但这不够灵活。你需要将presentational组件写到你的container组件里面。是否存在一种更加灵活的方式?正式进入高阶组件。
一个高阶组件是一个函数接收一个组件并返回一个新的组件。它实际看起来是怎样的。让我们新建一个函数接收一个组件并返回一个包含来自FCC API数据的新组件。你将更新container组件来完成这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const container = Presentational => class extends Component { state = { data: [] } componentDidMount() { fetch(‘https: .then(response => response.json()) .then(data => this.setState({ data })) } render() { return <Presentational data={this.state.data} /> } }
|
这里并没有太大的不同。但还有一个额外的步骤需要完成。你需要将这个container组件和presentational组件组合在一起。我仅仅是使用presentational组件作为参数调用container
函数,这仅需在presentational组件下面添加如下代码:
1
| const HigherOrderComponent = container(Presentational)
|
现在你需要做的只是渲染该高阶组件。
1
| render(<HigherOrderComponent />, document.querySelector(‘#root’))
|
你弄明白了这里你完成了什么吗?现在你对任何组件使用container
,而不仅仅是那个presentational组件。
让我们更进一步。我们调用另一个函数告诉container组件从哪获取数据。我认为这会使这个container组件更具有灵活性。
新的container组件看起来是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const container = endpoint => Presentational => class extends Component { state = { data: [] } componentDidMount() { fetch(endpoint) .then(response => response.json()) .then(data => this.setState({ data })) } render() { return <Presentational data={this.state.data} /> } }
|
现在你需要更新高阶组件的代码来处理新的远端调用。
1 2 3
| const HigherOrderComponent = container( ‘https: )(Presentational)
|
If that looks a little off to you then you have good eye(这句看不懂囧)。假若你能进一步做抽象呢?转入recompose。
让我们先暂停一会讨论下compose的思想是什么。为了理解compose你先要理解mapping。
在计算机编程领域mapping指接收一个值并将它转成另一个值。基本上每个被创建出来的函数都是用来做这个的。看下下面的例子体会下:
1 2 3 4
| function double(x) { return x * 2 } const doubled = double(2)
|
假设现在我想创建另一个转换函数,比如3倍,我该如何做呢?
1 2 3 4 5 6 7 8 9
| function triple(x) { return x * 3 } function double(x) { return x * 2 } const messedAroundAndGotATripleDouble = triple(double(2))
|
很简单,对吧?假如我想一个接一个地创建转换函数呢,函数调用会变得相当长。借助于compose函数你不但可以使代码更具可读性,而且组合性更高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function compose(f, g) { return function(x) { return f(g(x)) } } function triple(x) { return x * 3 } function double(x) { return x * 2 } const tripleThenDouble = compose(triple, double) const messedAroundAndGotATripleDouble = tripleThenDouble(2)
|
这只是一个简单的例子,但是它展示了你可以如何对同一个初始值应用多个转换函数。
现在设想presentational组件就是初始值,而container组件是转换函数之一。大脑在颤抖了!
使用recompose前要先将recompose库拉下来。我们切到PACKAGES页下将recompose库添加进来。
1 2 3 4 5 6 7 8 9 10
| { “name”: “esnextbin-sketch”, “version”: “0.0.0”, “dependencies”: { “react”: “15.3.2”, “react-dom”: “15.3.2”, “recompose”: “latest”, “babel-runtime”: “6.11.6” } }
|
返回到CODE页,引入compose。
1
| import { compose } from ‘recompose’
|
接着你就可以使用compose来进一步抽象高阶组件。
1 2 3 4 5
| const HigherOrderComponent = compose( container( ‘https: ) )
|
并且你可以更加声明式的描述整个组件所做的事。
1 2 3
| const FetchAndDisplayFCCData = HigherOrderComponent(Presentational) render(<FetchAndDisplayFCCData />, document.querySelector(‘#root’)
|
最终这允许你创建更加灵活多用的组件。你甚至可以与高阶组件分离单独测试presentational组件。高阶组件万岁!
为了好玩,让我们将presentational组件从渲染一大块数据改为渲染一组列表元素。
1 2 3 4 5 6 7 8
| const Presentational = ({ data }) => <ul> {data.map((v, k) => <li key={k}> {v.username} </li> )} </ul>
|
哇哦!真是好多的内容。如果想看完整的代码你可以访问我创建的这个gist。
更多的内容可以访问Andrew Clark的这个叫recompose的React工具库。