React系列教程(3)使用React Hooks开发一个增删改查App

豆约翰习惯将掌握某一技术分为5个层次:初窥门径,小试牛刀,渐入佳境,得心应手,玩转自如

本篇属于React框架中的第1层次即初窥门径

本文翻译自:
https://www.taniarascia.com/crud-app-in-react-with-hooks/

在React- Hooks中引入了一个新概念。钩子是类的替代方法。如果您以前使用过React,那么您将熟悉简单的(功能性)组件类组件

简单组件

const Example = () => {
  return <div>I'm a simple component</div>
}

类组件

class Example extends Component {
  render() {
    return <div>I'm a class component</div>
  }
}

直到现在,类的许多可用功能(例如生命周期方法状态)才对简单组件可用。新的Hooks提案添加了所有这些功能以及更多功能。

我想尝试一下Hooks,看看没有任何类的应用看起来如何,但是我还没有看到任何示例,所以我决定自己做一个。我创建了一个简单的CRUD(创建,读取,更新,删除)应用程序,该应用程序使用了Hooks,没有使用类,并且为其他想学习如何使用它们的人创建了本教程。

如果您不知道如何在React中制作一个简单的CRUD应用程序,无论您使用类还是钩子,这篇文章也将对您有所帮助。

先决条件

为了遵循本教程,您需要具备HTML,CSS和JavaScript / ES6的基础知识。您还应该了解React的基础知识,可通过阅读React入门来学习。

目标

在本教程中,我们将制作一个简单的CRUD应用程序。它将有用户,您将能够添加,更新或删除用户。我们将不使用任何React类,而是在功能组件上使用状态挂钩和效果挂钩。如果您一路迷路,请务必检查完成项目的来源

创建React应用

我们将从使用create-react-app(CRA)安装项目开始。

npx create-react-app react-hooks

然后运行npm i

现在,您已经准备好使用React。

最初设定

首先,从不需要的样板中清除所有文件。删除一切从/src文件夹除外App.jsindex.jsindex.css

对于index.css,我只是从Primitive复制并粘贴CSS,Primitive是我制作的一个简单CSS样板,因为此应用程序的重点是在React上工作,而不在乎设计。这个CSS样板只是添加了一些合理的默认值和一个简单的网格,因此我们可以开始制作原型。

在中index.js,我们将通过删除对Service Workers的引用来简化它。

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))

在中App.js,我将为而制作一个简单的功能组件,App而不是一个类。

App.js

import React from 'react'

const App = () => {
  return (
    <div className="container">
      <h1>CRUD App with Hooks</h1>
      <div className="flex-row">
        <div className="flex-large">
          <h2>Add user</h2>
        </div>
        <div className="flex-large">
          <h2>View users</h2>
        </div>
      </div>
    </div>
  )
}

export default App

现在,我们有了该应用程序的初始设置和框架。

State vs. Hook State

如果我们看一个非常简单的带有状态的类组件和一个带有Hook状态的功能组件的示例,我们可以看到相同点和不同点。使用类状态,您将获得一个主状态对象,然后使用类和上的方法进行更新setState()

我将快速制作一些示例代码,就好像它是一个图书馆,并且您有带有状态的书一样。

类组件状态示例

class App extends Component {
  initialState = {
    title: '',
    available: false,
  }

  state = initialState

  updateBook = book => {
    this.setState({ title: book.title, available: book.available })
  }
}

有了Hook状态,每种状态类型都有一个getter和setter方法(可以随意设置),并且我们显然创建函数而不是方法。

挂钩状态示例

const App = () => {
  const initialBookState = {
    title: '',
    available: false,
  }

  const [book, setBook] = useState(initialBookState)

  const updateBook = book => {
    setBook({ title: book.title, available: book.available })
  }
}

我不会深入了解钩子与类组件之间的基本原理,因为您可以在React的Hooks简介中了解所有内容。我将向您展示如何与他们一起创建功能实用的应用程序。

设置视图

我们要做的第一件事是为视图创建一些示例数据和一个表格以显示它。创建一个名为tablesin 的新目录src,以及一个名为的文件UserTable.js。我们将制作表格的骨架。

表/UserTable.js

import React from 'react'

const UserTable = () => (
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Username</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Name data</td>
        <td>Username data</td>
        <td>
          <button className="button muted-button">Edit</button>
          <button className="button muted-button">Delete</button>
        </td>
      </tr>
    </tbody>
  </table>
)

export default UserTable

现在,只需导入文件并添加新组件即可。

App.js

import React from 'react'
import UserTable from './tables/UserTable'

const App = () => {
  return (
    <div className="container">
      <h1>CRUD App with Hooks</h1>
      <div className="flex-row">
        <div className="flex-large">
          <h2>Add user</h2>
        </div>
        <div className="flex-large">
          <h2>View users</h2>
          <UserTable />
        </div>
      </div>
    </div>
  )
}

export default App

让我们引入一些随机的虚拟数据和useState从React导入。

App.js

import React, { useState } from 'react'
import UserTable from './tables/UserTable'

const App = () => {
  const usersData = [
    { id: 1, name: 'Tania', username: 'floppydiskette' },
    { id: 2, name: 'Craig', username: 'siliconeidolon' },
    { id: 3, name: 'Ben', username: 'benisphere' },
  ]

  const [users, setUsers] = useState(usersData)

  return (
    <div className="container">
      <h1>CRUD App with Hooks</h1>
      <div className="flex-row">
        <div className="flex-large">
          <h2>Add user</h2>
        </div>
        <div className="flex-large">
          <h2>View users</h2>
          <UserTable users={users} />
        </div>
      </div>
    </div>
  )
}

export default App

Props 的工作原理与以前一样。我们将通过发送的用户数据进行映射,并显示每个用户的属性,如果没有用户,则显示一条消息。编辑和删除按钮尚未连接到任何东西,因此它们不会做任何事情。

UserTable.js

import React from 'react'

const UserTable = props => (
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Username</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
      {props.users.length > 0 ? (
        props.users.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.username}</td>
            <td>
              <button className="button muted-button">Edit</button>
              <button className="button muted-button">Delete</button>
            </td>
          </tr>
        ))
      ) : (
        <tr>
          <td colSpan={3}>No users</td>
        </tr>
      )}
    </tbody>
  </table>
)

export default UserTable

稍后我们将介绍编辑和删除按钮。现在已经设置了基本视图,让我们开始添加功能。

添加新用户

我们将设置表单以添加新用户。

我们可以做的第一件事是创建实际功能,该功能会将新用户添加到状态中。我们setUsers自动提供了来自的功能useState,因此我们将使用它来更新用户状态。

由于我们没有使用可能具有自动递增ID的真实API和数据库,因此,我将手动增加新用户的ID。该函数将一个user对象作为参数,并将它们添加到users对象数组中。该...users代码确保所有先前的用户都保留在数组中。

App.js

const addUser = user => {
  user.id = users.length + 1
  setUsers([...users, user])
}

我们将为此创建一个组件,因此,我将继续在顶部添加对该组件的引用,并将该组件插入“添加用户”标题下。我们可以addUser()通过作为Props 。当我们将其作为参考时,请确保不要包括括号- <AddUserForm addUser={addUser} />而不是<AddUserForm addUser={addUser()} />

App.js

import React, { useState } from 'react'
import UserTable from './tables/UserTable'
import AddUserForm from './forms/AddUserForm'

const App = () => {
  const usersData = [
    { id: 1, name: 'Tania', username: 'floppydiskette' },
    { id: 2, name: 'Craig', username: 'siliconeidolon' },
    { id: 3, name: 'Ben', username: 'benisphere' },
  ]

  const [users, setUsers] = useState(usersData)

  const addUser = user => {
    user.id = users.length + 1
    setUsers([...users, user])
  }

  return (
    <div className="container">
      <h1>CRUD App with Hooks</h1>
      <div className="flex-row">
        <div className="flex-large">
          <h2>Add user</h2>
          <AddUserForm addUser={addUser} />
        </div>
        <div className="flex-large">
          <h2>View users</h2>
          <UserTable users={users} />
        </div>
      </div>
    </div>
  )
}

export default App

现在,我们必须创建一个可用于添加新用户的表单。让我们创建一个forms子目录,其中包含一个名为的文件AddUserForm.js

AddUserForm.js

import React, { useState } from 'react'

const AddUserForm = props => {
  return (
    <form>
      <label>Name</label>
      <input type="text" name="name" value="" />
      <label>Username</label>
      <input type="text" name="username" value="" />
      <button>Add new user</button>
    </form>
  )
}

export default AddUserForm

目前,该表单为空,由于我们的值字符串为空,因此您无法向其中添加任何值,提交按钮也不会执行任何操作。

就像以前一样,我们将要设置一些状态,只是该状态只是临时的,以便跟踪添加用户表单中当前的内容。

我将使用这些空值创建一个初始状态,并将用户状态设置为空值。在变量中具有初始状态很有用,因为在提交表单后,我们可以将其返回为初始的空值。

AddUserForm.js

const initialFormState = { id: null, name: '', username: '' }
const [user, setUser] = useState(initialFormState)

现在,我们将创建一个函数来更新表单中的状态。event总是传递给onDOM中的任何事件,因此您将看到它作为函数的参数。对象解构将使我们能够轻松地从表单中获取name(key)value。最后,我们将像在App组件上一样对用户进行设置,除了这次我们使用计算的属性名称来动态设置名称(使用[name])和值。

const handleInputChange = event => {
  const { name, value } = event.target

  setUser({ ...user, [name]: value })
}

如果您不了解正在传递的内容,请尝试console.log(event)在输入处理功能中进行尝试。

现在,我们从状态对象中提取值,并在onChange事件中引用我们的函数。

<form>
  <label>Name</label>
  <input type="text" name="name" value={user.name} onChange={handleInputChange} />
  <label>Username</label>
  <input type="text" name="username" value={user.username} onChange={handleInputChange} />
  <button>Add new user</button>
</form>

最后要注意的是实际上将表单提交回App组件。当我们使用传递函数时props,我们将使用道具来访问该函数。我将编写一个onSubmit函数,我们将防止触发默认表单提交。我添加了一点验证,以确保不能提交空值,并将用户发送到add函数。最后,成功提交后,我将使用设置器将表单重置为其初始值。

<form
  onSubmit={event => {
    event.preventDefault()
    if (!user.name || !user.username) return

    props.addUser(user)
    setUser(initialFormState)
  }}
>

幸运的是,这段代码非常简单,因为我们不必担心异步API调用。

这是我们的全部AddUserForm内容。

AddUserForm.js

import React, { useState } from 'react'

const AddUserForm = props => {
  const initialFormState = { id: null, name: '', username: '' }
  const [user, setUser] = useState(initialFormState)

  const handleInputChange = event => {
    const { name, value } = event.target

    setUser({ ...user, [name]: value })
  }

  return (
    <form
      onSubmit={event => {
        event.preventDefault()
        if (!user.name || !user.username) return

        props.addUser(user)
        setUser(initialFormState)
      }}
    >
      <label>Name</label>
      <input type="text" name="name" value={user.name} onChange={handleInputChange} />
      <label>Username</label>
      <input type="text" name="username" value={user.username} onChange={handleInputChange} />
      <button>Add new user</button>
    </form>
  )
}

export default AddUserForm
image.png

删除用户

我们要解决的下一个问题是删除用户,这是最简单的功能。

addUser中的下面App.js,我们将创建deleteUser,它将获取用户ID并将其从用户数组中过滤出来。

const deleteUser = id => {
  setUsers(users.filter(user => user.id !== id))
}

我们通过Props将该功能传递给UserTable

<UserTable users={users} deleteUser={deleteUser} />

现在我们要做的UserTable.js就是确保删除按钮调用该函数。

<button onClick={() => props.deleteUser(user.id)} className="button muted-button">
  Delete
</button>

现在,您可以删除部分或全部用户。

更新用户

难题的最后一步是引入更新现有用户的功能。这类似于添加用户,除了我们必须能够识别正在编辑的用户。在类组件中,我们将使用componentDidUpdate生命周期方法来实现这一点,但是现在我们将使用Effect Hook。该Effect Hook就像是componentDidMountcomponentDidUpdate合并。

我们要构造的方式是,当为用户选择“编辑”操作时,“添加用户”表单将变为“编辑用户”表单,并且将使用所选用户的数据预先填充该表单。您可以取消编辑模式,也可以提交更改,这将更新所选用户并退出编辑模式。

让我们开始。在中App.js,我们要做的第一件事是使状态为是否打开编辑模式。它将开始为false。

App.js

const [editing, setEditing] = useState(false)

由于不知道正在编辑的对象,因此我们将为表单创建初始的空状态,就像添加表单一样。

const initialFormState = { id: null, name: '', username: '' }

我们需要一种查看和更新​​正在编辑的当前用户的人的方法,因此我们会将空用户应用于currentUser状态。

const [currentUser, setCurrentUser] = useState(initialFormState)

在用户上选择“编辑”后,它应打开编辑模式并设置当前用户,我们将在此editRow功能中执行此操作。

const editRow = user => {
  setEditing(true)

  setCurrentUser({ id: user.id, name: user.name, username: user.username })
}

现在,只需将该函数传递给UserTable我们就可以了deleteUser

<UserTable users={users} editRow={editRow} deleteUser={deleteUser} />

在中UserTable.js,我们将user对象发送过来。

UserTable.js

<button
  onClick={() => {
    props.editRow(user)
  }}
  className="button muted-button"
>
  Edit
</button>

现在我们已完成所有设置-有一个用于编辑模式的开关,以及一个按钮,该按钮将在翻转编辑模式开关的同时使当前用户进入状态。

让我们创建在提交编辑表单时将调用的实际函数。与delete(通过ID筛选出用户)或add(将用户追加到数组)不同,update函数需要映射到数组,并更新与通过的ID相匹配的用户。

这意味着我们将使用两个参数-已更新的用户对象和id-并且将使用三元操作来映射用户并找到我们要更新的参数。

App.js

const updateUser = (id, updatedUser) => {
  setEditing(false)

  setUsers(users.map(user => (user.id === id ? updatedUser : user)))
}

我们只需要自己制作编辑表单即可。

创建forms/EditUserForm.js。大部分将与添加表单相同。到目前为止,唯一的区别是我们将直接currentUser通过props 设置状态。还有一个取消按钮,可以简单地关闭编辑模式。

EditUserForm.js

import React, { useState } from 'react'

const EditUserForm = props => {
  const [user, setUser] = useState(props.currentUser)

  const handleInputChange = event => {
    const { name, value } = event.target

    setUser({ ...user, [name]: value })
  }

  return (
    <form
      onSubmit={event => {
        event.preventDefault()

        props.updateUser(user.id, user)
      }}
    >
      <label>Name</label>
      <input type="text" name="name" value={user.name} onChange={handleInputChange} />
      <label>Username</label>
      <input type="text" name="username" value={user.username} onChange={handleInputChange} />
      <button>Update user</button>
      <button onClick={() => props.setEditing(false)} className="button muted-button">
        Cancel
      </button>
    </form>
  )
}

export default EditUserForm

现在,我们必须将编辑表单放入App.js,并创建一个切换以显示添加或编辑表单。

首先,引入组件。

App.js

import EditUserForm from './forms/EditUserForm'

然后创建切换。我们将使用三元运算来检查editing状态是否为true。如果为true,则显示编辑表单。如果为false,则显示添加表单。确保将我们创建的所有功能传递给编辑组件。

App.js

<div className="flex-large">
  {editing ? (
    <div>
      <h2>Edit user</h2>
      <EditUserForm
        setEditing={setEditing}
        currentUser={currentUser}
        updateUser={updateUser}
      />
    </div>
  ) : (
    <div>
      <h2>Add user</h2>
      <AddUserForm addUser={addUser} />
    </div>
  )}
</div>

好的,因此此时单击“编辑”按钮应该可以切换编辑模式,并且您应该能够更新用户。但是,我们完成了吗?

使用Effect Hook

如果您稍作尝试,则会注意到一个问题。二,实际上。如果您开始编辑一个用户,然后尝试切换到另一用户,则不会发生任何事情。为什么?好了,该组件已经打开,并且尽管父级上的状态已更改,但尚未注册到props。

这就是Effect Hook的位置。我们想让EditUserForm组件知道道具已经更改,这是我们之前使用所做的componentDidUpdate

第一步是引入useEffect

EditUserForm.js

import React, { useState, useEffect } from 'react'

EditUserForm.js

useEffect(() => {
  setUser(props.currentUser)
}, [props])

在效果挂钩中,我们创建了一个回调函数,该函数user使用正在发送的新道具更新状态。之前,我们需要进行比较if (prevProps.currentUser !== this.state.currentUser),但是有了Effect Hook,我们就可以[props]通过它来告知我们正在观看props。

使用[props]数组类似于使用componentDidUpdate。如果您正在执行类似的一次性事件componentDidMount,则可以传递一个空数组([])。

现在,如果您尝试更改要编辑的用户,它将可以正常工作!

我说这里有两个问题,另一个问题是您可以在当前正在编辑用户的同时删除它。我们可以通过添加setEditing(false)到中的deleteUser函数来解决此问题App.js

就是这样。我们有一个完整的CRUD应用程序,利用React State和Effect Hook

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342