高阶元件(hoc ),适合初学者

高阶元件(hoc ),适合初学者

原文:https://medium.com/hackernoon/higher-order-components-hocs-for-beginners-25cdcf1f1713

前言

注:这是最初发布在我的个人网站上的。

我写这篇文章是因为其他每篇文章——包括关于高阶组件的 React 官方文档——都让我这个初学者感到困惑。我明白高阶组件是一个东西,但不明白它们如何有用。本文旨在澄清一些关于高阶元件的困惑。

在我们理解 HOCs 之前,我们必须先了解一些关于 Javascript 中函数的事情。

ES6 箭头功能简介

本文将提供仅使用 ES6 箭头函数的例子。如果你以前从未见过箭头函数,它们本质上等同于正则函数表达式。下面的代码展示了常规函数和箭头函数之间的区别。

function () {
  return 42
}// same as:
() => 42// same as:
() => {
  return 42
}function person(name) {
  return { name: name }
}// same as:
(name) => {
  return { name: name }
}

阅读 MDN 上的箭头功能文档以获得更完整的理解。

作为价值和部分应用的功能

就像数字,字符串,布尔等。,函数是值。这意味着您可以像传递其他数据一样传递函数。您可以将函数作为参数传递给另一个函数:

const execute = (someFunction) => someFunction()execute(() => alert('Executed'))

你可以从一个函数返回一个函数。

const getOne = () => () => 1getOne()()

我们在getOne后面有两个()的原因是函数的第一个应用返回另一个。举例说明:

const getOne = () => () => 1getOne
//=> () => () => 1getOne()
//=> () => 1getOne()()
//=> 1

从函数返回函数的好处是,我们可以编写跟踪初始输入的函数。例如,下面的函数接受一个数字作为参数,并返回一个将该参数乘以一个新参数的函数:

const multiply = (x) => (y) => x * ymultiply(5)(20)

这个例子和getOne的工作原理一样,每个括号向函数应用一些输入。在这种情况下,我们将x分配给5,将y分配给20

const multiply = (x) => (y) => x * ymultiply
//=> (x) => (y) => x * ymultiply(5)
//=> (y) => 5 * ymultiply(5)(20)
//=> 5 * 20

当我们调用只有一个参数的函数multiply时,我们部分地应用了这个函数。当我们调用multiply(5)时,我们得到一个将它的输入乘以 5 的函数。如果我们调用multiply(7),我们会得到一个将输入乘以 7 的函数,依此类推。我们可以使用部分应用程序创建具有预定义输入的新功能:

const multiply = (x) => (y) => x * yconst multiplyByFive = multiply(5)
const multiplyBy100 = multiply(100)multiplyByFive(20)
//=> 100
multiply(5)(20)
//=> 100multiplyBy100(5)
//=> 500
multiply(100)(5)
//=> 500

这可能一开始看起来不是超级有用。但是,您可以使用分部应用程序来编写更易于阅读和推理的代码。例如,我们可以用更简洁的东西来代替[styled-components](https://www.styled-components.com/docs/basics#adapting-based-on-props)复杂的函数插值语法。

// 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"的初始字符串来查找该对象。我们可以更进一步,编写类似于backgroundColortextColor的函数,它们部分应用了fromTheme函数:

const fromTheme = (prop) => ({ theme }) => theme[prop]
const backgroundColor = fromTheme("bgColor")
const textColor = fromTheme("textColor")const Button = styled.button`
  background-color: ${backgroundColor}
  color: ${textColor}
`

高阶函数

高阶函数被定义为接受一个函数作为自变量的函数。如果您曾经使用过像[map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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 * xconsole.log(map(square, [1, 2, 3, 4, 5]))
//=> [ 1, 4, 9, 16, 25 ]

或者返回一个数组<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](https://facebook.github.io/react/docs/jsx-in-depth.html)的函数都被称为无状态功能组件,或简称为功能组件。一个基本的功能组件如下所示:

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>

您可以编写一个将其输入转换为大写的特设:

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 中的类是函数的语法糖。这允许您挂钩到像componentDidMount这样的 React 生命周期方法。这就是 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,它将传递 gists api 调用的结果。你可以在这里看到这个更具体的例子。

结论:hoc 是🔥 🔥 🔥

Redux 使用一个特设的[connect](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options)将值从应用程序商店传递到“连接”的组件。它还进行一些错误检查和组件生命周期优化,如果手动进行,将会导致您编写大量样板代码。

如果您发现自己在不同的地方写了很多代码做同样的事情,那么您可以将这些代码重构为一个可重用的 HOC。

hoc 真的很有表现力,你可以用它们做很多很酷的东西。然而,因为它们太富于表现力了,如果你想的话,你可以过度使用它们。

尽量让你的 HOCs 保持简单,并致力于编写不需要你读一篇长文章就能理解的代码。

附加练习

以下是一些巩固你对 HOCs 理解的练习:

  • 编写一个撤销其输入的特设
  • 编写一个从 API 向其传递的组件提供数据的特设
  • 写一个实现[shouldComponentUpdate](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate) 的特设来避免调和
  • 编写一个使用[React.Children.toArray](https://facebook.github.io/react/docs/react-api.html#react.children.toarray)对传递给其传递组件的子组件进行排序的特设组件。

本站为非盈利网站,作品由网友提供上传,如无意中有侵犯您的版权,请联系删除