React: kontekst
Opublikowano ndz 30 sierpnia 2020 w reactjs • 3 min read
Context API
Kontekst/Context API pozwala komponentowi na pozyskanie danych z kontekstu a nie przez props poprzez tworzenie globalnych danych dostępnych dla całości drzewa komponentu [innymi słowy uniknięcia sytuacji, w której przekazujemy informację do wielowarstwowo-zagnieżdżonego komponentu przez wiele pośredniczących komponentów (tzn. przez każdy poziom struktury)]. Na jakiego typu danych może nam zależeć aby były dostępne globalnie? Np. preferencje językowe, stylistyczne (np. kolorystyka) lub informacja o zalogowaniu się użytkownika.
Context API ma również swoje limity - z dokumentacji Reacta możemy dowiedzieć się, że utrudnia on "wielokrotne używanie komponentów zależnych" (zalecane rozwiązanie jest poprzez skorzystanie z możliwości kompozycji i props.children)
Praca z kontekstem
W pierwszej kolejności należy stworzyć obiekt kontekstowy w który przekazujemy zdefiniowany kontekst/dane do przekazania
const MyContext = React.createContext(defaultValue);
Komponent providera przetrzymuje wartość jaką "konsumencki" komponent będzie wykorzystywał
<MyContext.Provider value={/* jakaś wartość */}>
Wskazanie konsumenta odbywa się poprzez opakowanie go w komponent wyższego rzędu
<MyContext.Consumer>
Następnie w komponencie odbierającym/konsumenckim w odpowiedni sposób należy przypisać do niego kontekst wykorzystując właściwość contextType
(komponent klasowy) lub consumer
(komponent funkcyjny). Przypisanie odbywa się po za blokiem klasy komponentu oraz odwołując się do właściwości consumer komponentu funkcyjnego.
W przypadku komponentów funkcyjnych można również skorzystać z hooka useContext
i tą opcję uważam za najprostszą
(źródło poniższych przykładów)
========== Komponent klasowy ===========
// context.js
let MyContext = React.createContext(defaultValue);
export { MyContext }
// exampleClass.js
import { MyContext } from './myContext';
class ExampleClass extends React.Component {
render(){
let value = this.context;
// zwraca kontekst
}
}
ExampleClass.contextType = MyContext;
// przypisanie MyContext do ExampleClass
========== Komponent funkcyjny ===========
<MyContext.Consumer>
{value => {
return (
// zwraca element zależny od kontekstu
)
}}
</MyContext.Consumer>
//////////// Przykładowe zastosowanie ////////////
....
return (
<div>
<MyContext.Consumer>
{value => {
return (
// zwraca element zależny od kontekstu
)
}}
</MyContext.Consumer>
</div>
)
Przykład zastosowania Context API - jednostronne
Poniższy przykład za reactjs.org - Hooks API Reference
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// komponent będący konsumentem
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
Dynamiczny kontekst
Aby kontekst edytować, podobnie jak w przypadku dziedziczenia oraz przekazywania właściwości przy pomocy drylowania również i tutaj należy stworzyć funkcję/metodę, która pozwoli na jego edycję a następnie należy ją przekazać lub rozpakować (w zależności od wybranego podejścia zastosowania) w komponencie będącym providerem/dostawcą.
Przykładowo
// theme-context.js (plik przechowujący kontekst)
const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},})
=================================
// theme-toggler-button.js (tu wykorzystywany jest kontekst)
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
=================
// app.js (aplikacja)
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// State also contains the updater function so it will // be passed down into the context provider this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme, };
}
render() {
// The entire state is passed to the provider return (
<ThemeContext.Provider value={this.state}> <Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
=================================
Inny przykład - wykorzystyjący hook useContext (źródło przykładu)
// context.js
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
//LanguageSwitcher.js Context Consumer
const LanguageSwitcher = () => {
const { language, setLanguage } = useContext(LanguageContext);
return (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
);
};
// App.js
const App = () => {
const [language, setLanguage] = useState("en");
const value = { language, setLanguage };
return (
<LanguageContext.Provider value={value}>
<h2>Current Language: {language}</h2>
<p>Click button to change to jp</p>
<div>
{/* Can be nested */}
<LanguageSwitcher />
</div>
</LanguageContext.Provider>
);
};
Ten sam przykład klasowo ->
// context.js
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
// konsumer LanguageSwitcher.js
class LanguageSwitcher extends Component {
render() {
return (
<LanguageContext.Consumer>
{({ language, setLanguage }) => (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
)}
</LanguageContext.Consumer>
);
}
}
// App.js (provider)
class App extends Component {
setLanguage = language => {
this.setState({ language });
};
state = {
language: "en",
setLanguage: this.setLanguage
};
render() {
return (
<LanguageContext.Provider value={this.state}>
<h2>Current Language: {this.state.language}</h2>
<p>Click button to change to jp</p>
<div>
{/* Can be nested */}
<LanguageSwitcher />
</div>
</LanguageContext.Provider>
);
}
}
Źródła:
Playing with the Context API in React 16.3