让我们通过构建食谱搜索应用程序来学习 React Hooks 和 Context API
我们将要构建的最终版本
React Hooks
这是一篇关于理解和的初学者教程Context API
。实际上,这更像是试图向我自己和感兴趣的读者解释这些概念。本文将分为两部分,第一部分重点介绍的基础知识hooks
。另一部分将重点介绍更高级的用例和Context API
。我们将实现搜索功能,并将其转换为使用来Context API
管理状态并避免props drilling
。
更新:第二部分已上线
我们要怎样学习?
我们将对比一下用类组件和 来处理相同状态逻辑的差异React Hooks
。我们将构建一个食谱搜索应用程序来巩固概念,之后我们会喝一瓶红酒来庆祝😃。我相信摆弄代码是最好的学习方式。
我们将要构建的最终版本
我们将构建一个简单的 food2fork 网站克隆版本。这不会是完整版,但我们会使用他们的 API 来获取排名前 30 的食谱。添加一些我们自己的 UI 逻辑来调整状态,看看如何处理它们hooks
。
先决条件
- React 基础知识
- JavaScript ES6 基础知识 [数组方法、解构等]
那么什么是 React Hook?
首先,什么是 React Hook?来自文档
什么是 Hook? Hook 是一种特殊的函数,可以让你“钩住” React 的功能。例如,useState 就是一个 Hook,可以让你向函数组件添加 React 状态。
简单来说,钩子允许我们“钩住”特定的 React 功能。例如useState
,顾名思义,钩子可以帮助我们在 React 中使用状态功能,而在其他情况下,例如在函数组件中,状态功能是无法使用的。我们将通过构建食谱搜索应用程序来详细解释其语法用法等。
设置
我们将使用 create-react-app 来启动应用程序。我已经创建了一个包含 create-react 应用基本框架的 repo,方便我们快速上手。只需克隆即可继续操作。运行以下命令:
git clone https://github.com/olajohn-ajiboye/Blog-React-Hook-Tutorial.git
cd Blog-React-Hook-Tutorial
回到我们将要构建的应用。我们将使用 food2fork API 来获取并搜索 30 个热门食谱列表。但是,该 API 每天的查询次数有限。为了方便本教程,我创建了精确的 JSON 响应。该响应将从此处提供,这样我们就不会过于频繁地访问他们的服务器。
让我们在src
文件夹中创建一个组件文件夹,用于存放应用可能用到的不同组件。想象一下,我们会有一个组件显示每个菜谱Recipe
,一个RecipeList
组件渲染菜谱列表,以及一个RecipeSearch
组件和RecipeDetails
组件显示每个菜谱的附加详细信息Recipe
。所有组件都将是函数式组件,因为使用钩子是本教程的重点。
如果你所有操作都正确,你应该会得到如下所示的文件夹结构。你也可以从此处second
克隆仓库中的分支,跳转到本教程的此处。
如果你还没有安装,我推荐你安装一个扩展,那就是。它允许你输入简写来获取一些 React 代码片段,从而加快你的 React 开发速度。你可以在这里ES7 React/Redux/GraphQL/React-Native snippets
了解更多信息。
让我们Hooked
为什么还要 Hooks?
在任何前端应用程序中,最常做的事情之一就是获取和显示数据,并操作显示以获得出色的用户体验。React 也不例外。状态的常见用例之一是存储来自 API 调用的数据。之前hooks
,如果您需要在应用程序中使用state
任何类型的组件,则必须使用类组件。您还需要在生命周期内异步获取数据。对于许多人来说,这不是一个大问题,但 React 团队认为这会导致组件逻辑的紧密耦合。此外,在更复杂的应用程序中,很难重用状态逻辑。不要轻信我的话,只需阅读此处componentDidMount
Hooks 的动机即可。
让我们先看看如何从我在经典组件中创建的 REST API 中获取数据,然后再讨论如何使用hooks
import React, { Component } from 'react'
import RecipeList from './components/RecipeList
export default class test extends Component {
constructor(props) {
super(props)
this.state = {
apiResponse: [],
}
}
componentDidMount() {
fetch(`https://api.myjson.com/bins/t7szj`)
.then(data => data.json)
.then(apiResponse => this.setState({ apiResponse }))
}
render() {
return (
<div>
<RecipeList recipes={this.state.recipes}>
</div>
)
}
}
让我们看一下带有 Hook 和 Effect 的相同代码,然后进行解释
import React, { useState, useEffect } from 'react';
import RecipeList from './components/RecipeList
function App() {
const url = useState(`https://api.myjson.com/bins/t7szj`)
const [recipes, setRecipes] = useState([])
const fetchRecipe = async () => {
const recipeData = await fetch(url)
const { recipes } = await recipeData.json()
setRecipes(recipes)
}
useEffect(() => {
fetchRecipe()
})
return (
<div className="App">
<RecipeList recipes={recipes}>
</div>
);
}
export default App;
有几件事显而易见,我们从 导入了 useState 和 useEffect react
。这些是暴露给我们的 API,使我们能够使用React Hooks
。HookuseState
接受初始状态。在上面的示例中,我们将其初始化为一个空数组。我们希望用 API 调用中的数据填充该数组。这相当于我们类组件中的以下代码。
this.state = {
apiResponse: [],
}
此外,它useState
还会返回一对值给我们。它们是当前状态和一个用于更新状态的函数。这样我们就可以从使用状态中返回。这就是我们在应用程序中[currentState, setStateFunction]
编写的原因。其中,是一个用于保存配方数据的数组。是使我们能够更新状态的函数,这相当于在类组件中。const [recipes, setRecipes] = useState([])
recipes
setRecipe
this.setState
语法可能看起来令人困惑,这些并非React特有的语法,而是普通的 ES6 JavaScript。这被称为解构。由于useState
返回一对值,我们将其解构为一个数组。我们为它们选择的名称不会影响它们的行为,将它们命名为 只是一种良好的习惯[name of your state, set+name of state]
,因此我们有:
const [recipes, setRecipes] = useState([])
如果您需要一些关于解构的复习或入门知识,我在这里写了一些相关内容。
为了充分理解这里发生的事情,我们需要注意的另一个 JavaScript 特性是closures
。由于,我们可以在函数内部的任何地方Javascript closure
访问解构变量。因此,在函数内部以及 内的任何地方,我们都可以使用或任何其他变量,而无需调用它等等。useState
fecthRecipe
component
setRecipe
this.setRecipe
就本教程而言,作用域的简单定义
closures
是,它使我们能够访问外部(封闭)函数的变量(作用域链)及其返回值。
让我们快速将 props 传递给组件,并设置它们以显示 Recipe 列表。由于这不是一个Hook
特定的功能,我将跳过它。您可以在这里找到到目前为止的更新仓库。我还添加了样式以加快速度。更新后的仓库位于仓库third/hook-in-app.js
的分支上。
使用useState
此时,您的App.js
代码应该如下所示,我们只是将状态中的食谱数组作为 传递recipes props
给RecipeList
组件。注意,我还添加了一个加载状态,useState
并在数据完全获取后将其设置回false
。这是使用多个状态的第一个示例。
import React, { useState, useEffect } from 'react';
import RecipeList from './components/RecipeList'
import RecipeDetails from './components/RecipeDetails'
function App() {
const url = `https://api.myjson.com/bins/t7szj`
const [recipes, setRecipes] = useState([])
const [loading, setLoading] = useState(true)
const fetchRecipe = async () => {
const recipeData = await fetch(url)
const { recipes } = await recipeData.json()
setRecipes(recipes)
setLoading(false)
}
useEffect(() => {
fetchRecipe()
})
return (
<div>
{loading ? <h1 className="text-center">...loading</h1> : <RecipeList recipes={recipes} />}
<RecipeDetails></RecipeDetails>
</div>
);
}
export default App;
接下来,让我们转到RecipeList
组件并看看我们有什么。
这里我们只是从父组件接收了recipes
作为 传递过来的,并立即执行了它——参见第 5行。然后我们对其进行了映射,将每个配方作为传递给组件。这里没什么特别有趣的。prop
App
destructured
Recipe
prop
import React from 'react'
import Recipe from './Recipe'
import RecipeSearch from './RecipeSearch'
export default function RecipeList({ recipes }) {
return (
<>
<RecipeSearch></RecipeSearch>
<div className="container my-5">
<div className="row">
<div className="col-10-mx-auto col-md-6 text-center text-uppercase mb-3">
<h1 className="text-slaned text-center">Recipe List</h1>
</div>
</div>
<div className="row">
{recipes.map(recipe => {
return <Recipe key={recipe.recipe_id} recipe={recipe} />
})}
</div>
</div>
</>
)
}
现在到了有趣的部分。
在组件内部Recipe
,我添加了一些states
足够简单易懂的实现。我们将尝试逐行逐行地讲解我们正在做的事情,以及我们如何用 处理状态逻辑useState hook
。你的组件中应该包含以下内容Recipe
。
import React, { useState } from 'react'
export default function Recipe({ recipe }) {
const { image_url, publisher, title, recipe_id } = recipe
const [showInfo, setShowInfo] = useState(false)
const [recipeDetails, setRecipeDetails] = useState([])
const { ingredients, social_rank } = recipeDetails
const handleShowInfo = async (e) => {
const { id } = e.target.dataset
const response = await fetch(`https://www.food2fork.com/api/get?key=7cdab426afc366070dab735500555521&rId=${id}`)
const { recipe } = await response.json()
setRecipeDetails(recipe)
setShowInfo(!showInfo)
}
return (
<>
<div className="col-10 mx-auto col-md-6 col-lg-4 my-3">
<div className="card">
<img src={image_url} alt="recipe" className="img-card-top" style={{ height: "14rem" }} />
<div className="card-body text-capitalize">
<h6>{title}</h6>
<h6 className="text-warning">
Provided by: {publisher}
</h6>
</div>
<div className="card-footer">
<button type="button" style={{ margin: `13px` }} className="btn btn-primary text-center" data-id={recipe_id} onClick={handleShowInfo}>More Info</button>
{showInfo &&
<button key={recipe_id} type="button" style={{ margin: `13px` }} className="btn btn-success text-center font-weight-bold" >{social_rank}</button>}
{showInfo ?
ingredients.map((i, index) => {
return <ul key={index} className="list-group">
<li className="list-group-item" >{i}</li>
</ul>
})
: null}
</div>
</div>
</div>
</>
)
}
让我们理解一下上面的代码。和之前一样,我们从它的父组件(也就是)中接收了 arecipe
作为 a ,然后立即在函数参数中对其进行了解构。接下来,我们进一步解构了对象中我们想要使用的部分。我知道对象包含什么,因为我已经测试过 API,所以这并不算什么魔法。这相当于下面的代码:prop
RecipeList
recipe
recipe
export default function Recipe(props) {
const recipe = this.props.recipe
const { image_url, publisher, title, recipe_id } = recipe
}
现在来看看一些有用的状态。如果你检查过这个应用程序,你会发现,当我们点击“更多详情”按钮时,我们会获得与该菜谱相关的额外信息,特别是list of ingredient
和social rating
。看一下上面的动图来复习一下。所以我们需要某种状态来处理所需的用户交互。
想一想,我们需要一种方法来切换是否显示更多信息。我们还需要一种方法来获取特定菜谱的信息。然后,所需的结果将存储在某种状态中。瞧,我们已经确定了至少两种状态。因此,在我们的应用程序中,我们有showInfo
和recipeDetails
状态。
利用我们掌握的信息,我们可以用它来useState Hook
解决这个问题。
- 首先,我们声明
showInfo
状态和设置 showInfo 的函数setShowInfo
(相当于this.SetState
)。我们将值设置为false
- 其次,我们声明
recipeDetails
和setRecipeDetails
。我们将值设置为空数组[]
。
希望这足够简单,我们已经设置了初始状态。并准备好使用 和 来处理状态setShowInfo
变化setRecipeDetails
。
转到handleShowInfo
函数。这是一个async
基本上用于获取数据的函数。它还处理状态变化以显示或不显示信息。让我们逐行分解它。
由于我们打算handleShowInfo
在点击按钮时调用,所以我们可以访问事件对象。在按钮内部,我们将 设置recipe_id
为data-attribute
。这使我们能够获取id
特定菜谱的 。然后,在 内部,我们通过从 属性中提取 来handleShowInfo,
获取。由于我们需要获取更多信息,因此需要使用 发出请求。这是我们接下来要做的事情,然后等待响应。然后,我们将值转换为并将值存储在 中。id
event.target
HTTP
id
json
const recipe
*注意:*您可能需要从food2fork获取 API 密钥。当前密钥可能超出限制。
我们得到的响应recipe
是插入到 内部的,setRecipeDetails
用作 的更新器recipeDetails
。此时,我们只是将 的状态设置recipeDetails
为数组响应变量recipe
。这相当于
this.setState{
recipedDetails: recipe
}
另外,我们将 的值设置showInfo
为与之前相反的值。这就是每次点击按钮时都会产生切换效果的原因。这相当于。
this.setState{
showInfo: !showInfo
}
就是这样,在返回结果中,jsx
我们根据按钮点击的状态有条件地渲染了信息showInfo
。我们还额外映射了成分数组,以将其作为附加信息显示。
这篇非常基础的介绍到此结束hooks
,或许有些过于简化。在本系列的下一篇中,我们将更详细地探讨钩子,然后学习Context API
……
希望你喜欢这篇介绍。欢迎提供反馈。敬请期待下次更新,期待很快与你见面。谢谢!
文章来源:https://dev.to/mongopark/let-s-learn-react-hooks-and-context-api-by-building-a-recipe-search-app-39pc