Sestavte si aplikaci CRUD v React with Hooks

V Reactu byl představen nový koncept – Hooks. Háčky jsou alternativou tříd. Pokud jste již React používali, budete obeznámeni s jednoduchými (funkčními) komponentami a komponenty třídy .

Jednoduchá komponenta
const Example = () => {
  return <div>I'm a simple component</div>
}
Komponenta třídy
class Example extends Component {
  render() {
    return <div>I'm a class component</div>
  }
}

Mnoho funkcí dostupných třídám – například metody životního cyklu a stav - dosud nebyly dostupné pro jednoduché komponenty. Nový návrh Hooks přidává všechny tyto funkce a další.

Chtěl jsem vyzkoušet Hooks a vidět, jak by aplikace mohla vypadat bez tříd, ale zatím jsem neviděl žádné příklady, tak jsem se rozhodl, že si jeden udělám sám. Vytvořil jsem jednoduchou aplikaci CRUD (create, read, update, delete), která využívá Hooks a no class, a vytvořil jsem tento tutoriál pro každého, kdo se chce naučit, jak je také používat.

Pokud nevíte, jak vytvořit jednoduchou aplikaci CRUD v Reactu, bez ohledu na to, zda používáte třídy nebo háčky, tento článek bude dobrý i pro vás.

  • Zobrazit ukázkovou aplikaci
  • Zobrazit zdroj

Předpoklady

Abyste mohli pokračovat v tomto tutoriálu, budete potřebovat základní znalosti HTML, CSS a JavaScript/ES6. Měli byste také znát základy Reactu, které se můžete naučit v části Začínáme s Reactem.

Cíle

V tomto tutoriálu vytvoříme jednoduchou aplikaci CRUD. Bude mít uživatele a budete moci přidávat, aktualizovat nebo mazat uživatele. Nepoužijeme žádné třídy React a místo toho použijeme stavové háky a háky efektů na funkční komponenty. Pokud se cestou ztratíte, nezapomeňte se podívat na zdroj dokončeného projektu.

Vytvořit aplikaci React

Začneme instalací projektu pomocí aplikace create-react-app (CRA).

npx create-react-app react-hooks

Poté spusťte npm i .

Nyní máte vše připraveno k React.

Počáteční nastavení

Začněme tím, že vymažeme všechny soubory ze základní desky, které nepotřebujeme. Smažte vše z /src složku kromě App.js , index.js a index.css .

Pro index.css , Jen zkopíruji a vložím CSS z Primitive, jednoduchý CSS standard, který jsem vytvořil, protože smyslem této aplikace je pracovat na Reactu a nestarat se o design. Tento standard CSS pouze přidává několik rozumných výchozích nastavení a jednoduchou mřížku, abychom mohli začít s prototypováním.

V index.js , zjednodušíme to odstraněním odkazů na 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'))

A v App.js , vyrobím jednoduchou funkční komponentu pro App místo třídy.

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

Nyní máme počáteční nastavení a kostru aplikace.

Stát vs. Hook State

Pokud se podíváme na velmi jednoduchý příklad komponenty třídy se stavem a funkční komponenty se stavem Hook, můžeme vidět podobnosti a rozdíly. Se stavem třídy získáte jeden objekt hlavního stavu, který aktualizujete pomocí metod třídy a setState() .

Udělám nějaký rychlý příklad kódu, jako by to byla knihovna a vy máte knihy, které mají stav.

Příklad stavu komponenty třídy
class App extends Component {
  initialState = {
    title: '',
    available: false,
  }

  state = initialState

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

Se stavem Hook existuje getter a setter pro každý typ stavu (můžete jich mít kolik chcete) a místo metod samozřejmě vytváříme funkce.

Příklad stavu zavěšení
const App = () => {
  const initialBookState = {
    title: '',
    available: false,
  }

  const [book, setBook] = useState(initialBookState)

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

Nebudu se pouštět hluboko do zdůvodnění háčků vs. komponent třídy, protože vše si můžete přečíst v úvodu React's Hooks. Jen vám ukážu, jak s nimi pracovat, abyste vytvořili funkční aplikaci.

Nastavení zobrazení

První věc, kterou uděláme, je vytvořit nějaká ukázková data a tabulku pro jejich zobrazení pro zobrazení. Vytvořte nový adresář s názvem tables v src a soubor v rámci s názvem UserTable.js . Vyrobíme kostru pro stůl.

tables/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

Nyní stačí importovat soubor a přidat nový komponent.

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

Přinesme nějaká náhodná fiktivní data a useState import z 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

Rekvizity fungují stejně jako předtím. Provedeme mapování uživatelských dat, kterými jsme odeslali, a zobrazíme vlastnosti každého uživatele, nebo zobrazíme zprávu, pokud žádní uživatelé nejsou. Tlačítka úprav a smazání zatím nejsou k ničemu připojena, takže nic nedělají.

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

K tlačítkům editace a mazání se dostaneme za chvíli. Nyní, když je základní zobrazení nastaveno, pojďme spustit funkci přidávání.

Přidání nového uživatele

Nastavíme formulář pro přidání nového uživatele.

Úplně první věc, kterou můžeme udělat, je vytvořit skutečnou funkci, která přidá nového uživatele do stavu. Máme setUsers fungovat automaticky od useState , takže to použijeme k aktualizaci stavu uživatele.

Protože nepoužíváme skutečné API a databázi, která by pravděpodobně měla automaticky se zvyšující ID, budu ID nového uživatele zvyšovat ručně. Tato funkce bude trvat user objekt jako parametr a přidejte je do users pole objektů. ...users kód zajišťuje, že všichni předchozí uživatelé zůstanou v poli.

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

K tomu vytvoříme komponentu, takže budu pokračovat a přidám odkaz na komponentu nahoře a vložím komponentu pod záhlaví "Přidat uživatele". Můžeme předat addUser() skrz jako rekvizitu. Ujistěte se, že nezahrnujete závorky, když je předáváme jako odkaz - <AddUserForm addUser={addUser} /> , nikoli <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

Nyní musíme vytvořit formulář, který můžete použít k přidání nového uživatele. Vytvořme forms podadresář se souborem uvnitř s názvem 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

Právě teď je formulář prázdný a nemůžete do něj přidávat žádné hodnoty kvůli našim prázdným hodnotovým řetězcům, ani tlačítko Odeslat nic nedělá.

Stejně jako předtím budeme chtít vytvořit určitý stav, kromě toho, že tento stav bude pouze dočasný, abychom mohli sledovat, co je aktuálně ve formuláři pro přidání uživatele.

Vytvořím počáteční stav s těmito prázdnými hodnotami a nastavím stav uživatele na prázdné hodnoty. Mít počáteční stav v proměnné je užitečné, protože po odeslání formuláře jej můžeme vrátit na počáteční, prázdnou hodnotu.

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

Nyní vytvoříme funkci pro aktualizaci stavu ve formuláři. event vždy je předán na jakékoli on událost v DOM, takže to uvidíte jako parametr funkce. Struktura objektu nám umožní snadno získat name (klíč) a value z formuláře. Nakonec nastavíme uživatele podobně jako na App komponentu, kromě toho, že k dynamickému nastavení názvu používáme vypočítané názvy vlastností (pomocí [name] ) a hodnotu.

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

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

Nyní extrahujeme hodnoty z objektu state a odkazujeme na naši funkci v onChange událost.

<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>

Poslední věcí, o kterou se musíte postarat, je odeslání formuláře zpět na App komponent. Jak jsme předali funkci s props , použijeme pro přístup k funkci rekvizity. Napíšu onSubmit a zabráníme spuštění výchozího odeslání formuláře. Přidal jsem malý kousek ověření, abych se ujistil, že nelze odeslat prázdné hodnoty, a poslal jsem uživatele do funkce add. Nakonec používám setter k resetování formuláře na jeho počáteční hodnotu po úspěšném odeslání.

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

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

Naštěstí je tento kód docela jednoduchý, protože se nemusíme starat o asynchronní volání API.

Zde je naše úplné AddUserForm komponenta.

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

Skvělé.

Smazání uživatele

Dalším, kterým se budeme zabývat, je smazání uživatele, což je nejjednodušší funkce, o kterou se lze postarat.

Pod addUser v App.js , vytvoříme deleteUser , který vezme ID uživatele a odfiltruje je z pole uživatelů.

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

Tuto funkci předáme přes rekvizity do UserTable .

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

Nyní vše, co musíme udělat v UserTable.js Ujistěte se, že tlačítko delete tuto funkci vyvolá.

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

Nyní můžete odstranit některé nebo všechny uživatele.

Aktualizace uživatele

Posledním kouskem skládačky je zavedení možnosti aktualizovat stávající uživatele. Bude to podobné jako při přidávání uživatele, s tím rozdílem, že budeme muset být schopni identifikovat, který uživatel je upravován. V komponentách třídy bychom použili componentDidUpdate k dosažení tohoto cíle, ale nyní použijeme Effect Hook . Efektový hák je jako componentDidMount a componentDidUpdate kombinované.

Způsob, jakým to budeme strukturovat, je, když je pro uživatele vybrána akce Upravit, formulář „Přidat uživatele“ se stane formulářem „Upravit uživatele“ a bude předem vyplněn daty od vybraného uživatele. Můžete buď zrušit režim úprav, nebo odeslat změnu, která aktualizuje vybraného uživatele a ukončí režim úprav.

Pojďme začít. V App.js , první věc, kterou budeme chtít udělat, je určit, zda je režim úprav zapnutý nebo ne. Začne to jako false.

App.js
const [editing, setEditing] = useState(false)

Protože nevíme, kdo je upravován, dokud není vybrán, vytvoříme pro formulář počáteční prázdný stav, jako jsme to udělali u formuláře pro přidání.

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

Budeme chtít způsob, jak zjistit a aktualizovat, kdo je aktuální uživatel, který se upravuje, takže tohoto prázdného uživatele použijeme na currentUser stavu.

const [currentUser, setCurrentUser] = useState(initialFormState)

Když je u uživatele vybrána možnost Upravit, měl by zapnout režim úprav a nastavit aktuálního uživatele, což provedeme v tomto editRow funkce.

const editRow = (user) => {
  setEditing(true)

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

Nyní tuto funkci předejte do UserTable jako jsme to udělali s deleteUser .

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

Více než za UserTable.js , posíláme user objekt přes.

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

Nyní máme všechna nastavení – je zde přepínač režimu úprav a tlačítko, které přepne aktuálního uživatele do stavu při přepnutí přepínače režimu úprav.

Pojďme vytvořit skutečnou funkci, která bude volána při odeslání editačního formuláře. Na rozdíl od mazání (které odfiltruje uživatele podle ID) nebo přidání (které připojí uživatele k poli), musí funkce aktualizace mapovat pole a aktualizovat uživatele, který odpovídá předávanému ID.

To znamená, že vezmeme dva parametry – aktualizovaný objekt uživatele a id – a použijeme ternární operaci k mapování uživatelů a nalezení toho, kterého chceme aktualizovat.

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

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

Potřebujeme jen vytvořit samotný formulář pro úpravy.

Vytvořte forms/EditUserForm.js . Většina z toho bude stejná jako formulář pro přidání. Jediný rozdíl je zatím v tom, že stav nastavíme přímo z currentUser přes rekvizity. K dispozici je také tlačítko Storno, které jednoduše vypne režim úprav.

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

Nyní musíme upravit formulář do App.js a také vytvoření přepínače pro zobrazení formuláře pro přidání nebo úpravu.

Nejprve vložte komponentu.

App.js
import EditUserForm from './forms/EditUserForm'

Poté vytvořte přepínač. Použijeme ternární operaci ke kontrole, zda editing stav je pravdivý nebo ne. Pokud je pravda, zobrazit formulář pro úpravy. Pokud je hodnota false, zobrazte formulář pro přidání. Ujistěte se, že jste předali všechny funkce, které jsme vytvořili, do editační komponenty.

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>

Dobře, takže v tomto okamžiku kliknutím na tlačítko Upravit byste měli přepnout režim úprav a měli byste být schopni aktualizovat uživatele. Ale skončili jsme?

Použití efektového háku

Pokud si s tím trochu pohrajete, všimnete si problému. Vlastně dvě. Pokud začnete upravovat jednoho uživatele, pak se pokusíte přepnout na jiného uživatele, nic se nestane. Proč? Komponenta je již otevřená, a přestože se stav nadřazené položky změnil, není registrována až do rekvizit.

Zde přichází na své místo Effect Hook. Chceme nechat EditUserForm komponenta ví, že se rekvizity změnily, což bychom udělali dříve s componentDidUpdate .

Prvním krokem je zavést useEffect .

EditUserForm.js
import React, { useState, useEffect } from 'react'
EditUserForm.js
useEffect(() => {
  setUser(props.currentUser)
}, [props])

V Effect Hook vytváříme funkci zpětného volání, která aktualizuje user stav s novou rekvizitou, která se posílá. Předtím jsme potřebovali porovnat if (prevProps.currentUser !== this.state.currentUser) , ale s Effect Hook můžeme předat [props] aby věděl, že sledujeme rekvizity.

Pokud se nyní pokusíte změnit uživatele, kterého upravujete, bude to fungovat správně!

Řekl jsem, že zde byly dva problémy a dalším problémem je, že můžete odstranit uživatele, když je aktuálně upravován. Tento problém můžeme vyřešit přidáním setEditing(false) na deleteUser funkce v App.js .

A to je vše. Máme kompletní aplikaci CRUD využívající háky React State a Effect.

Závěr

Nepokryl jsem do hloubky všechny případy použití pro Hooks nebo všechny funkce, ale pokusil jsem se poskytnout funkční příklad kompletního, i když jednoduchého programu React. Úplné často kladené otázky týkající se všech věcí souvisejících s háčky naleznete v části Nejčastější dotazy týkající se háčků.

Pokud jste se někde po cestě ztratili, nezapomeňte se podívat na demo a zdroj.

  • Zobrazit ukázkovou aplikaci
  • Zobrazit zdroj

Další věc, kterou byste měli hledat, je Suspense API.