01/11/2022
I den moderne digitale tidsalder står mange udviklingsteams over for det samme dilemma: hvordan man reducerer udviklingstid og omkostninger, samtidig med at man bygger applikationer, der fungerer fejlfrit på både mobile enheder og webbrowsere. Dette er en udfordring, der kræver en strategisk tilgang og innovative løsninger. Som mobiludviklere har vi altid været fascineret af at finde måder at genbruge den kode, vi skriver til mobilapps, til at skabe en fuldt funktionel webapp. I lang tid var dette blot en teoretisk overvejelse, indtil et projekt som 'Lili' opstod og skabte et konkret behov for en sådan løsning.

Efter lanceringen af den første version af Lili’s mobilapp i september 2020, opstod ønsket om også at udvikle en webapp. Da Lili’s mobilapp var skrevet i React Native, var den umiddelbare tanke at udvikle webappen med ReactJS. Dette ville dog stadig betyde to forskellige kodebaser, hvilket ville modarbejde målet om effektivitet og kode-genbrug. Samtidig var Flutter begyndt at vokse hurtigt og fremviste imponerende krydsplatformsevner. For at sikre det bedste valg besluttede teamet at undersøge Flutter nærmere, og en betaversion af Lili’s webapp blev endda bygget med Flutter.
Det blev dog hurtigt klart, at både React Native og Flutter havde deres fordele og ulemper. Flutter var en god løsning, men ikke det “mirakel”, som man i hemmelighed havde håbet på. Det afgørende argument i denne situation var teamets erfaring: med tre års erfaring med React Native mod kun tre måneder med Flutter, var valget klart – React-økosystemet skulle fortsat være fundamentet. Målet var nu at finde en måde at dele så meget kode som muligt mellem mobil og web. Det var under denne søgning, at en interview fra Microsoft Developer YouTube-kanal, hvor Robin Heinze forklarede, hvordan hun håndterede kode-deling mellem React Native- og ReactJS-projekter ved hjælp af symbolske links, blev den afgørende “gnist”. Denne simple, men effektive detalje, banede vejen for en ny version af Lili, der skulle skrives i React Native for både mobil og web.
- Hvorfor dele kode mellem mobil og web?
- React Native Web: Løsningen på krydsplatformsudvikling
- Trin for trin: Opsætning af et React Native Web-projekt
- Håndtering af platformsspecifikke forskelle
- Fordele ved at bruge React Native Web
- Store virksomheder, der bruger React
- Hvorfor vælger giganterne React?
- Ofte Stillede Spørgsmål (FAQ)
Hvorfor dele kode mellem mobil og web?
At opretholde separate kodebaser for mobil- og webapplikationer er ikke kun tidskrævende, men også dyrt. Hver platform kræver sit eget udviklingsteam, sine egne tests og sin egen vedligeholdelse. Dette fører til øgede omkostninger og forsinkelser i lanceringsprocessen. Ved at dele kode kan udviklingsteams opnå en række fordele:
- Reduceret udviklingstid: Skriv kode én gang, og brug den flere steder.
- Lavere omkostninger: Mindre udviklingstid betyder færre ressourcer.
- Ensartet brugeroplevelse: En fælles kodebase hjælper med at sikre, at applikationen ser ud og opfører sig ens på tværs af platforme.
- Nemmere vedligeholdelse: Fejlrettelser og nye funktioner skal kun implementeres ét sted for at påvirke alle platforme.
Lili-projektet er et perfekt eksempel på, hvordan denne tilgang kan løse reelle forretningsproblemer. Ved at vælge at udnytte eksisterende viden om React Native og inkorporere React Native Web, kunne teamet effektivt genbruge kode, mindske den samlede indsats og levere en samlet løsning hurtigere.
React Native Web: Løsningen på krydsplatformsudvikling
React Native Web er en bibliotek, der gør det muligt at køre React Native-komponenter og API'er direkte i en webbrowser. Det oversætter de native mobilkomponenter (som <View> og <Text>) til deres webækvivalenter (som <div> og <span>), hvilket muliggør en hidtil uset grad af kode-genbrug mellem mobil og web. Dette betyder, at du kan skrive din logik og dine UI-komponenter i React Native og derefter bruge dem – med minimale justeringer – på en webplatform.
Det centrale princip bag denne tilgang er brugen af symbolske links, der gør det muligt for webprojektet at “se” og importere filer fra mobilprojektets delte kodebase. Dette skaber en monorepo-lignende struktur, hvor al den delte kode bor ét sted, men kan tilgås fra flere separate projekter (mobil, web).
Trin for trin: Opsætning af et React Native Web-projekt
Lad os gennemgå de nødvendige trin for at oprette et projekt, der understøtter iOS, Android og webbrowsere, baseret på den metode, der blev brugt til Lili-appen. Til mobilnavigation foretrækkes react-native-navigation fra Wix, som giver en agil og native-lignende navigationsoplevelse. I dette eksempel vil vi bruge React Native version 0.71.11, da react-native-navigation er kompatibel med denne version.
1. Oprettelse af mobilappen
Først opretter vi vores mobile applikation. Naviger til den mappe, hvor du ønsker at oprette dit projekt, og udfør følgende kommandoer:
mkdir demo_app_web cd demo_app_web npx [email protected] init onthebeach_mobile_app --version 0.71.11Dette vil oprette din mobilapp med React Native version 0.71.11. Du kan derefter starte den med:
cd onthebeach_mobile_app yarn ios yarn android2. Strukturering af mobilprojektet og en delt komponent
For at forberede projektet til kode-deling, opretter vi en logisk mappestruktur. I rodmappen af dit mobilprojekt (onthebeach_mobile_app) opretter du en app-mappe. Inden i denne opretter du to undermapper: shared (til kode, der skal deles mellem mobil og web) og specific (til platformsspecifik kode).
mkdir app cd app mkdir shared mkdir specific cd shared mkdir components cd components mkdir Splashscreen cd Splashscreen touch Splashscreen.tsxNu redigerer vi Splashscreen.tsx-filen for at oprette en simpel splashscreen. Denne komponent vil fungere som vores delte kode-eksempel.
import React from "react"; import { Dimensions, Image, Platform, Text, View } from "react-native"; interface SplashscrenProps { } const Splashscreen = (props: SplashscrenProps) => { return ( <View style={{flex:1, justifyContent:"center", alignItems:"center", backgroundColor:"#F56565"}}> <Image source={{uri:"https://onthebeach.dev/images/OtbLogo.png"}} style={{width:100, height:100}} resizeMode="contain" /> <Text style={{ color:"#FFF", fontSize:28, marginTop:10 }}>{"OnTheBeach.dev"}</Text> <View style={{position:"absolute", bottom:0, width:Dimensions.get("window").width, alignItems:"center"}}> <Text style={{ color:"#FFF", fontSize:14, paddingBottom:10 }}>{"Version "+Platform.OS+" - Demo"}</Text> </View> </View> ) } export default Splashscreen;Bemærk brugen af Platform.OS, som automatisk returnerer den aktuelle platform (f.eks. “ios”, “android” eller “web”), hvilket er nyttigt til at bekræfte, at koden fungerer korrekt på webversionen.
3. Oprettelse af webappen
I stedet for at bruge et værktøj som create-react-app, opretter vi webappens filer manuelt. Gå tilbage til rodmappen demo_app_web og opret webapp-mappen:
cd .. mkdir onthebeach_web_app cd onthebeach_web_app touch index.html index.tsx tsconfig.jsonInitialiser derefter projektet med yarn init og udfyld de nødvendige oplysninger (f.eks. entry point (index.js): index.tsx).

Installer de nødvendige afhængigheder. Vi bruger Parcel som bundler, da den “bare virker” og er enklere at konfigurere end Webpack til dette formål:
yarn add -D parcel-bundler typescript yarn add react react-domNu skal vi konfigurere vores filer:
index.html:
<html> <body> <div id="root"></div> <script src="./index.tsx"></script> </body> </html>index.tsx (før integration med React Native Web):
import React from "react"; import { render } from "react-dom"; const App = () => { return ( <div> <p>This is my web app build with React</p> </div> ) } render(<App />, document.getElementById("root"));tsconfig.json:
{ "compilerOptions": { "jsx": "react", "lib": ["es6", "dom"], "target": "esnext", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "moduleResolution": "node", "noEmit": true, "skipLibCheck": true }, "exclude": ["./node_modules"] }Til sidst, tilføj Parcel-scripts til din package.json:
{ "name": "onthebeach_web_app", "version": "1.0.0", "description": "OnTheBeach Web App written with React Native", "main": "index.tsx", "author": "Axel de Sainte Marie", "license": "MIT", "private": true, "devDependencies": { "parcel-bundler": "^1.12.5", "typescript": "^5.1.6" }, "dependencies": { "@types/react-native": "^0.72.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, "scripts": { "start": "parcel serve ./index.html", "build": "parcel build ./index.html" } }Du kan nu starte webappen med yarn start og se den simple React-side.
4. Integration af React Native Web og deling af kode
Nu kommer “magien”. Installer React Native Web og dets TypeScript-definitioner:
yarn add react-native-web @types/react-nativeDerefter skal du tilføje et alias i din package.json-fil. Dette fortæller Parcel (og andre bundlere), at når koden importerer “react-native”, skal den i stedet bruge “react-native-web”:
{ // ... (resten af din package.json) "alias": { "react-native": "react-native-web" } }Opret en src-mappe i din webprojekts rod (onthebeach_web_app) til at holde din web-specifikke kode. Inde i denne src-mappe opretter du et symbolsk link til den delte kode fra dit mobilprojekt:
mkdir src cd src ln -s ../../onthebeach_mobile_app/app/shared sharedDette link gør, at src/shared i dit webprojekt peger direkte på onthebeach_mobile_app/app/shared. Nu kan du importere komponenter fra denne delte mappe i din webapp.
Rediger din index.tsx-fil i webprojektet til at importere og rendere din delte Splashscreen-komponent:
import React from "react"; import { render } from "react-dom"; import Splashscreen from "./src/shared/components/Splashscreen/Splashscreen"; render(<Splashscreen />, document.getElementById("root"));Hvis du nu starter webappen (yarn start), vil du se din React Native Splashscreen i browseren. Du vil dog bemærke, at flex:1-stilen ikke udfylder skærmen, som den gør på mobil. Dette skyldes, at webbrowsere ikke automatisk giver root-elementer en højde. En hurtig løsning er at bruge height:Dimensions.get("window").height, men dette vil ikke tilpasse sig, hvis browseren ændrer størrelse. Den bedre løsning er at bruge procentbaserede højder eller vh/vw-enheder for at opnå en dynamisk tilpasning:
<View style={{flex:1, justifyContent:"center", alignItems:"center", backgroundColor:"#F56565", height:"100%"}}> <Image source={{uri:"https://onthebeach.dev/images/OtbLogo.png"}} style={{width:100, height:100}} resizeMode="contain" /> <Text style={{ color:"#FFF", fontSize:28, marginTop:10 }}>{"OnTheBeach.dev"}</Text> <View style={{position:"absolute", bottom:0, width:Dimensions.get("window").width, alignItems:"center"}}> <Text style={{ color:"#FFF", fontSize:14, paddingBottom:10 }}>{"Version "+Platform.OS+" - Demo"}</Text> </View> </View>Med height:“100%” vil skærmen nu dynamisk justere sin højde, når browservinduet ændres.
Håndtering af platformsspecifikke forskelle
Selvom React Native Web muliggør en betydelig grad af kode-genbrug, er det vigtigt at forstå, at ikke alt kan deles 1:1. Hver platform har sine egne nuancer og specifikke krav. Den største udfordring ligger i at identificere, hvilken kode der kan deles, og hvilken der skal være specifik for hver platform.
- Navigation: Biblioteker som
react-native-navigationer specifikke for mobile enheder og kan ikke bruges til webnavigation. Hver platform vil kræve sin egen navigationsmetode, ofte placeret i de platformsspecifikke mapper (app/specific/mobileogsrc/specific/web). - Komponentadfærd: Nogle React Native-komponenter fungerer på begge platforme, men er ikke altid optimale for en webapp. Et klassisk eksempel er
ScrollView. På mobil er det nødvendigt at brugeScrollViewfor at aktivere scrolling, hvis indholdet overstiger skærmhøjden. På web er scrolling automatisk, hvis indholdet strækker sig ud over skærmens bund. Hvis du tilføjer en vertikalScrollViewi en webapp, kan du ende med to konkurrerende scrollbars, hvilket forårsager uønsket adfærd. Sådanne adfærd skal studeres nøje ved udvikling af visninger. - Platform-specifikke API'er: Nogle API'er eller native moduler er kun tilgængelige på enten mobil eller web. Brug af
Platform.OSer afgørende for at håndtere disse forskelle i din delte kode. Du kan f.eks. konditionelt rendere komponenter eller kalde funktioner baseret på den aktuelle platform. - Tredjepartsbiblioteker: Hver gang du tilføjer et nyt tredjepartsbibliotek, skal du kontrollere dets kompatibilitet med både mobil og web. Nogle biblioteker er rent JS og kan bruges i den delte kode (f.eks.
react-native-event-listeners), mens andre har native afhængigheder, der gør dem uegnede til web.
En succesfuld krydsplatform-strategi kræver omhyggelig planlægning og en bevidst beslutning om, hvad der kan deles, og hvad der skal implementeres specifikt for hver platform. Det handler om at maksimere genbrug, hvor det giver mening, og samtidig sikre en optimal brugeroplevelse på hver enkelt platform.
Fordele ved at bruge React Native Web
Efter et års produktion af Lili-appen er det bevist, at denne løsning fungerer. Den fortsatte forbedring af kodebasen og webappen viser, at der dagligt findes nye “tricks” og optimeringer. Hvis du allerede er bekendt med React Native og ønsker at kode et multiplatformsprojekt, er React Native Web et forsøg værd. Du vil hurtigt kunne arbejde mere effektivt og spare tid på vedligeholdelse af dine projekter.
Tabel: Sammenligning af platformsspecifikke aspekter
| Aspekt | Mobil-specifik (React Native) | Delt kode (React Native Web) | Web-specifik (React) |
|---|---|---|---|
| Navigation | React-Native-Navigation, React Navigation | Begrænset/kræver web-router | React Router DOM, Next.js routing |
| UI-komponenter | View, Text, Image, FlatList, ScrollView (native) | View, Text, Image (oversat til DOM) | div, span, img, standard HTML-elementer |
| Styling | Flexbox, Dimensions, StyleSheets | Flexbox, %-baserede højder, CSS-in-JS | CSS, Flexbox, Grid, Styled Components |
| Native Moduler | Direkte adgang til native API'er (kamera, GPS) | Kræver polyfills eller web-specifikke implementeringer | Web API'er (Geolocation API, WebRTC) |
| Ydeevne | Optimeret til native ydeevne | God ydeevne, men kan kræve web-specifikke optimeringer | Optimeret til browser-ydeevne |
Store virksomheder, der bruger React
ReactJS har revolutioneret måden, hvorpå brugergrænseflader bygges, og dets popularitet afspejles i det store antal internetgiganter, der anvender det. Selvom ikke alle bruger React Native Web direkte, understreger deres brug af React og React Native den styrke og fleksibilitet, der ligger i React-økosystemet, hvilket gør det til et logisk valg for krydsplatform-udvikling.
- Facebook: ReactJS blev oprindeligt skabt af Facebook. Deres websted bruger React til nyhedsfeed, profiler, notifikationer og andre interaktive komponenter. Deres mobilapp er bygget med React Native, som er en version af React optimeret til iOS- og Android-enheder.
- Instagram: Instagram er bygget med ReactJS og udnytter dets funktioner til geolocation, Google Maps API'er og søgemaskinepræcision. Deres appdrager fordel af Reacts envejsdataflow og ydeevneoptimeringer.
- Netflix: Netflix bruger React på deres “Gibbon”-platform til lavtydende TV-enheder, og de har offentligt talt om Reacts fordele for opstartshastighed, køretidsydelse og modularitet.
- WhatsApp: WhatsApp Web og desktopversionen (bygget med Electron) bruger React til deres dynamiske og interaktive brugergrænseflader, hvilket er essentielt for realtidschat-applikationer.
- The New York Times: Har brugt React til specifikke projekter, såsom en interaktiv galleri-applikation.
- Dropbox: Har udnyttet React til at forbedre sin webplatform betydeligt, hvilket resulterer i en mere effektiv og brugervenlig applikation.
- BBC: Har forbedret BBC iPlayer og migreret BBC News-webstedet til at bruge Next.js (et React-framework) for at forbedre ydeevne og tilgængelighed.
Hvorfor vælger giganterne React?
Internettets giganter vælger React af mange gode grunde, der alle bidrager til en mere effektiv udviklingsproces og en bedre brugeroplevelse:
- Komponentbaseret tilgang: React fremmer en komponentbaseret arkitektur, hvor UI er opdelt i genanvendelige dele. Dette modulære design gør kodebasen lettere at administrere og genbruge, hvilket fører til konsistente designmønstre.
- Genanvendelighed: UI-komponenter kan genbruges på tværs af forskellige dele af applikationen, hvilket sparer udviklingstid og sikrer et ensartet udseende.
- Effektive opdateringer med Virtual DOM: React bruger et Virtual DOM (Document Object Model), en in-memory repræsentation af det faktiske DOM. Når data ændres, beregner React forskellen og opdaterer kun de nødvendige dele af det faktiske DOM. Dette minimerer DOM-manipulationer og forbedrer renderingsydelsen, hvilket giver en hurtigere og mere flydende brugeroplevelse.
- Deklarativ syntaks: Reacts deklarative syntaks giver udviklere mulighed for at beskrive den ønskede UI-tilstand og hvordan den skal ændres over tid. Dette er mere intuitivt end imperativ manipulation og fører til renere og mere vedligeholdelsesvenlig kode.
- State management: React opmuntrer til en “single source of truth” for applikationens tilstand, hvilket sikrer, at UI nøjagtigt afspejler de underliggende data og forhindrer inkonsekvenser.
- Udviklerværktøjer: React leveres med kraftfulde udviklerværktøjer, der hjælper med at inspicere komponenthierarkier, spore tilstandsændringer og debugge UI-relaterede problemer, hvilket fremskynder fejlfinding.
- Ydeevneoptimering: Biblioteket tilbyder teknikker som lazy loading, code splitting og memoization, der hjælper med at optimere ydeevnen, når applikationen vokser, og sikrer en responsiv brugergrænseflade.
- Krydsplatformskonsistens: Med React Native kan den komponentbaserede tilgang udvides til at bygge krydsplatform-applikationer til både web og mobil, hvilket giver en velkendt og intuitiv brugeroplevelse på tværs af enheder.
- Cloud-kompatibel arkitektur: Reacts modulære arkitektur er velegnet til cloud-miljøer, hvilket muliggør udvikling af lette, modulære applikationer, der let kan skaleres inden for en cloud-infrastruktur.
- Mikrotjeneste-kompatibilitet: Reacts modulære natur passer godt sammen med mikrotjeneste-arkitekturen, hvilket gør det lettere at udvikle og vedligeholde separate mikrotjenester, samtidig med at en sammenhængende brugergrænseflade bevares.
- Integrationsfleksibilitet: Dens evne til at integrere problemfrit med forskellige arkitekturer og dens kompatibilitet med andre frameworks og biblioteker er en nøglefordel for virksomheder med komplekse teknologiske stack.
Ofte Stillede Spørgsmål (FAQ)
Kan jeg genbruge 100% af min React Native-kode på web?
Nej, det er sjældent muligt at genbruge 100% af koden. Selvom React Native Web muliggør betydelig kode-genbrug, især for UI-komponenter og forretningslogik, vil platformsspecifikke funktioner som navigation, adgang til native enheds-API'er (kamera, GPS) og visse UI-interaktioner ofte kræve separate implementeringer. Målet er at maksimere genbrug, hvor det giver mening, og optimere de platformsspecifikke dele for den bedste brugeroplevelse.
Hvilke stylingforskelle skal jeg være opmærksom på med React Native Web?
En væsentlig forskel er håndteringen af dimensioner og layout. I React Native bruges flex:1 ofte til at få en komponent til at udfylde den tilgængelige plads, og Dimensions.get("window").height giver enheds højden. På web kan flex:1 alene ikke altid garantere, at en komponent fylder hele skærmen, medmindre dens forælder også har en defineret højde. Det anbefales ofte at bruge procentbaserede højder (f.eks. height:"100%") eller CSS-enheder som vh (viewport height) for at sikre responsivt design på web.
Er React Native Web egnet til store, komplekse applikationer?
Ja, absolut. Projekter som Lili-appen og mange andre viser, at React Native Web er en robust løsning til store og komplekse applikationer. Det kræver dog en omhyggelig arkitekturplanlægning, hvor der tages højde for delte og platformsspecifikke komponenter. Den indledende investering i at sætte den rigtige mappestruktur og alias-konfiguration op betaler sig hurtigt tilbage i form af reduceret vedligeholdelse og hurtigere feature-udvikling på tværs af platforme.
Hvis du vil læse andre artikler, der ligner Del React Native-kode på tværs af mobil og web, kan du besøge kategorien Teknologi.
