Dansk

Udforsk avancerede React Context Provider-mønstre for effektivt at administrere tilstand, optimere ydeevne og forhindre unødvendige gen-renderinger i dine applikationer.

React Context Provider-mønstre: Optimering af ydeevne og undgåelse af gen-renderingproblemer

React Context API er et kraftfuldt værktøj til at administrere global tilstand i dine applikationer. Det giver dig mulighed for at dele data mellem komponenter uden at skulle sende props manuelt på alle niveauer. Men forkert brug af Context kan føre til ydeevneproblemer, især unødvendige gen-renderinger. Denne artikel udforsker forskellige Context Provider-mønstre, der hjælper dig med at optimere ydeevnen og undgå disse faldgruber.

Forstå problemet: Unødvendige gen-renderinger

Som standard, når en Context-værdi ændres, vil alle komponenter, der bruger den pågældende Context, gen-renderes, selvom de ikke er afhængige af den specifikke del af Context, der er ændret. Dette kan være en væsentlig flaskehals for ydeevnen, især i store og komplekse applikationer. Overvej et scenarie, hvor du har en Context, der indeholder brugeroplysninger, temaindstillinger og applikationspræferencer. Hvis kun temaindstillingen ændres, bør kun komponenter relateret til temaindstillinger ideal set gen-renderes, ikke hele applikationen.

For at illustrere skal du forestille dig en global e-handelsapplikation, der er tilgængelig i flere lande. Hvis valutapræferencen ændres (håndteres i Context), vil du ikke have hele produktkataloget gen-renderet – kun prisvisningerne skal opdateres.

Mønster 1: Værdihukommelse med useMemo

Den enkleste metode til at forhindre unødvendige gen-renderinger er at gemme Context-værdien i hukommelsen ved hjælp af useMemo. Dette sikrer, at Context-værdien kun ændres, når dens afhængigheder ændres.

Eksempel:

Lad os sige, at vi har en UserContext, der leverer brugerdata og en funktion til at opdatere brugerens profil.


import React, { createContext, useState, useMemo } from 'react';

const UserContext = createContext(null);

function UserProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  const contextValue = useMemo(() => ({ 
    user,
    updateUser,
  }), [user, setUser]);

  return (
    
      {children}
    
  );
}

export { UserContext, UserProvider };

I dette eksempel sikrer useMemo, at contextValue kun ændres, når user-tilstanden eller setUser-funktionen ændres. Hvis ingen af dem ændres, vil komponenter, der bruger UserContext, ikke gen-renderes.

Fordele:

Ulemper:

Mønster 2: Opdeling af bekymringer med flere Contexts

En mere granulær tilgang er at opdele din Context i flere, mindre Contexts, hver ansvarlig for en specifik del af tilstanden. Dette reducerer omfanget af gen-renderinger og sikrer, at komponenter kun gen-renderes, når de specifikke data, de afhænger af, ændres.

Eksempel:

I stedet for en enkelt UserContext kan vi oprette separate contexts for brugerdata og brugerpræferencer.


import React, { createContext, useState } from 'react';

const UserDataContext = createContext(null);
const UserPreferencesContext = createContext(null);

function UserDataProvider({ children }) {
  const [user, setUser] = useState({
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  });

  const updateUser = (newUserData) => {
    setUser(prevState => ({ ...prevState, ...newUserData }));
  };

  return (
    
      {children}
    
  );
}

function UserPreferencesProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [language, setLanguage] = useState('en');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    
      {children}
    
  );
}

export { UserDataContext, UserDataProvider, UserPreferencesContext, UserPreferencesProvider };

Nu kan komponenter, der kun har brug for brugerdata, bruge UserDataContext, og komponenter, der kun har brug for temaindstillinger, kan bruge UserPreferencesContext. Ændringer af temaet vil ikke længere få komponenter, der bruger UserDataContext, til at gen-renderes, og omvendt.

Fordele:

Ulemper:

Mønster 3: Selektorfunktioner med brugerdefinerede Hooks

Dette mønster involverer oprettelse af brugerdefinerede hooks, der udtrækker specifikke dele af Context-værdien og kun gen-rendereres, når disse specifikke dele ændres. Dette er især nyttigt, når du har en stor Context-værdi med mange egenskaber, men en komponent kun har brug for et par af dem.

Eksempel:

Ved hjælp af den originale UserContext kan vi oprette brugerdefinerede hooks til at vælge specifikke brugeregenskaber.


import React, { useContext } from 'react';
import { UserContext } from './UserContext'; // Forudsat at UserContext er i UserContext.js

function useUserName() {
  const { user } = useContext(UserContext);
  return user.name;
}

function useUserEmail() {
  const { user } = useContext(UserContext);
  return user.email;
}

export { useUserName, useUserEmail };

Nu kan en komponent bruge useUserName til kun at gen-rendere, når brugerens navn ændres, og useUserEmail til kun at gen-rendere, når brugerens e-mail ændres. Ændringer af andre brugeregenskaber (f.eks. placering) udløser ikke gen-renderinger.


import React from 'react';
import { useUserName, useUserEmail } from './UserHooks';

function UserProfile() {
  const name = useUserName();
  const email = useUserEmail();

  return (
    

Navn: {name}

E-mail: {email}

); }

Fordele:

Ulemper:

Mønster 4: Komponenthukommelse med React.memo

React.memo er en højere ordens komponent (HOC), der husker en funktionel komponent. Det forhindrer komponenten i at gen-rendere, hvis dens props ikke er ændret. Du kan kombinere dette med Context for yderligere at optimere ydeevnen.

Eksempel:

Lad os sige, at vi har en komponent, der viser brugerens navn.


import React, { useContext } from 'react';
import { UserContext } from './UserContext';

function UserName() {
  const { user } = useContext(UserContext);
  return 

Navn: {user.name}

; } export default React.memo(UserName);

Ved at pakke UserName med React.memo vil den kun gen-renderes, hvis user prop (videresendt implicit via Context) ændres. I dette simplistiske eksempel vil React.memo alene dog ikke forhindre gen-renderinger, fordi hele user-objektet stadig videregives som en prop. For at gøre det virkelig effektivt, skal du kombinere det med selektorfunktioner eller separate contexts.

Et mere effektivt eksempel kombinerer React.memo med selektorfunktioner:


import React from 'react';
import { useUserName } from './UserHooks';

function UserName() {
  const name = useUserName();
  return 

Navn: {name}

; } function areEqual(prevProps, nextProps) { // Brugerdefineret sammenligningsfunktion return prevProps.name === nextProps.name; } export default React.memo(UserName, areEqual);

Her er areEqual en brugerdefineret sammenligningsfunktion, der kontrollerer, om name-proppen er ændret. Hvis den ikke er det, gen-renderes komponenten ikke.

Fordele:

Ulemper:

Mønster 5: Kombinering af Context og Reducere (useReducer)

Kombination af Context med useReducer giver dig mulighed for at administrere kompleks tilstandslogik og optimere gen-renderinger. useReducer giver et forudsigeligt tilstandsstyringsmønster og giver dig mulighed for at opdatere tilstanden baseret på handlinger, hvilket reducerer behovet for at videregive flere setter-funktioner gennem Context.

Eksempel:


import React, { createContext, useReducer, useContext } from 'react';

const UserContext = createContext(null);

const initialState = {
  user: {
    name: 'John Doe',
    email: 'john.doe@example.com',
    location: 'New York, USA'
  },
  theme: 'light',
  language: 'en'
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_USER':
      return { ...state, user: { ...state.user, ...action.payload } };
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_LANGUAGE':
      return { ...state, language: action.payload };
    default:
      return state;
  }
};

function UserProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    
      {children}
    
  );
}

function useUserState() {
  const { state } = useContext(UserContext);
  return state.user;
}

function useUserDispatch() {
    const { dispatch } = useContext(UserContext);
    return dispatch;
}


export { UserContext, UserProvider, useUserState, useUserDispatch };

Nu kan komponenter få adgang til tilstanden og sende handlinger ved hjælp af brugerdefinerede hooks. For eksempel:


import React from 'react';
import { useUserState, useUserDispatch } from './UserContext';

function UserProfile() {
  const user = useUserState();
  const dispatch = useUserDispatch();

  const handleUpdateName = (e) => {
    dispatch({ type: 'UPDATE_USER', payload: { name: e.target.value } });
  };

  return (
    

Navn: {user.name}

); }

Dette mønster fremmer en mere struktureret tilgang til tilstandsstyring og kan forenkle kompleks Context-logik.

Fordele:

Ulemper:

Mønster 6: Optimistiske opdateringer

Optimistiske opdateringer involverer opdatering af UI'en umiddelbart, som om en handling er lykkedes, selv før serveren bekræfter den. Dette kan forbedre brugeroplevelsen markant, især i situationer med høj latenstid. Det kræver dog omhyggelig håndtering af potentielle fejl.

Eksempel:

Forestil dig en applikation, hvor brugere kan lide indlæg. En optimistisk opdatering ville straks forøge antallet af likes, når brugeren klikker på like-knappen, og derefter fortryde ændringen, hvis serveranmodningen mislykkes.


import React, { useContext, useState } from 'react';
import { UserContext } from './UserContext';

function LikeButton({ postId }) {
  const { dispatch } = useContext(UserContext);
  const [isLiking, setIsLiking] = useState(false);

  const handleLike = async () => {
    setIsLiking(true);
    // Optimistisk opdatering af antallet af likes
    dispatch({ type: 'INCREMENT_LIKES', payload: { postId } });

    try {
      // Simuler et API-kald
      await new Promise(resolve => setTimeout(resolve, 500));

      // Hvis API-kallet lykkes, skal du ikke gøre noget (UI'en er allerede opdateret)
    } catch (error) {
      // Hvis API-kallet mislykkes, skal du fortryde den optimistiske opdatering
      dispatch({ type: 'DECREMENT_LIKES', payload: { postId } });
      alert('Kunne ikke lide indlægget. Prøv igen.');
    } finally {
      setIsLiking(false);
    }
  };

  return (
    
  );
}

I dette eksempel sendes handlingen INCREMENT_LIKES straks, og derefter fortrydes den, hvis API-kallet mislykkes. Dette giver en mere responsiv brugeroplevelse.

Fordele:

Ulemper:

Valg af det rigtige mønster

Det bedste Context Provider-mønster afhænger af de specifikke behov for din applikation. Her er et resumé, der hjælper dig med at vælge:

Yderligere tips til optimering af Context-ydeevne

Konklusion

React Context API er et kraftfuldt værktøj, men det er vigtigt at bruge det korrekt for at undgå ydeevneproblemer. Ved at forstå og anvende de Context Provider-mønstre, der er diskuteret i denne artikel, kan du effektivt administrere tilstand, optimere ydeevnen og bygge mere effektive og responsive React-applikationer. Husk at analysere dine specifikke behov og vælge det mønster, der bedst passer til din applikations krav.

Ved at overveje et globalt perspektiv bør udviklere også sikre, at tilstandsstyringsløsninger fungerer problemfrit på tværs af forskellige tidszoner, valutaformater og regionale datakrav. For eksempel skal en datoformateringsfunktion i en Context lokaliseres baseret på brugerens præference eller placering, hvilket sikrer konsistente og nøjagtige datovisninger, uanset hvor brugeren tilgår applikationen fra.

React Context Provider-mønstre: Optimering af ydeevne og undgåelse af gen-renderingproblemer | MLOG