做者:Brandon Newtonjavascript
原文:Higher-Order Components (HOCs) for Beginnershtml
谈点:一篇面向初学者的 HOC 介绍。高阶组件听起来挺唬人的,只看名字恐怕不是那么容易明白到底是何物,并且一般来说高阶组件并非组件,而是接受组件做为参数,而且返回组件的函数。早期利用 ES5 的 mixin 语法来作的事,基本均可以使用高阶组件代替,并且能作的还有更多。java
写这篇文章的原由是其余关于高阶组件(Higher-Order Components)的文章,包含官方文档,都令初学者感到至关困惑。我知道有高阶组件这样一个东西,但不知道它到底有什么用。因此,想经过一篇文章来对高阶组件有一个更好的理解。react
在此以前,咱们须要先来说一下 JavaScript 中的函数。git
接下来将提供一些箭头函数的简单示例,若是以前没有使用过,能够认为它们与普通函数基本一致。下面的代码会展现箭头函数与普通函数的区别。github
function () { return 42 } // same as: () => 42 // same as: () => { return 42 } function person(name) { return { name: name } } // same as: (name) => { return { name: name } }
阅读 MDN 的箭头函数文档 了解更多信息。json
就像是数字、字符串、布尔值 同样,函数也是值,意味着能够像传递其余数据同样传递函数,能够将函数做为参数传递给另一个函数。redux
const execute = (someFunction) => someFunction() execute(() => alert('Executed'))
也能够在在函数中返回一个函数:api
const getOne = () => () => 1 getOne()()
之因此在 getOne
后面有两个 ()
,是由于第一个返回的返回值是一个函数。以下:数组
const getOne = () => () => 1 getOne //=> () => () => 1 getOne() //=> () => 1 getOne()() //=> 1
从函数返回函数能够帮助咱们追踪初始输入函数。例如,下面的函数接受一个数字做为参数,并返回一个将该参数乘以新参数的函数:
const multiply = (x) => (y) => x * y multiply(5)(20)
这个示例跟上述 getOne
同样,在下面这个例子,让 x = 5,y = 20。
const multiply = (x) => (y) => x * y multiply //=> (x) => (y) => x * y multiply(5) //=> (y) => 5 * y multiply(5)(20) //=> 5 * 20
在只传入一个参数调用 multiply
函数时,即部分调用该函数。好比,multiply(5)
讲获得一个将其输入值乘以 5 的函数,multiply(7)
将获得一个将其输入值乘以 7 的函数。依此类推。经过部分调用能够建立一个预约义功能的新函数:
const multiply = (x) => (y) => x * y const multiplyByFive = multiply(5) const multiplyBy100 = multiply(100) multiplyByFive(20) //=> 100 multiply(5)(20) //=> 100 multiplyBy100(5) //=> 500 multiply(100)(5) //=> 500
一开始看起来彷佛没什么用,可是,经过部分调用这种方式能够编写可读性更高,更易于理解的代码。举个例子,能够用一种更清晰的方式来代替 style-components 的函数插入语法。
// before const Button = styled.button` background-color: ${({ theme }) => theme.bgColor} color: ${({ theme }) => theme.textColor} ` <Button theme={themes.primary}>Submit</Button> // after const fromTheme = (prop) => ({ theme }) => theme[prop] const Button = styled.button` background-color: ${fromTheme("bgColor")} color: ${fromTheme("textColor")} ` <Button theme={themes.primary}>Submit</Button>
咱们建立一个接受一个字符串做为参数的函数 fromTheme("textColor")
:它返回一个接受具备 theme
属性的对象的函数:({ theme }) => theme[prop]
,而后再经过初始传入的字符串 "textColor"
进行查找。咱们能够作得更多,写相似的 backgroundColor
和 textColor
这种部分调用 fromTheme
的函数:
const fromTheme = (prop) => ({ theme }) => theme[prop] const backgroundColor = fromTheme("bgColor") const textColor = fromTheme("textColor") const Button = styled.button` background-color: ${backgroundColor} color: ${textColor} ` <Button theme={themes.primary}>Submit</Button>
高阶函数的定义是,接受函数做为参数的函数。若是曾经使用过相似 map 这样的函数,可能已经很熟悉高阶函数。若是不熟悉 map,它是一个数组遍历的方法,接受一个函数做为参数应用到数组中的每一个元素。例如,能够像这样对一个数组做平方:
const square = (x) => x * x [1, 2, 3].map(square) //=> [ 1, 4, 9 ]
能够实现一个咱们本身的 map
版原本说明这个概念:
const map = (fn, array) => { const mappedArray = [] for (let i = 0; i < array.length; i++) { mappedArray.push( // apply fn with the current element of the array fn(array[i]) ) } return mappedArray }
而后再使用咱们的 map 版原本对一个数组做平方:
const square = (x) => x * x console.log(map(square, [1, 2, 3, 4, 5])) //=> [ 1, 4, 9, 16, 25 ]
译者注:咱们也能够将 map 方法从对象中解耦出来:
const map = (fn, array) => Array.prototype.map.call(array, fn)这样也能够像上述例子同样调用。
或者更函数式的作法,再来点柯里化:const map = array => fn => Array.prototype.map.call(array, fn)
或者是返回一个 <li>
的 React 元素数组:
const HeroList = ({ heroes }) => ( <ul> {map((hero) => ( <li key={hero}>{hero}</li> ), heroes)} </ul> ) <HeroList heroes=[ "Wonder Woman", "Black Widow", "Spider Man", "Storm", "Deadpool" ]/> /*=> ( <ul> <li>Wonder Woman</li> <li>Black Widow</li> <li>Spider Man</li> <li>Storm</li> <li>Deadpool</li> </ul> )*/
咱们知道,高阶函数是接受函数做为参数的函数。在 React 中,任何返回 JSX 的函数都被称为无状态函数组件,简称为函数组件。基本的函数组件以下所示:
const Title = (props) => <h1>{props.children}</h1> <Title>Higher-Order Components(HOCs) for React Newbies</Title> //=> <h1>Higher-Order Components(HOCs) for React Newbies</h1>
高阶组件则是接受组件做为参数并返回组件的函数。如何使用传入组件彻底取决于你,甚至能够彻底忽视它:
// Technically an HOC const ignore = (anything) => (props) => <h1>:)</h1> const IgnoreHeroList = ignore(HeroList) <IgnoreHeroList /> //=> <h1>:)</h1>
能够编写一个将输入转换成大写的 HOC:
const yell = (PassedComponent) => ({ children, ...props }) => <PassedComponent {...props}> {children.toUpperCase()}! </PassedComponent> const Title = (props) => <h1>{props.children}</h1> const AngryTitle = yell(Title) <AngryTitle>Whatever</AngryTitle> //=> <h1>WHATEVER!</h1>
你也能够返回一个有状态组件,由于 JavaScript 中的类不过是函数的语法糖。这样就可使用到 React 生命周期的方法,好比 componentDidMount
。这是 HOCs 真正有用的地方。咱们如今能够作一些稍微有趣点的事,好比将 HTTP 请求的结果传递给函数组件。
const withGists = (PassedComponent) => class WithGists extends React.Component { state = { gists: [] } componentDidMount() { fetch("https://api.github.com/gists/public") .then((r) => r.json()) .then((gists) => this.setState({ gists: gists })) } render() { return ( <PassedComponent {...this.props} gists={this.state.gists} /> ) } } const Gists = ({ gists }) => ( <pre>{JSON.stringify(gists, null, 2)}</pre> ) const GistsList = withGists(Gists) <GistsList /> //=> Before api request finishes: // <Gists gists={[]} /> // //=> After api request finishes: // <Gists gists={[ // { /* … */ }, // { /* … */ }, // { /* … */ } // ]} />
withGists
会传递 gist api 调用的结果,而且你能够在任何组件上使用。点击这里 能够看到一个更加完整的例子。
react-redux 也是使用 HOC, connect 将应用 store 的值传递到“已链接” 的组件。它还会执行一些错误检查和组件生命周期优化,若是手动完成将致使编写大量重复代码。
若是你发现本身在不一样地方编写了大量的代码,那么也能够将代码重构成可重用的 HOC。
HOCs 很是具备表现力,可使用它们创造不少很酷的东西。
尽量地保持你的 HOC 简单,不要编写须要阅读长篇大论才能理解的代码。
下面有一些练习,来巩固对 HOC 的理解:
shouldComponentUpdate
,以免更新。React.Children.toArray
对传入组件子元素进行排序。