使用 Hooks 开始使用 React 什么是 React?JSX 将组件拆分成多个文件 状态效果 自定义 Hooks 类组件 结论

2025-06-08

使用 Hooks 开始使用 React

什么是 React?

JSX

将组件拆分为多个文件

状态

效果

自定义钩子

类组件

结论

你好呀。

几周前,React 正式发布了这个名为 Hooks 的新 API,而过去几个月一直好奇地关注着 React,甚至编写过一些组件的你,一直在想现在是不是该进一步了解 React 的时候了……

好吧,如果有一件事我可以向你保证,那就是现在比以往任何时候都更适合开始学习 React 技能。React 已经存在了 5 年,并从周围的环境和过去的经验中汲取了很多教训,现在它是一个非常可靠的库,几乎可以在任何地方构建应用程序。新的 API 使组件之间的逻辑组合和使用更加简洁和便捷。

让我们简单介绍一下最原始的概念,介绍最基本的钩子,然后我会给你指出一些很棒的资源,你可以在其中了解有关 React 的一切。

什么是 React?

你可能已经知道什么是 React,但我们先简单介绍一下。React 是一个库,它负责找到在某个地方渲染应用程序的最佳方式。它基于 JavaScript 构建,我们主要用它来构建 Web 应用,但如今它几乎可以用于各种平台的渲染,例如在 Android 和 iOS 上、React Native、电视、服务器、命令行等等。

这怎么可能呢?React 构建了一套逻辑,将渲染所需的最小工作量与实际的渲染原语分离开来。这样,在某个地方使用 React 时,我们只需要构建渲染层。对于 Web 来说,这就是一个react-dom库。

要创建 React 元素,我们需要做的就是将 React 和 ReactDOM 导入到 html 文件中,然后在前两个脚本标签之后打开第三个脚本标签,然后开始创建组件,对吗?

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React Example</title>
</head>

<body>
  <div id="root"></div>
  <script src="https://unpkg.com/react@16.8.3/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16.8.3/umd/react-dom.development.js"></script>
  <script>
    // Your code is going to go here
  </script>
</body>

</html>

嗯,还没完全搞定。我们先把根 div 添加到那里,它会是我们渲染组件的地方。不过,首先我们必须创建组件,然后才能把它们放到root我们创建的 div 里。

要创建元素,我们必须在第三个标签内执行以下操作:

const App = () => React.createElement(
    'div',
    {},
    React.createElement('h1', {}, 'My Element')
)

为了渲染它,我们在 App 组件之后添加:

ReactDOM.render(React.createElement(App), document.getElementById("root"))

现在我们应该看到h1元素内部div有“My Element”文本。

我知道,你可能会想:“真的吗?所有这些就为了渲染一些简单的 HTML 代码?” 再等等吧。

值得注意的是,React 渲染的所有内容都是组件。这是 React 的又一大优势。你的应用被拆分成多个小块,它们可以协同工作,并抽象出其内部结构。

我们在这里使用了h1div,但是您放在那里的任何内容都将是输出,因此您也可以使用 Web 组件。

我们还可以随意嵌套组件,这时事情就开始变得有趣了。

让我们创建一个Person组件。

const Person = () => React.createElement(
    'div',
    {},
    [
        React.createElement('h1', {}, 'Juliano Rafael'),
        React.createElement('h2', {}, '@frontendwizard'),
        React.createElement('h2', {}, 'Front End Developer'),
    ]
)

Person现在,我们可以在我们的应用程序中创建任意数量的组件实例:

const App = () => React.createElement(
    'div',
    {},
    [
        React.createElement(Person),
        React.createElement(Person),
        React.createElement(Person),
    ]
)

有点无聊,但很有趣。你应该在你的 html 上看到三个 Juliano。Juliano 太多了,没人会喜欢的😄。我们组件的值Person是硬编码到组件中的。让我们用最基本的组件功能来改变它,那就是props。你注意到我们一直作为第二个参数传递给 的空对象了React.createElement吗?它们就是props。它们保存了传递给对象的所有属性。方便的是,它们也是传递给函数组件的第一个参数。让我们修改Person组件以使用它。

const Person = props => React.createElement(
    'div',
    {},
    [
        React.createElement('h1', {}, props.name),
        React.createElement('h2', {}, props.twitter),
        React.createElement('h2', {}, props.job),
    ]
)

太棒了,现在我们可以摆脱那些 Juliano 的克隆并赋予每个实例自己的身份。

const App = () => React.createElement(
    'div',
    {},
    [
        React.createElement(Person, {
            name: "Juliano Rafael",
            twitter: "@frontendwizard",
            job: "Front End Developer"
        }),
        React.createElement(Person, {
            name: "Leonardo Elias",
            twitter: "@leonardoelias_",
            job: "Front End Developer"
        }),
        React.createElement(Person, {
            name: "Marcos Eptacio",
            twitter: "@eptaccio",
            job: "JavaScript Developer"
        }),
        React.createElement(Person, {
            name: "Wallace Batista",
            twitter: "@uselessdevelop",
            job: "Front End Developer"
        }),
    ]
)

很酷吧?现在我们可以征服世界,构建最复杂的应用程序了!当然,用这种语法不行。虽然我们已经可以将代码抽象成组件,但这语法太繁琐,难以阅读。是时候在此基础上进行改进了。

JSX

没有人会在不使用 JSX 的情况下使用 React,这是因为 JSX 大大提高了代码的可读性。JSX 允许你编写类似 HTML 的语法,并将其转换为我们之前一直在使用的代码。缺点是,现在我们需要在工作流程中加入这个转换过程才能使用它。我们可以@babel/standalone直接在浏览器中执行此操作,但这对生产环境不利,因为它会实时转换代码,而这是在代码被浏览器处理之前必须完成的另一个步骤。

目前 React 项目中使用的头号构建工具是webpack。得益于create-react-app,整个设置都被抽象到了 中react-scripts。你需要做的就是:

npx create-react-app my-app
cd my-app
npm start

这将启动一个全新的应用程序http://localhost:3000

现在你问我:“嘿,你是如何从带有脚本的简单 html 文件转变为使用 webpack 进行这种对我来说像魔术一样的复杂设置的?”

我懂你的意思。相信我,你一定会爱上它。现在你只需要知道,这个设置能为你提供一个可靠的工作流程来开发、调试和发布你的应用,而无需设置所有 webpack 配置,这可不是件容易的事。这个设置会将你的 JSX 代码转译成我们之前写过的代码,并额外添加一些功能,让我们能够使用 ES2015+ 中一些并非所有浏览器都支持的优秀特性。总而言之,相信它react-scripts是一个安全的选择,能让你安心无忧。

设置已经足够了,现在我们可以重构我们的Person组件以使用 JSX:

const Person = props => (
    <div>
        <h1>{props.name}</h1>
        <h2>{props.twitter}</h2>
        <h2>{props.job}</h2>
    </div>
)

App可以这样写:

const App = () => (
    <div>
        <Person
            name="Juliano Rafael"
            twitter="@frontendwizard"
            job="Front End Developer"
        />
        <Person
            name="Leonardo Elias"
            twitter="@leonardoelias_"
            job="Front End Developer"
        />
        <Person
            name="Marcos Eptacio"
            twitter="@eptaccio"
            job="JavaScript Developer"
        />
        <Person
            name="Wallace Batista"
            twitter="@uselessdevelop"
            job="Front End Developer"
        />
    </div>
)

我不知道你是怎么想的,但这对我来说感觉好多了。组件嵌套得越多,你就越能看到 JSX 的好处。

不过,JSX 还是有一些小问题,你可以在这里的文档中阅读 JSX 的所有细节。目前,你需要知道的是:

  • 您的组件(除 HTML 中指定的组件外)必须大写,否则它会尝试将其作为 Web 组件而不是 React 组件。
  • 如果您要向组件添加一个类,则需要使用className属性,而不是class因为 class 是 JavaScript 上的保留字。
  • 所有带有连字符的属性都需要加上前缀,data-否则 React 将不会在 DOM 上呈现它们。

我可以接受这一点。

将组件拆分为多个文件

您可能已经注意到,create-react-app默认情况下,组件会被拆分成单独的文件,并使用 ES2015 语法进行导入和导出。

这得益于后台的 webpack 设置。有了它,你可以将每个组件放入它自己的文件中,然后使用以下命令将其导出:

export default MyComponent

在另一个文件中,您可以使用 import 语句导入组件:

import MyComponent from './relative-path-to-MyComponent'

好的,现在我们可以用类似 HTML 的语法编写组件,将其分解成更小的组件,抽象出它的实现,但所有这些仍然是静态的。这是因为 props 在组件内部无法更改,它们是不可变的。我们需要一个可以存储状态并动态更改状态的东西,同时让 React 更新界面作为响应。

让我们谈论状态。

状态

状态是组件的一个动态属性。你可以在组件的生命周期内随意更改它,React 会负责更新渲染的组件,以反映状态的变化。每次你更改状态时,React 都会找到最快的方式更新渲染的组件,并负责为你完成更新。这真是太棒了。

为了创建状态,我们将使用 Hook。Hook 是 React 16.8 的新增功能,它极大地简化了 React API,并允许以全新的方式来构建和组合组件及功能。可以肯定的是,从现在开始,Hook 将成为我们创建组件的默认方式。

我们可以使用useStatefromReact并传入 initialValue 作为第一个参数来创建一个状态。此钩子将返回一个数组,其中第一个值是当前状态,第二个值是用于更新状态的函数。

const Counter = () => {
    const [count, setCount] = React.useState(0)
    const handleClick = () => setCount(count + 1)
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={handleClick}>Click Me!</button>
        </div>
    )
}

这是我们第一个真正的动态组件🎉🎉🎉。这正是 React 的真正优势所在。创建动态组件非常简单。

但事情可能会变得更有趣。只是useState我们实际上无法实现异步操作,而且说实话,Web 开发的主要部分是与 API 交互。那么,我们该怎么做呢?

效果

有时你需要对变化做出响应。这就是我们所说的 effect。你可以为组件生命周期内的各种事件触发 effect,例如挂载、卸载和更新。

为了演示这个概念,我们来做一些更贴近现实的事情。我们创建一个组件,从这个公共 API获取一张随机的猫咪图片,并将其渲染到页面上。

对于效果,我们有另一个钩子,即useEffect。这个钩子接受两个参数:第一个是在组件生命周期的某个时刻执行的函数,第二个是一个可选数组,列出了此函数所依赖的内容。第二个参数可以实现三件事:

  • 如果没有传递任何内容给它,React 会假定无论何时发生任何事情,都需要再次运行此效果。
  • 如果传递一个空数组,React 将假定该函数只需要在组件第一次渲染后运行一次。
  • 如果你在这个数组中放入一些东西,那么每次数组内的任何元素发生变化时,React 都会运行效果。

就这样:

const RandomCat = () => {
  const [ready, setReady] = React.useState(false)
  const [src, setSrc] = React.useState(null)
  const [error, setError] = React.useState(null)
  React.useEffect(() => {
    fetch("https://aws.random.cat/meow")
      .then(response => response.json())
      .then(data => {
        setReady(true)
        setSrc(data.file)
      })
      .catch(error => setError(error))
  }, [])
  if (!ready) return <p>Loading...</p>
  if (error) return <p>Oops, something went wrong!</p>
  return <img src={src} alt="random cat photo" />
}

瞧,我们的组件向第三方 API 发出了请求,获取了图片地址,并用图片标签渲染了它。我们可以将其重构为自定义钩子,使其更加完善。

自定义钩子

我们不必局限于使用 React 提供的 hooks。我们可以创建自己的 hooks,抽象出细节,只暴露重要的部分。让我们开始动手吧:

const useRandomCatImg = () => {
  const [ready, setReady] = React.useState(false)
  const [src, setSrc] = React.useState(null)
  const [error, setError] = React.useState(null)
  React.useEffect(() => {
    fetch("https://aws.random.cat/meow")
      .then(response => response.json())
      .then(data => {
        setReady(true)
        setSrc(data.file)
      })
      .catch(error => setError(error))
  }, [])
  return { ready, error, src }
}

const RandomCat = () => {
  const { ready, error, src } = useRandomCatImg()
  if (error) return <p>Oops, something went wrong!</p>
  if (!ready) return <p>Loading...</p>
  return <img src={src} alt="random cat photo" />
}

这样做的直接好处是,我们现在可以useRandomCatImg在任何我们认为合适的地方使用它。

这里有几点需要注意:

  • 钩子必须在函数组件内部调用
  • 这是一个约定,Hooks 应该始终以 开头use。这是因为 React 会假定你遵循了这一约定,并且只有在你遵循此约定时才会警告你违反了约定。所以,一定要遵循!

另外,请求过程中的状态管理确实有点繁琐和笨拙,但请稍等,因为 React Suspense 很快就会解决这个问题。我现在不打算深入讲解,因为我们还没讲到那一步,但你应该知道,它很快就会变得更好。

类组件

尽管 Hooks 很棒,但它最近才正式推出,而在此之前,React 的所有强大功能都已经存在,只是方式不同。虽然我们肯定会迁移到 Hooks 作为标准,但类组件仍然存在,而且它们不会消失。此外,在 Hooks 出现之前,你很可能会在任何地方找到它们。

通过创建一个类并扩展,你可以创建一个类组件React.Component。这样,你就可以访问组件的生命周期方法,例如componentDidMountcomponentDidUpdate等。你还可以实例化并使用它进行更改。componentShouldUpdatecomponentWillUnmountstatethis.setState

您可以使用类组件执行的所有操作,都可以使用钩子执行,反之亦然,除了componentDidCatch生命周期方法之外,但这也适用于钩子。

我们之前用钩子做的相同RandomCat组件,带有类,如下所示:

class RandomCat extends React.Component {
  constructor(props) {
    super(props)
    this.state = {}
    this.fetchRandomCatImg = this.fetchRandomCatImg.bind(this)
  }

  componentDidMount() {
    this.fetchRandomCatImg()
  }

  fetchRandomCatImg() {
    fetch("https://aws.random.cat/meow")
      .then(response => response.json())
      .then(data => {
        this.setState({
          error: null,
          ready: true,
          src: data.file
        })
      })
      .catch(error => this.setState({ error }))
  }

  render() {
    if (this.state.error) return <p>Oops, something went wrong!</p>
    if (!this.state.ready) return <p>Loading...</p>
    return <img src={this.state.src} alt="random cat photo" />
  }
}

虽然代码更冗长,但功能相同。由于使用了 ,这段逻辑fetchRandomCatImg无法在其他地方共享,因此复用起来也更加困难this,这使得它依赖于组件实例。这个问题有一些解决方案,主要是渲染 props 和高阶组件模式,但它们也各自带来了一些问题。Hooks 只是在 React 应用中实现逻辑复用的一种更好的方法。

与往常一样,Dan Abramov发表了一篇精彩的文章,解释了为什么钩子如此好。

结论

状态和效果是 React 的核心。它们占了 React 的 20%,却完成了 80% 的事情。你的下一步应该如下:

  • 如果你还没看过,可以去看看文档。React文档非常棒,目前正在翻译成多种语言。如果你的语言版本尚未完成,不妨尝试贡献一份力量。所有版本文档的 repos 都可以在这里找到。
  • 学习如何使用路由器。我推荐你研究一下Ryan Florence开发的Reach Router,因为它注重易用性。
  • 学习如何处理表单,特别是受控组件。React 确实有另一种处理表单输入的方式,一开始可能会感觉有点别扭。帮自己一个忙,使用formik 吧
  • 学习如何测试 React 组件。Kent C. Dodds有很多关于这个主题的高质量内容,他也是react-testing-library的作者,这是一款非常棒的组件测试工具。我再怎么强调测试的重要性也不为过。他还有一篇介绍这个库的文章。可以去看看。
  • 看看一些状态管理解决方案。Redux最常用的,但一开始可能会让人不知所措,而且它并非唯一的选择。MobX以简洁著称。状态管理的一个好规则是,只在真正需要的时候才使用它。很多时候,它足以在树上的远距离组件之间共享状态。不过Context一开始你完全不需要担心这个问题。
  • 看看 React 生态系统中的 CSS 工具。我非常喜欢 CSS-in-JS 库,因为它在编写样式时赋予你更强大的功能,并且与 React 完美契合,可以将你的样式转化为组件。我非常喜欢styled-components,但Emotion也是一个不错的选择。如果你正在寻找更有说服力的 CSS-in-JS 论据, Max Stoiber最近的文章《我为什么用 JavaScript 编写 CSS》值得一读。

目前来说,这些已经足够了,肯定超出了我写这篇文章的预期😄。不要因为我刚才放了这么多链接而泄气。慢慢来,根据自己的时间尝试每个主题。更重要的是,多联系其他人,提出问题。React 社区非常乐于助人,你可以在任何地方找到帮助(reddit、Twitter 上的开发者、freeCodeCamp以及许多其他平台)。

最后,如果你还在看,请在下方评论区留言,告诉我你学习 React 的历程。我很想听听你的故事。另外,点击关注按钮,就能在我的下一篇文章发布时收到通知。

今天就到这里啦。✌️

封面图片由 Caspar Camille Rubin 在 Unsplash 上提供

鏂囩珷鏉ユ簮锛�https://dev.to/frontendwizard/starting-out-with-react-on-2019-3a0c
PREV
像我五岁一样解释非阻塞 I/O 简介您自己的表工厂非阻塞 I/O 实现的好处最终想法
NEXT
不要对 UI 组件进行快照,要进行断言!问题解决方案结论