React Native Academy · Lektion

Flytning af state op

Flyt delt state til den nærmeste fælles overordnede komponent, og videregiv både værdien og et callback til opdatering som props, så søskendekomponenter holdes synkroniserede.

Lektion 3 af 413 trin

Flytning af state op er en gratis React Native Academy-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i React Native Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. React Native Academy-kurset indeholder 4 lektioner i alt.

Problemet: Søskendekomponenter har brug for delt tilstand

Forestil dig to søskendekomponenter, der skal dele et stykke data — f.eks. en komponent med et søgefelt og en resultatliste, der filtreres ud fra søgeforespørgslen. Hvis søgetilstanden findes inde i søgefeltets komponent, har resultatlisten ingen mulighed for at få adgang til den. Ingen af søskendekomponenterne kan se den andens tilstand direkte. Løsningen er at løfte tilstanden op til den nærmeste fælles forfader — den overordnede komponent, der gengiver begge søskendekomponenter. Den overordnede komponent sender derefter tilstandsværdien til den ene underordnede komponent og setter-tilbagekaldet til den anden.

// PROBLEM: SearchInput holds state that ResultsList needs
// but siblings can't communicate directly

function SearchInput() {
  const [query, setQuery] = useState(''); // stuck here
  return <TextInput value={query} onChangeText={setQuery} />;
}

function ResultsList() {
  // How do we access 'query' from the sibling? We can't!
  return <FlatList data={/* needs query */} />;
}

Løft tilstanden til den fælles forfader

Flyt den delte tilstand til den nærmeste fælles forfader — den komponent, der gengiver begge søskendekomponenter. Den overordnede komponent ejer nu tilstanden og sender den videre som props: søgeforespørgslen går til resultatlisten, og setter-funktionen går til søgefeltet. Søskendekomponenterne kommunikerer gennem den overordnede komponent: Søgefeltet kalder setter-funktionen, som er sendt som en prop, den opdaterer den overordnede komponents tilstand, og den nye søgeforespørgsel sendes videre til resultatlisten som en ny prop. Dette er det grundlæggende dataflowmønster i React.

import { useState } from 'react';
import { View, TextInput, Text } from 'react-native';

// Parent owns the shared state
export default function SearchScreen() {
  const [query, setQuery] = useState('');
  const results = ITEMS.filter(item =>
    item.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <SearchInput query={query} onQueryChange={setQuery} />
      <ResultsList results={results} />
    </View>
  );
}

const ITEMS = ['Apple', 'Banana', 'Blueberry', 'Cherry', 'Avocado'];

Skrivning af de underordnede komponenter

Med løftet tilstand bliver de underordnede komponenter enkle og fokuserede. Komponenten SearchInput modtager query (den aktuelle værdi) og onQueryChange (til opdatering af den) som props. ResultsList modtager arrayet med de filtrerede resultater. Ingen af de underordnede komponenter håndterer tilstand — de er kontrollerede komponenter, hvis adfærd udelukkende styres af deres props. Det gør begge komponenter nemme at teste isoleret: Giv dem blot props, og kontrollér det gengivne resultat.

import { TextInput, FlatList, Text, View, StyleSheet } from 'react-native';

// Controlled: receives value and updater as props
function SearchInput({ query, onQueryChange }) {
  return (
    <TextInput
      value={query}
      onChangeText={onQueryChange}
      placeholder='Search...'
      style={styles.input}
    />
  );
}

// Pure display: receives filtered data as prop
function ResultsList({ results }) {
  return (
    <FlatList
      data={results}
      keyExtractor={item => item}
      renderItem={({ item }) => (
        <Text style={styles.item}>{item}</Text>
      )}
    />
  );
}

const styles = StyleSheet.create({
  input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, marginBottom: 12 },
  item: { padding: 12, borderBottomWidth: 1, borderBottomColor: '#eee' },
});

Find den nærmeste fælles forfader

Det afgørende spørgsmål, når du løfter tilstand, er: 'Hvilken komponent er den laveste i træet, der gengiver alle de komponenter, som har brug for denne tilstand?' Løft tilstanden præcis til den komponent — ikke højere. Hvis du løfter den for højt, f.eks. helt op til App, medfører det unødvendige gengivelser af komponenter, der ikke har noget med tilstanden at gøre. Den nærmeste fælles forfader er den rette balance mellem tilgængelighed, hvor alle forbrugere kan modtage tilstanden som props, og ydeevne, hvor kun det undertræ, der har brug for tilstanden, gengives igen. Tegn dit komponenttræ, og følg stien op fra hver forbruger for at finde det sted, hvor stierne mødes.

// Component tree:
// App
// └── TabBar (selected tab)
//     ├── HomeTab
//     │   └── Feed
//     └── ProfileTab
//
// If 'selected tab' is needed by TabBar AND by Feed to filter content,
// lift it to TabBar (nearest common ancestor of both).
// DO NOT lift all the way to App unnecessarily.

function TabBar() {
  const [activeTab, setActiveTab] = useState('home');
  return (
    <View>
      <TabButtons activeTab={activeTab} onChange={setActiveTab} />
      {activeTab === 'home' ? <HomeTab /> : <ProfileTab />}
    </View>
  );
}

Flere søskendekomponenter deler tilstand

Når tre eller flere søskendekomponenter har brug for den samme tilstand, skal du løfte den én gang til deres fælles overordnede komponent og sende den til hver søskendekomponent, der har brug for den. En produktside kan f.eks. have en ImageGallery, en VariantPicker og en AddToCartButton. Tilstanden for den valgte variant findes i den overordnede skærmkomponent og sendes til alle tre. VariantPicker får setter-funktionen til at ændre valget. ImageGallery viser billeder af den valgte variant. AddToCartButton tilføjer den specifikt valgte variant til indkøbskurven.

import { useState } from 'react';
import { View } from 'react-native';

const VARIANTS = ['Red / S', 'Red / M', 'Blue / S', 'Blue / M'];

export default function ProductDetailScreen() {
  const [selectedVariant, setSelectedVariant] = useState(VARIANTS[0]);

  return (
    <View style={{ flex: 1 }}>
      <ImageGallery variant={selectedVariant} />
      <VariantPicker
        variants={VARIANTS}
        selected={selectedVariant}
        onSelect={setSelectedVariant}
      />
      <AddToCartButton variant={selectedVariant} />
    </View>
  );
}

Tilbagekaldsprops kontra delt tilstand

Nogle gange deler komponenter en handling i stedet for en tilstandsværdi. Et almindeligt eksempel er en listeside med et indkøbskurvsikon i overskriften — et tryk på 'Add to Cart' for et listeelement skal opdatere antalbadge i overskriften. Både listeelementerne og overskriften er søskendekomponenter under den samme overordnede skærmkomponent. Den overordnede komponent gemmer antallet af varer i indkøbskurven i tilstanden, sender antallet til overskriften via props og sender et onAddToCart-tilbagekald til listen. Elementerne kalder tilbagekaldet, den overordnede komponent opdaterer sin tilstand, og badgen i overskriften gengives igen med det nye antal.

import { useState } from 'react';
import { View } from 'react-native';

export default function ShopScreen() {
  const [cartCount, setCartCount] = useState(0);

  function addToCart(item) {
    // Business logic lives in the parent
    setCartCount(prev => prev + 1);
    console.log('Added:', item.name);
  }

  return (
    <View style={{ flex: 1 }}>
      {/* Sibling 1: displays count from parent state */}
      <ShopHeader cartCount={cartCount} />
      {/* Sibling 2: triggers parent state update */}
      <ProductList onAddToCart={addToCart} />
    </View>
  );
}

Når det at løfte tilstand bliver til prop-boring

Hvis den fælles forfader ligger mange niveauer over de komponenter, der har brug for tilstanden, ender du med prop-boring — du sender props gennem mange mellemliggende komponenter, som ikke bruger dem. Hvis den fælles forfader f.eks. er App-roden, og der er fem komponentniveauer mellem App og forbrugerne, bliver hver mellemliggende komponent et mellemled. Løsningen til deling på tværs af dybe komponenttræer er Context API for data, som mange komponenter har brug for, eller et bibliotek til tilstandshåndtering som Zustand eller Redux. Tommelfingerreglen er: Løft tilstanden, men hvis den krydser mere end 2–3 niveauer uden at blive brugt undervejs, bør du overveje Context i stedet.

// When prop drilling gets too deep, switch to Context
// Instead of:
function App() {
  const [user, setUser] = useState(null);
  return <Nav user={user}>   // not used
    <Drawer user={user}>     // not used
      <Screen user={user}>  // not used
        <ProfileCard user={user} />  // finally used
      </Screen>
    </Drawer>
  </Nav>;
}

// Use Context:
const UserContext = React.createContext(null);
function App() {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value={user}>
      <Nav><Drawer><Screen><ProfileCard /></Screen></Drawer></Nav>
    </UserContext.Provider>
  );
}

Praktisk eksempel: Temperaturkonvertering

En klassisk demonstration af at løfte tilstand er to felter til temperaturindtastning, Celsius og Fahrenheit, som holdes synkroniserede. Hvert indtastningsfelt modtager den aktuelle temperatur og et onChange-tilbagekald. Den overordnede komponent gemmer temperaturen i én enhed, f.eks. Celsius, og konverterer den til det andet indtastningsfelt. Når et af felterne ændres, konverterer den overordnede komponent værdien og opdaterer sin tilstand. Begge felter modtager den korrekte værdi fra den overordnede komponents tilstand, så de holdes perfekt synkroniserede gennem den fælles tilstand.

import { useState } from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';

function TempInput({ label, value, onChangeText }) {
  return (
    <View style={styles.row}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        value={value}
        onChangeText={onChangeText}
        keyboardType='decimal-pad'
        style={styles.input}
      />
    </View>
  );
}

export default function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  const fahrenheit = celsius !== '' ? (parseFloat(celsius) * 9/5 + 32).toFixed(1) : '';

  function handleFahrenheitChange(f) {
    const c = f !== '' ? ((parseFloat(f) - 32) * 5/9).toFixed(1) : '';
    setCelsius(c);
  }

  return (
    <View style={{ padding: 24 }}>
      <TempInput label='Celsius' value={celsius} onChangeText={setCelsius} />
      <TempInput label='Fahrenheit' value={fahrenheit} onChangeText={handleFahrenheitChange} />
    </View>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
  label: { width: 100, fontSize: 16 },
  input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10 },
});

Placér tilstanden tæt på det sted, hvor den bruges

Det modsatte princip af at løfte tilstand er at holde tilstanden så lavt som muligt. Hvis kun én komponent har brug for et stykke tilstand, skal du beholde det inde i den komponent — løft det ikke unødvendigt. Et dropdown-elements åbne eller lukkede tilstand behøver f.eks. ikke at være i den overordnede skærmkomponent, medmindre den overordnede komponent skal reagere på den. Når lokal tilstand forbliver lokal, forbedres ydeevnen, fordi færre komponenter gengives igen, og koden bliver lettere at forstå, fordi tilstanden og brugergrænsefladen, der bruger den, er samlet samme sted. Løft kun tilstanden, når to eller flere komponenter reelt har brug for de samme data.

// Local state: Accordion open/close is private to Accordion
function Accordion({ title, children }) {
  const [open, setOpen] = useState(false); // stays local — parent doesn't care
  return (
    <View>
      <TouchableOpacity onPress={() => setOpen(prev => !prev)}>
        <Text>{title} {open ? '▲' : '▼'}</Text>
      </TouchableOpacity>
      {open && <View style={{ paddingLeft: 16 }}>{children}</View>}
    </View>
  );
}

// Only lift open state to parent IF the parent needs to:
// - Know which accordion is open to close others
// - Save/restore the open state on navigation
// Otherwise, keep it local!

Ukontrollerede kontra kontrollerede komponenter

En komponent er kontrolleret, når dens tilstand udelukkende styres af props og ændres via tilbagekald — som i eksemplet med søgefeltet. Den er ukontrolleret, når den håndterer sin egen interne tilstand uden at eksponere den for den overordnede komponent. Det meste af tiden i React bruger du kontrollerede komponenter, fordi de er forudsigelige og nemme at teste. Ukontrollerede indtastninger, hvor du bruger ref til at læse værdier, bruges lejlighedsvis af hensyn til ydeevnen, så du undgår nye gengivelser ved hvert tastetryk, eller til integration med kode, der ikke er skrevet i React. Standardelementerne til formularer i React Native's TextInput er kontrollerede, når du angiver value og onChangeText.

import { TextInput, useRef } from 'react-native';

// Uncontrolled: parent reads value on demand via ref
function UncontrolledInput({ inputRef }) {
  return (
    <TextInput
      ref={inputRef}
      // No value prop: TextInput manages its own text
      defaultValue='Initial text'
    />
  );
}

// Parent reads the value when the form is submitted:
function Form() {
  const inputRef = useRef(null);
  function handleSubmit() {
    // Use _lastNativeText (not recommended) or store value in ref.current
    console.log('Value:', inputRef.current?.props?.value);
  }
}

Virkeligt eksempel: Filter- og resultatrude

En praktisk anvendelse af løftet tilstand er en søgeskærm med en filterlinje (kategorivælger, skyder til prisinterval og sorteringsvælger) over en resultatliste. Hver filterværdi ligger i den overordnede SearchScreen-komponent. Filterlinjen modtager de aktuelle filterværdier og setter-callbacks som props. Resultatlisten modtager de aktuelle filtre og gengiver sig igen, hver gang et filter ændres. Den overordnede komponent henter også nye resultater, hver gang filtrene ændres (via useEffect med filterafhængigheder). Denne koordinering gennem én overordnet komponent gør søgefunktionen forudsigelig og nem at teste.

import { useState, useEffect } from 'react';
import { View } from 'react-native';

export default function SearchScreen() {
  // All filter state lifted here
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');
  const [sortBy, setSortBy] = useState('relevance');
  const [results, setResults] = useState([]);

  // Fetch results whenever any filter changes
  useEffect(() => {
    fetchResults({ query, category, sortBy }).then(setResults);
  }, [query, category, sortBy]);

  return (
    <View style={{ flex: 1 }}>
      <FilterBar
        query={query} onQueryChange={setQuery}
        category={category} onCategoryChange={setCategory}
        sortBy={sortBy} onSortChange={setSortBy}
      />
      <ResultsList results={results} />
    </View>
  );
}

Hurtigt tjek

Test din forståelse af begreberne inden for mobiludvikling med React Native fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at løftet tilstand i den nærmeste fælles overordnede komponent gør det muligt for søskendekomponenter at dele data gennem deres overordnede komponent, at kontrollerede komponenter modtager deres værdi og opdateringsfunktion som props, hvilket gør dem forudsigelige og nemme at teste, og at tilstanden bør ligge så lavt som muligt — løft den kun, når flere komponenter faktisk har brug for den. Dernæst bygger vi en interaktiv tællerapp, der kombinerer alle disse begreber.

Gratis at komme i gang

Lær JavaScript med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Flytning af state op” gratis?

Ja — alle 3 lektioner i læringssporet React Native Academy, inklusive “Flytning af state op”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. React Native Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Flytning af state op”?

Flyt delt state til den nærmeste fælles overordnede komponent, og videregiv både værdien og et callback til opdatering som props, så søskendekomponenter holdes synkroniserede. Du øver dig i React Native Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på React Native Academy?

Der kræves ingen tidligere erfaring. React Native Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Flytning af state op”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne React Native Academy-lektion?

Ja. Alle React Native Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Videregivelse af data med props
  2. Håndtering af komponenttilstand med useState
  3. Flytning af state op
  4. Opbygning af en interaktiv tællerapp
← Tilbage til React Native Academy