使用 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 的又一大优势。你的应用被拆分成多个小块,它们可以协同工作,并抽象出其内部结构。
我们在这里使用了h1
和div
,但是您放在那里的任何内容都将是输出,因此您也可以使用 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 将成为我们创建组件的默认方式。
我们可以使用useState
fromReact
并传入 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
。这样,你就可以访问组件的生命周期方法,例如componentDidMount
、componentDidUpdate
、等。你还可以实例化并使用它进行更改。componentShouldUpdate
componentWillUnmount
state
this.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