Lekce 9. - React Hooks: Praktický průvodce pro začátečníky

09.04.2021 Programování #react #programování

Úvodní lekce do React Hooks, ve které se zinovuje vyvíjena aplikace ToDos-


V této lecki se naučíte, jak napsat plně funkční aplikaci bez použití syntaxe třídy. Začnete spravovat stav a další prvky třídy v komponentě funkce. To je možné díky zavedení Hooks, které přišlo ve verzi 16.8 React.

Výhodou je, že budete moci napsat čitelnější, stručnější a jasnější kód.

Co jsou React Hooks?

React Hooks (představený v Reactu ve verzi 16.8) jsou funkce JavaScriptu, které umožňují stavět komponentu Reactu POUZE s funkcí.

To má vyřešit některé komplikace spojené s logikou třídy.

Jedním z problémů, s nimiž se často setkáváme u metody životního cyklu třídy, je to, že související logiku často rozdělujeme do různých metod. Viděli jsme to v naší aplikaci todos, kde máme objekt localStorage přítomný v metodách připojení i aktualizace životního cyklu.

Nyní, když se vaše aplikace zvětší, zjistíte, že metoda životního cyklu třídy může také obsahovat nesouvisející logiku. To může aplikaci přidat další vrstvu složitosti.

Nyní můžeme oddělit logiku podle jejich účelu nebo podle toho, jak souvisí, a nikoli na základě metod životního cyklu.

React poskytuje několik Hooků pro správu většiny případů použití logiky třídy. Také přichází s lepším způsobem, jak vytvořit vlastní Hooky, kdykoli chceme znovu použít stavovou logiku mezi komponentami.

Zde prozkoumáme některé základní Hooks a uvidíme, jak můžeme replikovat logiku třídy pomocí nich.

Příprava souborů projektu

Pro uchování dosavadního stavu aplikace proveďte její kopii jako zálohu pro referenční účely.

Přejděte do složky src a vytvořte dvě nové složky s názvem classBased functionBased. Rozdělte soubory (kromě index.js) ve složce v src a vložte je do každé z nově vytvořených složek.

Struktura složky src by měla vypadat takto:

└── src
    ├── classBased
    │   ├── components
    │   └── App.css
    ├── functionBased
    │   ├── components
    │   └── App.css
    └── index.js

Nyní přejděte dovnitř src/index.js a aktualizujte v TodoContainer CSS import a odkazujte na novou cestu k souboru.

import TodoContainer from "./functionBased/components/TodoContainer"

//stylesheet
import "./functionBased/App.css"

Uložte soubor a zkontrolujte, zda se vaše aplikace správně vykresluje.

Od této chvíle budeme pracovat pouze v adresáři src/functionBased.

Stavové chování v adresáři komponent v současné době řídí tři komponenty. InputTodoTodoItemTodoContainer.

Začneme s komponentou, kde je spravována pouze logika stavu (a ne logika životního cyklu). Pojďme se tedy podívat na soubor InputTodo.js.

V tuto chvíli máme objekt state (kde vlastnosti přiřadíme výchozí prázdný řetězec title) a metody třídy na nejvyšší úrovni komponenty.

Vymažeme v tomto souboru celý kód a jednoduše přidáme následující počáteční kód, abychom zabránili rozbití stránky:

import React from "react"

const InputTodo = () => {
  return <div></div>
}

export default InputTodo

Toto je první konverze. Všimněte si, že nyní používáme místo třídy funkci.

Používání React Hooks useState

Hook useState() umožňuje přidat místní stav v komponentě funkce. Nezapomeňte, že v komponentě třídy, jak je vidět výše, jsme definovali data místního stavu v objektu state a poté k nim přistupujeme pomocí this.state. Podobně jsou aktualizovány pomocí metody this.setState.

Abychom to replikovali ve funkční komponentě, nejprve importujeme hook useState z modulu react takto:

import React, { useState } from "react"

const InputTodo = () => {
  console.log(useState("hello"))
  return <div></div>
}

export default InputTodo

Uložte soubor a otevřete konzolu svého prohlížeče DevTools.

usestate-console 

Hook useState přijímá pouze jeden argument, který je počáteční stav. Ale na rozdíl od přístupu založeného na třídách nemusí být počáteční stav objekt. Může obsahovat pole, číslo a řetězec (jak je vidět výše). Jak uvidíte později, nejste omezeni na jednu vlastnost stavu jako v případě komponenty třídy.

Zde si zvyknete definovat samostatné stavy pomocí useState().

Tento stav nyní vrací pole obsahující pouze dvě položky. První položka je předaná aktuální hodnota (v našem případě ahoj ). Toto je proměnná místního stavu.

Druhá položka je funkce, která nám umožní aktualizovat aktuální hodnotu.

OK. K získání těchto položek z pole můžeme použít destrukci pole JavaScriptu.

Například,

const [title, setTitle] = useState("hello")



Zde jsme deklarovali lokální stavovou proměnnou nazvanou title (kterou můžete pojmenovat, jak chcete). Toto udržuje aktuální stav, tj. Hello. Poté funkce, kterou k aktualizaci stavu jsme pojmenovali setTitle. Je to podobné this.state.title, a this.setState v naší třídě komponenty.

Vezměte prosím na vědomí, že.

Než budeme pokračovat a použijeme tyto háčky, pojďme se podívat na pravidla, která použití řídí.

Musíte mít na paměti, že zavoláte pouze háčky na nejvyšší úrovni vaší funkční komponenty nebo z vlastních háčků. Ne uvnitř smyčky, podmínky nebo běžné funkce. Tím je zajištěno, že vaše logika komponenty je viditelná pro React a je volána ve stejném pořadí na každém vykreslení.

To je důležité, aby React věděl, jak správně zachovat stav Hooků mezi více komunikacemi. Pokud je váš projekt nastaven pomocí aplikace Create React, toto pravidlo se automaticky vynucuje. Jinak byste k jeho vynucení museli zahrnout plugin ESLint s názvem eslint-plugin-react-hooks .

Zpět na náš kód, pojďme aktualizovat komponentu, abyste měli:

import React, { useState } from "react"

const InputTodo = props => {
  const [title, setTitle] = useState("")

  const onChange = e => {
    setTitle(e.target.value)
  }

  const handleSubmit = e => {
    e.preventDefault()
    if (title.trim()) {
      props.addTodoProps(title)
      setTitle("")
    } else {
      alert("Please write item")
    }
  }

  return (
    <form onSubmit={handleSubmit} className="form-container">
      <input
        type="text"
        className="input-text"
        placeholder="Add todo..."
        value={title}
        name="title"
        onChange={onChange}
      />
      <button className="input-submit">Submit</button>
    </form>
  )
}

export default InputTodo

Uložte soubor. Měli byste vidět vstupní pole zpět v rozhraní. Vyzkoušejte a vše by mělo fungovat perfektně.

Co se děje v kódu?

Nezapomeňte, že ve verzi třídy jsme deklarovali objekt state, kde jsme přiřadili pár klíč – hodnota. Ale teď to děláme pomocí React Hook useState.

Tady, namísto použití this.state pro přístup k aktuálnímu stavu hodnotu, jsme jednoduše použít proměnné title. Podobně nyní aktualizujeme stav pomocí druhého prvku vráceného useState.

Jak je vidět na funkci onChange handleSubmit, používáme setTitle místo this.setState používané v komponentě třídy.

Poznámka: klíčové slovo this používané v třídě ve funkci neexistuje.
To platí také pro metody v komponentě třídy ( onChange handleSubmit). Nezapomeňte, že ve funkci nemůžeme použít metody třídy, ale můžeme definovat funkce ve funkci.

Všechno, co jsme zde udělali, bylo převdení metody třídy tak, aby fungovaly přidáním klíčového slova const. S touto jednoduchou změnou můžete volat funkci v rámci JSX bez použití klíčového slova this.

Další oblastí zájmu je metoda onChange. Tato metoda se volá vždy, když se změní vstupní textové pole.

Pokud jste ostražití, možná vás zajímá, proč nepoužíváme metodu e.target.name v onChange, protože ji máme ve verzi třídy. 

Nyní si pozorně přečtěte.

V našem kódu přiřazujeme řetězec proměnné title stavu prostřednictvím useState. Toto je nejjednodušší případ použití háku. Toto nastavení nám umožňuje spravovat vstupní pole při volání funkce. Pokud přidáte další pole, budete muset definovat samostatný Hook useState a funkci pro jeho správu.

Tohle je fajn.

Ale v některých případech byste pracovali s objekty jako stavovými hodnotami. Například můžete pracovat se souvisejícími daty jako v případě polí formulářů a chcete, aby jediný obslužný program sledoval všechna pole.

Mezitím nahraďte součást InputTodo níže uvedeným kódem.

import React, { useState } from "react"

const InputTodo = props => {
  const [inputText, setInputText] = useState({
    fName: "",
    lastName: "",
  })

  const onChange = e => {
    setInputText({
      ...inputText,
      [e.target.name]: e.target.value,
    })
  }

  const handleSubmit = e => {
    e.preventDefault()
    console.log("submitted")
  }

  return (
    <>
      <form onSubmit={handleSubmit} className="form-container">
        <input
          type="text"
          className="input-text"
          placeholder="Add first name"
          value={inputText.fName}
          name="fName"
          onChange={onChange}
        />
        <input
          type="text"
          className="input-text"
          placeholder="Add last name"
          value={inputText.lastName}
          name="lastName"
          onChange={onChange}
        />

        <button className="input-submit">Submit</button>
      </form>
      <h2>{inputText.fName}</h2>
      <h2>{inputText.lastName}</h2>
    </>
  )
}

export default InputTodo

Nyní předáváme objekt jako hodnotu stavu. Všimněte si, jak odkazujeme na každou z hodnot v JSX. Pokud soubor uložíte a začnete do polí přidávat text. Aktualizaci uvidíte v zobrazení při každém stisknutí klávesy.

Nyní pojďme zakomentovat …inputText ve funkci onChange takto:

const onChange = e => {
  setInputText({
    // ...inputText,
    [e.target.name]: e.target.value,
  })
}

Pokud soubor uložíte a pokusíte se do pole znovu přidat text, všimnete si, že se navzájem přepisují.

Co se tedy děje?

Na rozdíl od třídního přístupu. Kdykoli máte objekt jako hodnotu stavu, React nesloučí stav vrácený hákem useState se stavem předané aktualizace.

To znamená, že neslučuje starý a nový stav. Místo toho přepíše celý stav aktuálním.

Ve výše uvedeném případě použití jsme definovali stav takto:

const [inputText, setInputText] = useState({
  fName: "",
  lastName: "",
})

Nyní, když zavoláme funkci aktualizátoru, setInputText a spustili jsme funkci onChange níže:

const onChange = e => {
  setInputText({
    [e.target.name]: e.target.value,
  })
}

Starý stav je nahrazen stavem, který událost spouští.

Chcete-li to opravit, budete muset zkopírovat celé vlastnosti ze starého stavu pomocí operátoru šíření ( ) a přepsat jeho část.

const onChange = e => {
  setInputText({
    ...inputText,
    [e.target.name]: e.target.value,
  })
}

Můžete také jít dále tím, že při nastavování nových hodnot zajistíte nejnovější stav. To je zvláště nutné, pokud nové hodnoty závisí na starém stavu. Například pokud pracujete na zaškrtávacím políčku vstupu.

Tímto způsobem můžete předat zpětné volání aktualizátoru a použít jej jako první argument takto:

const onChange = e => {
  setInputText(prevState => {
    return {
      ...prevState,
      [e.target.name]: e.target.value,
    }
  })
}

S tímto přístupem můžete spravovat několik vstupních polí ve vaší aplikaci pomocí jedné funkce (v tomto případě onChange). Musíte se jen ujistit, že vlastnost name v těchto polích odpovídá tomu, co jste zadali ve stavu.

Když jste prošli celou cestu ze světa založeného na třídách, je použití objektu jako hodnoty stavu něco, co znáte.

Pokud se vám ale nelíbí seskupovat související data, jako je tato, můžete je rozdělit na různá useState. Nezapomeňte však, že k jejich správě budete potřebovat samostatné funkce.

Doufám, že je to jasné?

Zpět k našemu kódu. Jak již bylo zmíněno dříve, pojďme se vrátit k našemu kódu titulu todos a aktualizovat komponentu InputTodo tak, aby pojala více textových polí.

Váš kód by měl vypadat takto:

import React, { useState } from "react"

const InputTodo = props => {
  const [inputText, setInputText] = useState({
    title: "",
  })

  const onChange = e => {
    setInputText({
      ...inputText,
      [e.target.name]: e.target.value,
    })
  }

  const handleSubmit = e => {
    e.preventDefault()
    if (inputText.title.trim()) {
      props.addTodoProps(inputText.title)
      setInputText({
        title: "",
      })
    } else {
      alert("Please write item")
    }
  }

  return (
    <form onSubmit={handleSubmit} className="form-container">
      <input
        type="text"
        className="input-text"
        placeholder="Add todo..."
        value={inputText.title}
        name="title"
        onChange={onChange}
      />
      <button className="input-submit">Submit</button>
    </form>
  )
}

export default InputTodo

Uložte soubor a otestujte svou aplikaci.

Nyní, když jste se naučili, jak spravovat stav v komponentě funkce pomocí Hook useState, pojďme se podívat, jak můžeme replikovat logiku životního cyklu v tomto typu komponenty.

React Hooks useEffect

useEffect (jak název napovídá) je další vestavěný hák, který je volán v komponentě funkce, aby do něj přidal nějaký efekt. Jinými slovy se používá k provádění vedlejších účinků. Můžete si myslet, že useEffect Hook je jako třída componentDidMountcomponentDidUpdate componentWillUnmount jejich kombinace.

Nezapomeňte, že jsme dříve zmínili, jak nám React Hook umožňuje spravovat související logiku společně, místo abychom je rozdělili do různých metod životního cyklu třídy. Uvidíte, jak se to za chvíli uplatní.

Prozatím se zaměřme na soubor TodoContainer.js. Tento soubor spravuje stav a dvě metody životního cyklu componentDidmount() componentDidUpdate().

Chcete-li replikovat jejich příslušnou logiku a plně pochopit Hooks, musíte začít přemýšlet z hlediska efektů, a nikoli z úhlu nahrazení metod životního cyklu třídy.

V souboru začněme převedením logiky stavu na použití háku useState.

Na konci konverze by váš kód měl vypadat takto:

import React, { useState } from "react"
import Header from "./Header"
import InputTodo from "./InputTodo"
import TodosList from "./TodosList"
import { v4 as uuidv4 } from "uuid"

const TodoContainer = () => {
  const [todos, setTodos] = useState([])

  const handleChange = id => {
    setTodos(prevState =>
      prevState.map(todo => {
        if (todo.id === id) {
          return {
            ...todo,
            completed: !todo.completed,
          }
        }
        return todo
      })
    )
  }

  const delTodo = id => {
    setTodos([
      ...todos.filter(todo => {
        return todo.id !== id
      }),
    ])
  }

  const addTodoItem = title => {
    const newTodo = {
      id: uuidv4(),
      title: title,
      completed: false,
    }
    setTodos([...todos, newTodo])
  }

  const setUpdate = (updatedTitle, id) => {
    setTodos(
      todos.map(todo => {
        if (todo.id === id) {
          todo.title = updatedTitle
        }
        return todo
      })
    )
  }

  return (
    <div className="container">
      <div className="inner">
        <Header />
        <InputTodo addTodoProps={addTodoItem} />
        <TodosList
          todos={todos}
          handleChangeProps={handleChange}
          deleteTodoProps={delTodo}
          setUpdate={setUpdate}
        />
      </div>
    </div>
  )
}

export default TodoContainer

Všimněte si, že ještě nezahrnujeme logiku životního cyklu. Za chvíli se na to podíváme.

Uložte soubor a otestujte svou aplikaci.

Funguje to, ale nejsme schopni ukládat data do místního úložiště. Proto po opětovném načtení stránky ztratíte své úkoly. To, jak jsme řekli, čeká na logiku životního cyklu.

Co se děje v kódu?

Podle očekávání jsme začali importem háku useState, abychom mohli spravovat stav. Poté jsme odstranili veškerý výskyt this.state použitý ve verzi třídy. Neplatí pro funkční komponentu.

Podobně se používá setTodos (v každé funkci na nejvyšší úrovni komponenty) k aktualizaci hodnoty stavu jejich příslušné this.setState.

Podívejte se také na to, jak přistupujeme k předchozímu stavu z funkce setTodos v handleChange. Stejně jako v případě metody setState() ve složce třídy máme přístup k předchozímu stavu z funkce předané do setTodos. Nezapomeňte, že je to užitečné, pokud je nový stav vypočítán pomocí předchozího stavu.

Ve verzi třídy ukládáme vstupní položky todos do lokálního úložiště voláním metody componentDidUpdate(). Podobně získáváme uložené položky voláním componentDidmount(). Ale ve funkční komponentě nemůžeme použít tyto metody. Místo toho využijeme useEffect.

Nejprve musíme importovat hák z modulu react. Jděte do souboru TodoContainer.js a aktualizujte jej tak, aby zahrnoval import.

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

Poté přidejte Hook kdekoli nad výpis return a uložte soubor:

useEffect(() => {
  console.log("test run")
})

S tímto jednoduchým doplňkem, pokud znovu načtete rozhraní, měla by se vám zobrazit zpráva protokolu zobrazená v konzole prohlížeče.

Tento hook přijímá funkci jako argument a volitelné pole (zatím je vynechané). Funkce definuje vedlejší efekt, který se má spustit (v našem případě ukládání a čtení položek todos do a z lokálního úložiště) a volitelné pole bude definovat, kdy se má efekt znovu spustit.

Ve výchozím nastavení se tento efekt spustí po každém dokončeném vykreslení. To znamená při prvním vykreslení a po každé změně stavu nebo vlastnosti. Můžete to otestovat pokusem o interakci s aplikací todos, když je konzola otevřená. Zpráva protokolu se zobrazí na každém dokončeném vykreslení.

To se děje proto, že Hook kombinuje různé logiky životního cyklu.

useEffect(() => {
  ...
}, []);

Toto pole umožňuje určit některé závislosti.

Můžete také nechat prázdné, ale POUZE, pokud váš efekt nepoužívá žádnou hodnotu z vykresleného rozsahu. Jinými slovy, efekt nepoužívá hodnoty zevnitř vaší komponenty.

V případě, že v efektu používáte některou z hodnot komponenty (jako vlastnosti, stav nebo dokonce funkce), musíte je přidat jako závislosti do pole.

Tímto způsobem, pokud a pouze v případě, že se některá z hodnot změní mezi opětovným vykreslením, React znovu spustí efekt. Jinak to přeskočí použití efektu.

Takto se podíváme na efekt Hook.

Ano, React přeskočí jakoukoli formu opětovného vykreslení a provede efekt pouze jednou, pokud v poli nezadáte žádnou závislost, takže je prázdná. Toto je synonymum pro komponentu třídy componentDidMount.

Nechte React rozhodnout, kdy provést opakované spuštění. Vaším úkolem je předat matici všech potřebných znaků, tj. závislostí.

Zpět v souboru TodoContainer.js, pojďme aktualizovat Hook tak, aby zahrnoval logiku připojení. Nezapomeňte, že zde chceme získat data z lokálního úložiště, když komponenta připojí obrazovku.

useEffect(() => {
  console.log("test run")

  // getting stored items
  const temp = localStorage.getItem("todos")
  const loadedTodos = JSON.parse(temp)

  if (loadedTodos) {
    setTodos(loadedTodos)
  }
}, [setTodos])

Po přidání logiky zpětného volání jako efektu si všimněte, že předáváme funkci setTodos setteru jako jedinou závislost. Na základě našeho dřívějšího vysvětlení je to jediná hodnota z komponenty, kterou používáme uvnitř efektu.

Ale nikdy se nemění. Proč to tedy přidávat?

To je důvod, proč React říká, že je bezpečné jej vynechat ze seznamu závislostí, protože je zaručeno, že bude stabilní a nikdy se nezmění při opětovném vykreslení.

V případě, že byste na svůj ESLint dostali varování. Jednoduše to přidejte.

To tedy platí:

useEffect(() => {
  console.log("test run")

  // getting stored items
  const temp = localStorage.getItem("todos")
  const loadedTodos = JSON.parse(temp)

  if (loadedTodos) {
    setTodos(loadedTodos)
  }
}, [])

Pokud soubor uložíte a otestujete svou aplikaci, uvidíte v konzole, že efekt se spustí pouze při připojení komponenty.

V tuto chvíli nemáme data v lokálním úložišti k zobrazení, protože jsme nepřidali logiku. Udělejme to hned. Ve verzi třídy kódu se tato logika nachází v metodě componentDidUpdate.

Víme, že se componentDidUpdate spuští, když dojde ke změně stavu nebo vlastnosti.

Pokud si pamatujete, zkontrolovali jsme aktualizaci porovnáním objektu todos pomocí prevState s aktuálním snímkem. Ale tento požadavek je zabudován do háku useEffect.

Z výše uvedeného vysvětlení můžeme určit závislost stavoé proměnné todos. To říká Reactu, aby se znovu spustil (samozřejmě po prvním vykreslení), až se změní stav. React, začne sledovat proměnnou, zda neobsahuje nějaké změny, a poté znovu spustí efekt.

Takhle efekt provádí logiku aktualizace v komponentě funkce.

Pomocí právě zmíněného přístupu přidáme tento kód kamkoli nad výpis return a uložíme soubor.

useEffect(() => {
  // storing todos items
  const temp = JSON.stringify(todos)
  localStorage.setItem("todos", temp)
}, [todos])

Poznámka: Stejně jako useState(), můžeme použít více useEffect().

Jak je vidět v kódu, efekt používá hodnotu todos zevnitř komponenty. Proto jsme to zahrnuli jako závislost. React nyní zkontroloval změny v této hodnotě a znovu spustil efekt. Takto použijeme logiku metody componentDidUpdate.

Jednoduché a přímočaré.

Uložte a otestujte svou aplikaci. Nyní můžeme v lokálním úložišti uchovávat trvale naše úkoly.

Přístup k uloženým úlohám přímo v počátečního stavu

V kódu výše voláme funkci setTodos setter v efektu. Je to podobné jako voláním setState() v metodě componentDidMount. Jak jsme již zmínili dříve v, spustí se další vykreslení. React již řekl, že je to v pořádku, protože k tomu dojde dříve, než prohlížeč aktualizuje zobrazení.

Nicméně poskytneme jednoduchou, ale optimalizovanou alternativu k získání hodnoty a přiřazení ke stavu při připojení komponenty.

Zpět do souboru TodoContainer.js. Vymažte nebo zakomentujte připojovací logiku (tj. Efekt s prázdným polem) a místo toho přidejte tuto funkci.

function getInitialTodos() {
  // getting stored items
  const temp = localStorage.getItem("todos")
  const savedTodos = JSON.parse(temp)
  return savedTodos || []
}

Kód je jasný. Přistupujeme k lokálnímu úložišti a získáváme uložené úkoly nebo jednoduše vracíme zpět prázdné pole.

Pak můžeme tuto funkci přiřadit ke stavové proměnné takto:

const [todos, setTodos] = useState(getInitialTodos())

Uložte soubor a otestujte svou aplikaci.

Provedení logiky componentWillUnmount ve funkční komponentě

Je to snadné. Normálně provádíme vyčištění, jako je zrušení požadavku na síť, zneplatnění časovačů, odebrání posluchačů událostí mimo jiné, aby se zabránilo zahlcení paměti.

Takže když má být komponenta zničena, React volá metodu, aby mohla provést vyčištění.

Hook useEffect funguje trochu jinak.

Tento hák, jak již víme, je ve výchozím nastavení spuštěn při každém vykreslení. Provádí také efekt vyčištění z předchozího vykreslení před dalším cyklem a samozřejmě před odpojením komponenty. To pomáhá předcházet chybám v komponentě.

Podívejme se, jak používat hook useEffect.

Jak se dalo očekávat, zaměříme se na soubor TodoItem.js. Jeho součást obsahuje tuto metodu. Začneme převedením komponenty třídy na funkční bázi.

import React, { useState } from "react"
import styles from "./TodoItem.module.css"

const TodoItem = props => {
  const [editing, setEditing] = useState(false)

  const handleEditing = () => {
    setEditing(true)
  }

  const handleUpdatedDone = event => {
    if (event.key === "Enter") {
      setEditing(false)
    }
  }

  const completedStyle = {
    fontStyle: "italic",
    color: "#595959",
    opacity: 0.4,
    textDecoration: "line-through",
  }

  const { completed, id, title } = props.todo

  let viewMode = {}
  let editMode = {}

  if (editing) {
    viewMode.display = "none"
  } else {
    editMode.display = "none"
  }

  return (
    <li className={styles.item}>
      <div onDoubleClick={handleEditing} style={viewMode}>
        <input
          type="checkbox"
          className={styles.checkbox}
          checked={completed}
          onChange={() => props.handleChangeProps(id)}
        />
        <button onClick={() => props.deleteTodoProps(id)}>Delete</button>
        <span style={completed ? completedStyle : null}>{title}</span>
      </div>
      <input
        type="text"
        style={editMode}
        className={styles.textInput}
        value={title}
        onChange={e => {
          props.setUpdate(e.target.value, id)
        }}
        onKeyDown={handleUpdatedDone}
      />
    </li>
  )
}

export default TodoItem

Uložte soubor.

Všimněte si, že nezahrnujeme logiku odpojení.

Nejprve aktualizujte import tak, aby obsahoval hook useEffect:

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

Poté přidejte následující kód kamkoli nad výpis return.

useEffect(() => {
  return () => {
    console.log("Cleaning up...")
  }
}, [])

Uložte soubor. Otevřete konzolu a zkuste odstranit všechny položky. Měli byste vidět displej zprávy protokolu.

Co se děje?

Kdykoli vrátíte funkci uvnitř hook useEffect a poté nemáte v poli žádnou závislost, efekt se spustí jen jednou a funkce návratu bude volána, když se komponenta chystá odpojit.

Pokud však nezadáme pole, vrátí se efekt zpět na výchozí hodnotu. To znamená, že se provede při každém opětovném vykreslení a provede vyčištění před dalším spuštěním a samozřejmě před odpojením komponenty.

V tomto případě se v naší aplikaci zobrazí zpráva protokolu o každé interakci s položkami úkolů. Ať už přepnete zaškrtávací políčko, upravíte položku, odstraníte nebo zadáte nové položky, zobrazí se v konzole zpráva protokolu.

To jsme nechtěli. Takže jsme museli přidat řadu závislostí. Ale tentokrát nebyla v efektu uvedena žádná hodnota komponenty. Proto je pole prázdné.

V tomto okamžiku máte úplnou kontrolu nad typem komponenty, kterou chcete vytvořit. Nyní můžeme spravovat stavovou logiku ve funkční komponentě.

Přesto máme v souboru TodosList.js komponentu konstruovanou třídou. Tato součást nemá žádnou logiku stavu ani životního cyklu. Díky tomu je převod snadný a přímý.

Zde je konverze.

import React from "react"
import TodoItem from "./TodoItem"

const TodosList = props => {
  return (
    <ul>
      {props.todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          handleChangeProps={props.handleChangeProps}
          deleteTodoProps={props.deleteTodoProps}
          setUpdate={props.setUpdate}
        />
      ))}
    </ul>
  )
}
export default TodosList

Nyní máme kompletní aplikaci React napsanou pouze s funkční komponentou. Díky React Hooks.