I et Next.js prosjekt er organiseringen av filene i "pages" mappen essensiell for å forstå hvordan ruting fungerer. Hver fil i denne mappen representerer en rute på nettstedet ditt. For eksempel vil filen pages/index.tsx representere hovedsiden av nettstedet ditt, og pages/about.tsx representerer en side som er tilgjengelig via ruten /about.

Når du oppretter en dynamisk rute, som for eksempel en bloggartikkel, kan du bruke firkantede parenteser for å indikere at denne filen er dynamisk. For eksempel vil filen pages/posts/[post].tsx håndtere alle blogginnlegg med ruter som /posts/1, /posts/2, og så videre. Next.js bruker verdien i firkantede parenteser som en parameter for å generere de nødvendige sidene dynamisk.

Når du har forstått hvordan filer og mapper brukes til å definere sider og ruter, er det viktig å merke seg at noen filer i "pages" mappen ikke nødvendigvis genererer faktiske sider, men er viktig for rammeverket. To slike filer er _document.tsx og _app.tsx.

Filen _document.tsx er essensiell for å tilpasse HTML-strukturen, spesielt for å få tilgang til <html> og <body>-taggene, og denne filen blir alltid rendret på serveren. På den annen side er _app.tsx filen som brukes til å initialisere sidene og legge til felles komponenter som en header på tvers av flere sider. Her kan du også koble til skript eller konfigurere et felles oppsett som skal brukes på tvers av sidene.

For å legge til en header i nettstedet ditt, kan du modifisere _app.tsx på denne måten:

tsx
const inter = Inter({ subsets: ["latin"] });
export default function App({ Component, pageProps }: AppProps) { return ( <> <header> <nav>
<a href="/">Home</a>
<a href="/posts">Posts</a>
<a href="/about">About</a>
</nav> </header> <Component {...pageProps} /> </> ); }

Denne koden gir et felles navigasjonsfelt på tvers av hele applikasjonen, slik at alle sider som bruker _app.tsx vil inkludere denne headeren.

Når det gjelder server-side rendering (SSR), er det viktig å forstå at Next.js kan generere HTML både under bygging og på forespørsel. På hovedsiden (pages/index.tsx), for eksempel, blir HTML generert på serveren under byggingen og returnert til nettleseren. Dette gir en raskere lastetid, ettersom HTML allerede er klar til å bli rendret uten at nettleseren må vente på JavaScript.

Men for dynamiske sider som krever oppdaterte data ved hver forespørsel, kan du bruke SSR med getServerSideProps. Dette er nyttig når du ønsker å hente data fra en ekstern API eller database på hver forespørsel. For eksempel kan du hente informasjon om en bruker fra GitHub på /about-siden som vist i eksempelet nedenfor:

tsx
export const getServerSideProps = async () => {
const res = await fetch("https://api.github.com/users/sakhnyuk");
const user = await res.json(); return { props: { user } }; };

Her hentes brukerdata fra GitHub og sendes som props til komponenten, og dermed kan informasjonen vises på siden når den lastes.

Videre finnes det mekanismer for statisk sidegenerering (SSG) og Incremental Static Regeneration (ISR), som lar deg bygge sider på forhånd og oppdatere dem periodisk uten at de må rendres på nytt ved hver forespørsel. Dette er nyttig for sider med innhold som ikke endres så ofte, som blogginnlegg.

For statisk sidegenerering kan du bruke getStaticProps, som genererer sider under byggingen av prosjektet:

tsx
export async function getStaticProps() {
const posts = ["1", "2", "3"]; return { props: { posts } }; } export default function Posts({ posts }: { posts: string[] }) { return ( <div> <h1>Posts</h1> {posts.map((post) => (
<div key={post}>Post {post}</div>
))}
</div> ); }

På den dynamiske ruten /posts/[post].tsx kan du bruke getStaticPaths for å spesifisere hvilke sider som skal genereres på forhånd. For eksempel:

tsx
export const getStaticPaths = async () => { return { paths: [ { params: { post: "1" } }, { params: { post: "2" } }, { params: { post: "3" } }, ], fallback: true, }; };

I dette tilfellet vil Next.js bygge sidene for de tre spesifiserte innleggene under byggingen, og for alle andre innlegg vil det bli brukt SSR (server-side rendering) når de blir besøkt.

Slike tilnærminger gir stor fleksibilitet og effektivitet i hvordan innhold kan håndteres og vises på nettsteder, noe som gjør Next.js et kraftig rammeverk for både statiske og dynamiske nettsteder.

Det er viktig å merke seg at bruken av getStaticProps og getServerSideProps kan påvirke ytelsen til nettstedet ditt, spesielt når det gjelder tid brukt på serveren for å hente og generere data. For mer komplekse prosjekter, hvor du kanskje har store mengder data eller hyppige oppdateringer, kan du vurdere å bruke en mellomliggende løsning som ISR, som lar deg balansere mellom forhåndsbygde sider og oppdateringer på forespørsel.

Hvordan lage API-forespørsler i React Native

React Native gir en kraftig og praktisk måte å lage mobile applikasjoner på, hvor mye av logikken kan gjøres på samme måte som i webapplikasjoner. Et viktig aspekt ved dette er hvordan man håndterer API-forespørsler. Heldigvis er fetch()-API-en polyfylt i React Native, noe som betyr at nettverkskoden i mobile applikasjoner kan se ut og føles på samme måte som i webapplikasjoner.

Når du bygger en mobilapplikasjon, kan du bruke fetch()-metoden for å hente data fra API-er. Her er et eksempel på hvordan du kan bygge en enkel API som bruker funksjoner som returnerer løfter, på samme måte som fetch() gjør. Vi starter med å lage en mock-API for en liste med elementer:

javascript
const items = new Array(100).fill(null).map((v, i) => `Item ${i}`);
function filterAndSort(data: string[], text: string, asc: boolean) { return data .filter((i) => text.length === 0 || i.includes(text)) .sort( asc
? (a, b) => (b > a ? -1 : a === b ? 0 : 1)
:
(a, b) => (a > b ? -1 : a === b ? 0 : 1) ); }
export function fetchItems(filter: string, asc: boolean): Promise<{ json: () => Promise<{ items: string[] }> }> {
return new Promise((resolve) => { resolve({
json: () => Promise.resolve({
items: filterAndSort(items, filter, asc), }), }); }); }

Med denne mock-API-funksjonen på plass, kan vi nå gjøre endringer i ListContainer-komponenten. I stedet for å bruke lokale datakilder, kan vi bruke fetchItems() for å hente data fra mock-API-et:

javascript
export default function ListContainer() { const [asc, setAsc] = useState(true); const [filter, setFilter] = useState(""); const [data, setData] = useState([]); useEffect(() => { fetchItems(filter, asc) .then((resp) => resp.json()) .then(({ items }) => { setData(mapItems(items)); }); }, []); }

Her bruker vi useState og useEffect hooks for å hente data for listen. Nå kan vi bruke disse nye håndtererne i List-komponenten for å oppdatere listen dynamisk.

Lazy List Loading (Lat innlasting av lister)

Når det gjelder mobilapplikasjoner, er det viktig å kunne håndtere store mengder data effektivt. Noen ganger vet ikke brukeren hva de ser etter, og derfor er filtrering eller sortering ikke alltid nyttig. I slike tilfeller er uendelig rulling en effektiv løsning. Tenk på Facebooks nyhetsfeed, hvor du bare ruller nedover for å se nytt innhold uten nødvendigvis å lete etter noe spesifikt.

I React Native kan du implementere uendelig rulling ved hjelp av FlatList-komponenten. Her kan du hente mer API-data når brukeren ruller til slutten av listen. Ved hjelp av generatorer kan vi simulere en uendelig datakilde:

javascript
function* genItems() { let cnt = 0; while (true) { yield `Item ${cnt++}`; } } let items = genItems();
export function fetchItems({ refresh }: { refresh?: boolean }) {
if (refresh) { items = genItems(); } return Promise.resolve({ json: () => Promise.resolve({
items: new Array(30).fill(null).map(() => items.next().value as string),
}), }); }

Når du bruker fetchItems(), kan du gjøre API-forespørsler for nye data hver gang brukeren når slutten av listen. Dette gir deg muligheten til å implementere uendelig rulling i applikasjonen din.

Pull-to-Refresh (Trekke for å oppdatere)

En annen populær gest på mobile enheter er "pull-to-refresh", som gjør det mulig for brukeren å oppdatere innholdet i en liste uten å måtte åpne appen på nytt. Dette er en funksjon som har blitt svært populær siden den ble introdusert av Loren Brichter i 2009 i appen Tweetie (senere Twitter for iPhone).

I React Native kan du implementere denne funksjonen med FlatList-komponenten. Ved å bruke onRefresh og refreshing-propsene kan vi aktivere pull-to-refresh gesten:

javascript
export default function List({ data, fetchItems, refreshItems, isRefreshing }: Props) {
return ( <FlatList data={data} renderItem={({ item }) => <Text>{item.value}</Text>} onEndReached={fetchItems} onRefresh={refreshItems} refreshing={isRefreshing} /> ); }

I ListContainer kan vi håndtere tilstanden for oppfrisking:

javascript
const [isRefreshing, setIsRefreshing] = useState(false);
function fetchItems() { return api .fetchItems({}) .then((resp) => resp.json()) .then(({ items }) => { setData([ ...data, ...items.map((value) => ({ key: value, value, })), ]); }); } function refreshItems() { setIsRefreshing(true); fetchItems() .finally(() => setIsRefreshing(false)); }

Med dette oppsettet kan brukeren dra ned på listen for å oppdatere innholdet, og appen vil hente nye data fra API-et.

Endelig er det viktig å merke seg at effektiv håndtering av data i mobilapplikasjoner, spesielt ved bruk av API-er, krever at man tar hensyn til ytelse og brukeropplevelse. Selv om uendelig rulling og pull-to-refresh gir brukeren en dynamisk opplevelse, er det viktig å implementere mekanismer som håndterer tilstanden på en effektiv måte, spesielt når applikasjonen begynner å håndtere store mengder data.

Hvordan synkronisere lokal lagring med eksterne API-er i offline modus

Når vi utvikler applikasjoner som er avhengige av nettverksforbindelse, er det viktig å sikre at de fortsatt fungerer selv når enheten er offline. Dette gjelder spesielt for apper som lagrer data som kan bli brukt senere, eller som gjør nettverksforespørsler som må synkroniseres når nettverket er tilgjengelig. En løsning på dette problemet er å bruke lokal lagring for å midlertidig lagre data og deretter synkronisere det med eksterne tjenester når forbindelsen er gjenopprettet.

I denne sammenhengen er AsyncStorage i React Native et nyttig verktøy. Dette er en enkel mekanisme for å lagre data lokalt på en enhet, og det kan brukes for å håndtere tilfeller der enheten mister nettverksforbindelsen. For å gjøre bruken av AsyncStorage mer fleksibel og robust, er det viktig å abstrahere bort direkte kall til lagring og nettverksoperasjoner, slik at utvikleren kan håndtere tilkoblingsstatus og synkronisering på en mer elegant måte.

Grunnprinsippet for å synkronisere data er å ha en modul som kan sjekke nettverkstilkoblingen og bestemme hvordan data skal lagres, avhengig av om enheten er online eller offline. Når enheten er offline, kan endringer lagres lokalt ved hjelp av AsyncStorage. Når forbindelsen gjenopprettes, kan disse endringene synkroniseres med det eksterne API-et.

Et godt eksempel på en slik løsning kan illustreres gjennom en enkel modul som håndterer både innsetting og henting av data. Funksjonen set sjekker om enheten er tilkoblet til nettverket, og hvis den er det, blir dataene synkronisert med en falsk nettverksdatabase (representert her med objektet fakeNetworkData). Hvis enheten er offline, lagres dataene i AsyncStorage, og nøkkelen blir lagt til en liste over usynkroniserte data som vil bli synkronisert senere.

typescript
export function set(key: Key, value: boolean) {
return new Promise((resolve, reject) => { if (connected) { fakeNetworkData[key] = value; resolve(true); } else {
AsyncStorage.setItem(key, value.toString()).then(
() => { unsynced.push(key); resolve(false); }, (err) => reject(err) ); } }); }

Modulen get fungerer på en lignende måte. Den henter data enten fra nettverket eller fra lokal lagring, avhengig av tilkoblingens status. Hvis enheten er offline og en spesifikk nøkkel er oppgitt, vil get forsøke å hente dataene fra AsyncStorage. Hvis ingen nøkkel er gitt, hentes alle lagrede data.

typescript
export function get(key?: Key): Promise<any> {
return new Promise((resolve, reject) => { if (connected) { resolve(key ? fakeNetworkData[key] : fakeNetworkData); } else if (key) { AsyncStorage.getItem(key)
.then((item) => resolve(item === "true"))
.
catch((err) => reject(err)); } else { AsyncStorage.getAllKeys() .then((keys) => AsyncStorage.multiGet(keys).then((items) =>
resolve(Object.fromEntries(items) as any)
) ) .
catch((err) => reject(err)); } }); }

For å sikre at endringer som gjøres offline blir synkronisert når enheten er tilkoblet igjen, bruker vi en hendelseslytter som reagerer på endringer i nettverkstilkoblingen. Når forbindelsen gjenopprettes, blir usynkroniserte data fra AsyncStorage sendt til serveren for synkronisering.

typescript
NetInfo.addEventListener((connection) => {
connected = ["wifi", "unknown"].includes(connection.type); if (connected && unsynced.length) {
AsyncStorage.multiGet(unsynced).then((items) => {
items.
forEach(([key, val]) => set(key as Key, val === "true")); unsynced.length = 0; }); } });

Dette gir en sømløs opplevelse for brukeren, hvor appen kan fungere uten internettforbindelse, samtidig som den holder data synkronisert med den eksterne serveren så snart forbindelsen er gjenopprettet.

Det er viktig å merke seg at nettverksstatusen alltid må sjekkes før forsøk på å hente data. Hvis nettverksstatusen ikke er riktig håndtert, vil appen anta at enheten er offline, selv om det faktisk kan være en fungerende tilkobling. Dette kan føre til uventet oppførsel, der data feilaktig blir hentet fra lokal lagring i stedet for nettverket.

En annen viktig betraktning er at dette systemet kan være nyttig ikke bare for apper som er avhengige av API-kall, men også for apper som trenger å lagre data lokalt, for eksempel for bruk når enheten er i områder med dårlig eller ingen nettverkstilkobling. AsyncStorage gir en generell lagringsløsning som kan brukes til å lagre alt fra brukerpreferanser til applikasjonens tilstand.

Å implementere en offline-lagringsmekanisme som dette gir utviklere en kraftig måte å sikre at applikasjonen forblir funksjonell under alle forhold, samtidig som den gir en bedre brukeropplevelse. Når synkroniseringen av data skjer automatisk og i bakgrunnen, kan appen gi en opplevelse som er både pålitelig og brukervennlig, selv uten konstant nettverksforbindelse.