Wprowadzenie do interfejsu API kontekstu React

Opublikowany: 2022-03-10
Krótkie podsumowanie ↬ W tym artykule dowiesz się, jak korzystać z API kontekstowego Reacta, które pozwala zarządzać globalnymi stanami aplikacji w aplikacjach Reacta bez konieczności drążenia rekwizytów.

W tym samouczku powinieneś dobrze rozumieć haki. Jednak zanim zaczniemy, pokrótce omówię, czym one są i jakie haki będziemy używać w tym artykule.

Według React Docs:

Hooki to nowy dodatek w React 16.8. Pozwalają korzystać ze stanu i innych funkcji Reacta bez pisania klasy”.

To jest w zasadzie hak React. Pozwala nam na używanie state, refs i innych funkcji React w naszych komponentach funkcjonalnych.

Omówmy dwa haki, które napotkamy w tym artykule.

Hak useState

Hak useState pozwala nam na użycie stanu w naszych komponentach funkcjonalnych. useState przyjmuje początkową wartość naszego stanu jako jedyny argument i zwraca tablicę dwóch elementów. Pierwszym elementem jest nasza zmienna state a drugim elementem jest funkcja, w której możemy skorzystać z aktualizacji wartości zmiennej state.

Spójrzmy na następujący przykład:

 import React, {useState} from "react"; function SampleComponent(){ const [count, setCount] = useState(0); }

Tutaj count jest naszą zmienną stanu i jej początkową wartością jest 0 , podczas gdy setCount to funkcja, której możemy użyć do aktualizacji wartości count.

Hook useContext

Omówię to w dalszej części artykułu, ale ten hak w zasadzie pozwala nam konsumować wartość kontekstu. Co to właściwie oznacza, stanie się bardziej widoczne w dalszej części artykułu.

Obszary robocze przędzy

Obszary robocze Yarn umożliwiają organizowanie bazy kodu projektu za pomocą monolitycznego repozytorium (monorepo). React jest dobrym przykładem projektu typu open source, który jest monorepo i wykorzystuje obszary robocze Yarn do osiągnięcia tego celu. Przeczytaj powiązany artykuł →

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Dlaczego potrzebujemy API kontekstowego?

Chcemy zbudować komponent „przełącznik motywu”, który przełącza się między trybem jasnym i ciemnym dla naszej aplikacji React. Każdy komponent musi mieć dostęp do bieżącego trybu motywu, aby można było go odpowiednio stylizować.

Normalnie udostępnilibyśmy bieżący tryb motywu wszystkim komponentom za pomocą props i zaktualizowalibyśmy bieżący motyw za pomocą state :

 import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Text theme= "blue" /> <h1>{theme}</h1> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

W powyższym przykładzie kodu utworzyliśmy komponent tekstowy, który renderuje element h1 . Kolor elementu h1 zależy od bieżącego trybu motywu. Obecnie motyw jest niebieski. Możemy przełączać się między motywami blue i red za pomocą state .

Stworzymy stan o nazwie „theme” za pomocą useState . useState zwróci bieżącą wartość motywu oraz funkcję, której możemy użyć do aktualizacji motywu.

Stwórzmy więc nasz stan motywu:

 const [theme, setTheme] = React.useState("blue");

Dodamy również element przycisku do naszego komponentu App . Ten przycisk będzie używany do przełączania motywów i wymaga obsługi zdarzenia kliknięcia. Napiszmy więc obsługę zdarzeń kliknięcia w następujący sposób:

 const onClickHandler = () => { setTheme(); }

Teraz chcemy ustawić nowy motyw na Red , jeśli bieżący motyw to Blue i na odwrót. Zamiast używać instrukcji if , wygodniejszym sposobem na to jest użycie operatora trójargumentowego w JavaScript.

 setTheme( theme === "red"? "blue": "red");

Więc teraz napisaliśmy nasz program obsługi onClick . Dodajmy ten element przycisku do komponentu App :

 <button onClick = {onClickHandler}>Change theme</button>

Zmieńmy również wartość właściwości tematycznych komponentu Tekst na stan tematyczny.

 <Text theme={theme}/>

Teraz powinniśmy mieć to:

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { const[theme, setTheme] = React.useState("red"); const onClickHandler = () => { setTheme( theme === "red"? "blue": "red"); } return ( <div> <Text theme={theme}/> <button onClick = {onClickHandler}>Change theme</button> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

Możemy teraz przełączać się między naszymi dwoma motywami. Gdyby jednak była to znacznie większa aplikacja, trudno byłoby użyć motywu w głęboko zagnieżdżonych komponentach, a kod stałby się niewygodny.

Przedstawiamy API kontekstowe

Pozwólcie, że przedstawię Context API. Zgodnie z dokumentacją React:

„Kontekst umożliwia przekazywanie danych przez drzewo komponentów bez konieczności ręcznego przekazywania właściwości na każdym poziomie”.

Aby uzyskać bardziej dogłębną definicję, zapewnia sposób udostępnienia określonych danych wszystkim komponentom w całym drzewie komponentów, bez względu na to, jak głęboko może być zagnieżdżony dany komponent.

Spójrzmy na ten przykład:

 const App = () => { return( <ParentComponent theme = "light"/> ); } const ParentComponent = (props) => ( <Child theme = {props.theme} /> ) const Child = (props) => ( <Grandchild theme = {props.theme} /> ) const Grandchild = (props) => ( <p>Theme: {props.theme}</p> )

W powyższym przykładzie określiliśmy motyw aplikacji za pomocą właściwości w składniku ParentComponent o nazwie theme . Musieliśmy przekazać te właściwości do wszystkich komponentów w drzewie komponentów, aby uzyskać je tam, gdzie jest to potrzebne, czyli do komponentu GrandChild . ChildComponent nie miał nic wspólnego z rekwizytami motywu, ale był używany tylko jako pośrednik.

Teraz wyobraź sobie, że składnik GrandChild był głębiej zagnieżdżony niż w górnym przykładzie. Musielibyśmy przekazać rekwizyty tematyczne w taki sam sposób, jak tutaj, co byłoby uciążliwe. To jest problem, który rozwiązuje Context . Dzięki Context , każdy komponent w drzewie komponentów ma dostęp do dowolnych danych, które zdecydujemy się umieścić w naszym kontekście.

Zacznijmy od Context

Czas na replikę przycisku przełączania motywów, który zbudowaliśmy na początku artykułu, za pomocą Context API. Tym razem nasz przełącznik motywów będzie osobnym komponentem. Zbudujemy komponent ThemeToggler , który przełącza motyw naszej aplikacji React za pomocą Context .

Najpierw zainicjujmy naszą aplikację React. (Wolę używać create-react-app ale możesz użyć dowolnej metody).

Po zainicjowaniu projektu React utwórz plik o nazwie ThemeContext.js w folderze /src . Możesz także utworzyć folder o nazwie /context i umieścić tam swój plik ThemeContext , jeśli chcesz.

Teraz przejdźmy dalej.

Tworzenie interfejsu API kontekstu

Stworzymy nasz kontekst motywu w naszym pliku ThemeContext.js .

Do stworzenia kontekstu używamy React.createContext , który tworzy obiekt kontekstu. Możesz przekazać wszystko jako argument do React.createContext . W tym przypadku przekażemy ciąg, który jest bieżącym trybem motywu. Więc teraz naszym obecnym trybem motywu jest „lekki” tryb motywu.

 import React from "react"; const ThemeContext = React.createContext("light"); export default ThemeContext;

Aby ten kontekst był dostępny dla wszystkich naszych komponentów React, musimy użyć Providera. Kim jest dostawca? Zgodnie z dokumentacją React, każdy obiekt kontekstu jest dostarczany z komponentem Provider React, który pozwala zużywającym komponentom na subskrybowanie zmian kontekstu. To dostawca umożliwia wykorzystanie kontekstu przez inne komponenty. To powiedziawszy, stwórzmy naszego dostawcę.

Przejdź do pliku App.js. Aby utworzyć naszego dostawcę, musimy zaimportować nasz ThemeContext .

Po zaimportowaniu komponentu ThemeContext , musimy umieścić zawartość naszego komponentu App w znacznikach ThemeContext.Provider i nadać komponentowi ThemeContext.Provider zwane value , które będą zawierać dane, które chcemy udostępnić naszemu drzewu komponentów.

 function App() { const theme = "light"; return ( <ThemeContext.Provider value = {theme}> <div> </div> </ThemeContext.Provider> ); }

Tak więc teraz wartość „światła” jest dostępna dla wszystkich naszych komponentów (o czym napiszemy wkrótce).

Tworzenie naszego pliku motywu

Teraz utworzymy nasz plik motywu, który będzie zawierał różne wartości kolorów zarówno dla naszych jasnych, jak i ciemnych motywów. Utwórz plik w folderze /src o nazwie Colors.js .

W Colors.js utworzymy obiekt o nazwie AppTheme . Ten obiekt będzie zawierał kolory dla naszych motywów. Gdy skończysz, wyeksportuj obiekt AppTheme w następujący sposób:

 const AppTheme = { light: { textColor: "#000", backgroundColor: "#fff" }, dark: { textColor: "#fff", backgroundColor: "#333" } } export default AppTheme;

Teraz nadszedł czas, aby zacząć tworzyć nasze różne komponenty React.

Tworzenie naszych komponentów React

Stwórzmy następujące komponenty:

  • Header
  • ThemeToggler
  • MainWithClass

Header.jsx

 import React from "react"; import ThemeToggler from "./ThemeToggler"; const headerStyles = { padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" } const Header = () => { return( <header style = {headerStyles}> <h1>Context API</h1> <ThemeToggler /> </header> ); } export default Header;

ThemeToggler.jsx

(Na razie zwrócimy tylko pusty div .)

 import React from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { return( <div style = {themeTogglerStyle}> </div> ); } export default ThemeToggler;

Zużywający kontekst z komponentami opartymi na klasach

Tutaj użyjemy wartości naszego ThemeContext . Jak być może już wiesz, w React mamy dwie metody pisania komponentów : poprzez funkcje lub klasy. Proces użycia kontekstu w obu metodach jest inny, dlatego stworzymy dwa komponenty, które będą służyć jako główna sekcja naszej aplikacji: MainWithClass i MainWithFunction .

Zacznijmy od MainWithClass .

MainWithClass.jsx

Będziemy musieli zaimportować nasze ThemeContext i AppTheme . Gdy to zrobimy, napiszemy klasę, która zwraca nasz JSX z metody render. Teraz musimy skonsumować nasz kontekst. W przypadku komponentów opartych na klasach można to zrobić na dwa sposoby:

  1. Pierwsza metoda to Class.contextType .

    Aby skorzystać z tej metody, przypisujemy obiekt context z naszego ThemeContext do właściwości contextType naszej klasy. Następnie będziemy mogli uzyskać dostęp do wartości kontekstu za pomocą this.context . Możesz również odnieść się do tego w dowolnej metodzie cyklu życia, a nawet w metodzie render.

     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context]; return( <main></main> ); } }

    Po przypisaniu ThemeContext do właściwości contextType naszej klasy, zapisałem bieżący obiekt motywu w zmiennej currentTheme .

    Teraz pobierzemy kolory ze zmiennej currentTheme i użyjemy ich do nadania stylu adiustacji.
     render() { const currentTheme = AppTheme[this.context]; return ( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main>

    Otóż ​​to! Ta metoda ogranicza jednak do korzystania tylko z jednego kontekstu.
  2. Druga metoda to ThemeContext.Consumer , która obejmuje użycie Konsumenta. Każdy obiekt kontekstu zawiera również komponent Consumer React, który może być używany w komponencie opartym na klasach. Komponent konsumenta przyjmuje funkcję potomną i ta funkcja zwraca węzeł reakcji. Bieżąca wartość kontekstu jest przekazywana do tej funkcji jako argument.

    Teraz zastąpmy kod w naszym komponencie MainWithClass następującym:
     class Main extends Component { constructor() { super(); this.state = { } } render(){ return( <ThemeContext.Consumer> { (theme) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } }

    Jak widać, użyliśmy bieżącej wartości naszego ThemeContext , którą nazwaliśmy aliasem „theme”, pobraliśmy wartości kolorów dla tego trybu motywu i przypisaliśmy je do zmiennej currentTheme . Dzięki tej metodzie możesz korzystać z wielu Konsumentów.

Są to dwie metody konsumowania kontekstu za pomocą komponentów opartych na klasach.

Kontekst konsumujący z komponentami funkcjonalnymi

Używanie kontekstu z komponentami funkcjonalnymi jest łatwiejsze i mniej żmudne niż robienie tego z komponentami opartymi na klasach. Aby wykorzystać kontekst w komponencie funkcjonalnym, użyjemy zaczepu o nazwie useContext .

Oto jak wyglądałoby konsumowanie naszego ThemeContext za pomocą komponentu funkcjonalnego:

 const Main = () => { const theme = useContext(ThemeContext); const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;

Jak widać, wszystko, co musieliśmy zrobić, to użyć naszego haka useContext z naszym ThemeContext przekazanym jako argument.

Uwaga : aby zobaczyć wyniki, musisz użyć tych różnych komponentów w pliku App.js.

Aktualizowanie naszego motywu za pomocą komponentu ThemeToggler

Teraz będziemy pracować nad naszym komponentem ThemeToggler . Musimy być w stanie przełączać się między jasnymi i ciemnymi motywami. Aby to zrobić, będziemy musieli edytować nasz ThemeContext.js . Nasz React.createContext przyjmie teraz jako argument obiekt przypominający wynik haka useState .

 const ThemeContext = React.createContext(["light", () => {}]);

Do funkcji React.createContext przekazaliśmy tablicę. Pierwszym elementem tablicy jest bieżący tryb motywu, a drugim funkcja, która zostałaby użyta do aktualizacji motywu. Jak powiedziałem, przypomina to wynik haka useState , ale nie jest to dokładnie wynik haka useState .

Teraz edytujemy nasz plik App.js. Musimy zmienić wartość przekazywaną do dostawcy na hook useState . Teraz wartością naszego kontekstu motywu jest hook useState , którego domyślną wartością jest „light”.

 function App() { const themeHook = useState("light"); return ( <ThemeContext.Provider value = {themeHook}> <div> <Header /> <Main /> </div> </ThemeContext.Provider> ); }

Pisanie naszego komponentu ThemeToggler

Napiszmy teraz nasz komponent ThemeToggler :

 import React,{useContext} from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { const[themeMode, setThemeMode] = useContext(ThemeContext); return( <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}> <span title = "switch theme"> {themeMode === "light" ? "" : "️"} </span> </div> ); } export default ThemeToggler;

Ponieważ wartością naszego kontekstu motywu jest teraz przechwycenie za każdym razem, gdy wywołamy na nim useContext , zwróci on tablicę. Korzystając z destrukturyzacji, udało nam się pobrać elementy z tablicy. Następnie napisaliśmy procedurę obsługi zdarzeń onClick dla naszego ThemeToggler . Dzięki temu kodowi za każdym razem, gdy klikniesz przełącznik motywu, zmieni on motyw naszej aplikacji.

Teraz będziemy edytować różne wersje naszego Main komponentu.

Edytowanie naszego komponentu MainWithClass

  1. Wersja komponentu MainWithClass korzystająca z metody Class.contextType :
     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context[0]]; return( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } }
  2. Wersja komponentu MainWithClass korzystająca z metody ThemeContext.Consumer :
     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component { constructor() { super(); this.state = {} } render() { return ( <ThemeContext.Consumer> { ([theme]) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } } export default Main;

Edytowanie naszego MainWithFunction Component

Składnik MainWithFunction należy edytować w następujący sposób:

 import React, { useContext } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; const Main = () => { const theme = useContext(ThemeContext)[0]; const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;

Wniosek

Otóż ​​to! Udało nam się zaimplementować dwa tryby motywów dla naszej aplikacji React za pomocą Context API.

W trakcie tego nauczyliśmy się:

  • Czym jest Context API i jaki problem rozwiązuje;
  • Kiedy używać interfejsu API kontekstu;
  • Tworzenie Context i konsumowanie go zarówno w komponentach funkcjonalnych, jak i klasowych.

Dalsze czytanie na SmashingMag:

  • Stylizacja w nowoczesnych aplikacjach internetowych
  • Tworzenie aplikacji mobilnych za pomocą Ionic i React
  • Zbuduj PWA z Webpackiem i Workbox
  • Poznawanie interfejsu API MutationObserver