Nowe Context API w React pozwala nam na łatwe przekazywanie wartości w dół drzewa komponentów. Nie musimy zatem przekazywać wartości jako propsy. W internecie pojawia się wiele artykułów, które sugerują, że Context API może być dobrą alternatywą np. dla Reduxa. W pierwszym artykule z tej serii przedstawię na czym polega działanie Context API i jak wygląda jego proste użycie, zaś w następnym artykule dotyczącym Context API, przedstawię małą niedogodność „kontekstu” i będzie to pewną odpowiedzią, czy rzeczywiście Context API jest dobrą alternatywą dla state management typu Redux lub MobX.

Jeżeli w ogóle nie wiesz co to jest Context API i jak działa, poniższe przykłady na pewno Tobie pomogą, więc zachęcam do pozostania, bo być może w Twoim projekcie Context API znajdzie wykorzystanie.

Na początek krótka informacja, jak musielibyśmy przekazywać wartości w zagnieżdżonym drzewie komponentów bez żadnego „state managementu” lub Context API.

prop drilling
„prop drilling” – przekazywanie wartości w dół drzewa komponentów

Objaśnienie – „prop drilling”:

Wartość pogrubiona oznacza, że komponent wykorzystuje daną wartość, zaś wartość niepogrubiona oznacza, że komponent daną wartość posiada, mimo że z niej nie korzysta.
Component przekazuje wartości: value 1, value 2, value 3 do Component 2
Component 2 przekazuje wartości: value 2, value 3 do Component 3, zaś korzysta tylko z wartości value 1.
Component 3 przekazuje wartość: value 3 do Component 4 , zaś korzysta tylko z wartości value 2.
Component 4 korzysta z wartości value 3.

Jak łatwo zauważyć, Component 3, mimo że używa tylko jednej wartości i tak musi otrzymać wartość value 3, tylko po to, aby przekazać ją niżej do Component 4 itd.

Takie przekazywanie wartości w dół drzewa komponentów może być mało intuicyjne. Jeżeli będziemy przekazywali większa ilość wartości, łatwo będzie się w tym pogubić. Dodatkowo wysyłamy wartość z jednego komponentu tylko po to, aby przekazać go niżej, w ogóle go nie używając.

Wyobraźmy sobie, że chcemy przekazać nową wartość, która pochodzi z Component do Component 4. Przy takim podejściu musimy znów przekazywać wartość niżej komponent po komponencie. Oczywiście możemy przekazać obiekt z wartościami, wtedy wystarczy dodać wartość do obiektu w Component i będzie on dostępny w Component 4. Tutaj jednak tracimy trochę kontrolę nad tym, co jest potrzebne danemu komponentowi do poprawnego działania, dodatkowo następuje zmiana „propsów”.

Załóżmy inną sytuację. Sprawdzamy użycie komponentu Component 2 i widzimy, że przyjmuje on 3 wartości. Aby mieć pewność, z których wartości tak naprawdę korzysta komponent musimy po prostu prześledzić zawartość komponentu.
Przy większej złożoności staje się to dosyć skomplikowane…

Context API

Context API może pomóc nam rozwiązać problem „props drilling”.
Context API składa się z dwóch głównych elementów – Providera i Consumera.<Provider> dostarcza dane, zaś <Consumer> odbiera je. Tutaj dzieje się cała magia Context API. „Zwrapowane” poprzez <Provider> komponenty oraz komponenty podrzędne mają dostęp do wartości przekazanych przez <Provider>.

Pseudokod:

<Provider> // dostarcza dane
    <Component > // dostęp do danych przekazanych przez <Provider>
        <Component2 /> // dostęp do danych przekazanych przez <Provider>
        <Component3 /> // dostęp do danych przekazanych przez <Provider>
        <Component4 /> // dostęp do danych przekazanych przez <Provider>
    <Component /> // dostęp do danych przekazanych przez <Provider>
</Provider>

W wyżej wymienionym przykładzie nasz główny <Component> (komponent znajdujący się na samej górze mógłby być „zwrapowany” przez <Provider>, a dzięki temu Component 2, Component 3, Component 4 mają dostęp do przekazanych wartości do Context API, czyli wszystkich value 1, value 2, value 3.

Context API - dostęp do wartości
Context API – dostęp do wartości

Przykłady użycia:
Pora na konkretne przykłady, gdyż ja osobiście najbardziej lubię przyswajać praktyczną wiedzę i taką też chciałbym Wam przekazać…

Tworzenie kontekstu

Na początek trzeba stworzyć Context używając React.createContext(). React.createContext() zwraca nam Provider i Consumer.

import React from 'react'

const ExampleContext = React.createContext('');

export default ExampleContext;

Teraz stwórzmy komponenty, w którym użyjemy naszego Providera do dostarczenia danych do komponentu.

import React from 'react';

// Konfiguracja ContextAPI
import ExampleContext from "./Example/Context/ExampleContext";
// Importujemy 3 "rodzaje" komponentów, 
// aby pokazać jak korzystać z ContextAPI dla każdego typu komponentu
import Classes from "./Example/Usage/Classes";
import Functional from "./Example/Usage/Functional";
import Hooks from "./Example/Usage/Hooks";

function Example() {
    <ExampleContext.Provider value={'https://magicweb.pl'}>
        <Classes/>
        <Functional />
        <Hooks />
    </ExampleContext.Provider>
}

export default Example;

Odbieranie danych z Context API

Sekcja ta będzie zawierała 3 przykłady. Będziemy odbierać poprzez Context API dane w komponencie klasowym. Komponencie funkcyjnym bez użycia React hooks oraz w komponencie funkcyjnym przy użyciu hooka React.useContext().

Komponent klasowy

import React from 'react';
import ExampleContext from '../Context/ExampleContext';

class Classes extends React.Component {
    static contextType = ExampleContext;

    render() {
        return (
            <div>
                <strong>Classes:</strong> {this.context}
            </div>
        );
    }
}

export default Classes;

Komponent funkcyjny – bez użycia React hooks

import React from 'react';

import ExampleContext from "../Context/ExampleContext";

function Functional() {
    return (
        <ExampleContext.Consumer>
            {value => (
                <div>
                    <strong>Functional:</strong> {value}
                </div>
            )}
        </ExampleContext.Consumer>
    );
}

export default Functional;

Komponent funkcyjny – użycie React hooks – useContext()

import React from 'react';

import ExampleContext from '../Context/ExampleContext';

function Hooks() {
    const value = React.useContext(ExampleContext);

    return (
        <div>
            <strong>Hooks:</strong> {value}
        </div>
    );
}

export default Hooks;

Powyższe przykłady nie zawierają zagnieżdżonej struktury drzewa komponentów, a jedynie jeden poziom. Zapewniam jednak, że komponent znajdujący się głęboko w drzewie komponentów również odbierze wartość poprawnie. Osiągnęliśmy nasz cel, uniknęliśmy przekazywania wartości jako propsy. 🙂

Jakie korzyści płyną z użycia Context API?

Przede wszystkim nie mamy problemu z „props drilling”, czyli nie musimy przekazywać propsów komponent po komponencie w dół.
Druga sprawa – źródło danych. Jeżeli dobrze zastanowimy się nad podziałem komponentów i zasilaniem ich źródłem danych, będziemy mogli mieć jedno źródło danych do drzewa komponentów, czyli prościej mówiąc miejsca, z którego te dane pochodzą i będzie to nasze Context API.

Nawet jeżeli będziemy chcieli prześledzić jakąś wartość, będziemy szukać jej w miejscu, w którym kontekst dane otrzymuje. Oczywiście przed „wstrzyknięciem danych do Contextu” zapewne będzie jeszcze cała logika, które dane odbiera, przygotowuje itd. Natomiast dzięki temu sama analiza powinna się nieco uprościć.

Context API – pobranie danych, przygotowanie danych i odbieranie w komponentach

Podsumowanie

Artykuł jest pierwszym z cyklu artykułów dotyczący Context API.
W następnym artykule przedstawię problem ponownego renderowania komponentów podczas wykorzystania Context API.

Uwaga:
Przygotowałem proste repozytorium z przykładem z artykułu wraz z zagnieżdżoną strukturą komponentów, w którym to tylko ostatni komponent używa Context API do odebrania wartości.

Zapraszam na githuba:
https://github.com/magicwebpl/react-context-api-usage

Zadanie domowe

W związku z tym, że przykład podany w artykule nie zawiera zagnieżdżonej struktury komponentów, przygotuj proszę samodzielnie <Provider> oraz kilka komponentów, gdzie jeden będzie zawarty w drugim, aby powstała zagnieżdżona struktura, a następnie spróbuj odebrać wartość w ostatnim komponencie drzewa. Można posiłkować się kodem z Githuba, tam zawarte jest rozwiązanie zadania domowego.

Linki:
https://reactjs.org/docs/context.html – o Context API na oficjalnej stronie Reacta

Źródła:
Diagram przygotowany przy użyciu narzędzia draw.io.