Zephyrnet-logotyp

Vägen till en skalbar och underhållbar React frontend-arkitektur

Datum:

Modern frontend-arkitektur — 101

För några veckor sedan var mina kollegor nyfikna på vår front-end-arkitektur och projektstruktur. Efter några små presentationer tänkte jag att det skulle vara en bra idé att avsluta det hela och dela med mig av min inställning här.

Motivation

Moderna ramverk och bibliotek gör det enkelt för oss att fokusera på återanvändbarhet när vi utvecklar UI-komponenter. Detta är ett mycket viktigt koncept för att utveckla underhållsbara front-end-applikationer. Under loppet av många projekt har jag upptäckt att det ofta inte räcker med att bara bygga återanvändbara komponenter. Ansökningar kan bli ohållbara om kraven ändras avsevärt eller nya krav kommer upp.
Det tog mer och mer tid att hitta rätt filer eller att felsöka något.

Visst, jag kan träna upp min minnesförmåga eller lära mig att använda Visual Studio Code ännu bättre. Men jag är mest en del av ett team, så jag är inte den enda som jobbar med koden. Så vi måste förbättra upplägget av våra projekt för alla. Det betyder att vi bör göra dem mer underhållsbara och mer skalbara. Syftet är att göra ändringar i nuvarande funktionalitet så snabbt som möjligt, men också att kunna utveckla nya funktioner snabbare.

Utsikten uppifrån

Som fullstack-utvecklare kommer jag ursprungligen från backend-sidan.
Där kan man hitta många arkitektoniska tillvägagångssätt som vi kan använda oss av. Två mycket använda begrepp är "Domändriven utveckling" och "Separation of Concerns".
De är också ganska användbara i front-end-arkitekturen.
"DDD" är ett tillvägagångssätt där man försöker skapa grupper som är tekniskt relaterade. Med "SoC" separerar vi ansvar och beroenden för logik, användargränssnitt och data från varandra.

0*hTN6qVaxMhD-Sz2r.webp

Frontend-arkitekturbasen

När en användare interagerar med vår webbapplikation dirigerar appen dem till rätt modul. Varje modul innehåller vissa funktioner och affärslogik. Dessutom har varje modul möjlighet att kommunicera med backend med applikationslagret (http/s) via ett API (REST, GraphQL).

React Project-setup

Om vi ​​vill sätta upp vår React-projektstruktur kan vi lita på strukturen som visas nedan. Hela koden för applikationen finns i appkatalogen. Varje modul, som till största delen motsvarar en rutt, finns i moduler-mappen.
Återanvändbara UI-komponenter, av vilka de flesta inte innehåller någon speciell logik, finns i komponentkatalogen.
Alla konfigurationer som redux-mellanprogram, axios för förfrågningar eller konstanter finns i config-mappen. I tjänster hittar man affärslogikfunktioner som används av moduler. Modellgränssnitt, stöd för flera språk, globala redux-reducerare eller hjälpfunktioner som inte är särskilt specifika finns i den delade katalogen. JSON:erna för flera språk men även textsträngar finns i mappen i18n.

app/ > config/ > components/ > services/ > modules/ > shared/
content/ > css/ > img/ > fonts/ > js/
cypress/
i18n/
webpack/
.gitignore
package.json
tsconfig.json
Readme.md

Innehållskatalogen innehåller våra tillgångar (t.ex. bilder, typsnitt eller JS-kod från tredje part). Man bör alltid använda ett testramverk (här Cypress), som finns i sin egen katalog.
Webpack används som en tillgångsbuntare. De återstående .json-filerna är konfigurationer. Naturligtvis vill vi inte glömma vår readme.

Låt oss kolla in detaljerna

Med den grundläggande arkitekturen och projektstrukturen har vi en bra grund. Nu behöver vi mer detaljer för att implementera frontend-arkitekturen.
Det är alltid bra att skapa en komponentarkitektur.
Jag gillar att använda draw.io för detta.
Där namnger vi de enskilda komponenterna och modulerna.
Denna del är mycket viktig, för här ser vi hierarkin och användningen av komponenterna. Jag försöker visuellt separera komponenter och modulspecifika komponenter som har använts flera gånger (streckad vs normal linje). Det är också en bra idé att lägga till mellanprogram eller ytterligare funktioner här.

0*sXZlYGnjFJlXyPqQ.webp

Moduler är stora UI-komponenter med logik. Andra moduler kan använda komponenter, men inte andra moduler. Om olika moduler vill kommunicera med varandra använder vi Redux. Varje modul består av huvudkomponenten, en logikfil och en reducerfil. Hela vår verksamhet och UI-logik kan hittas i kroken för logiken. I reduceraren kan vi placera Redux-implementeringen och alla API-anrop.

app/ > modules/ > shop/ > shop.tsx > shop.logic.ts > shop.reducer.ts

Genom att använda hook-standarderna i React lägger vi ut hela funktionaliteten till logiken och använder sedan denna hook i vår modulkomponent.
Komponenten i butiksmodulen skulle då kunna se ut så här.

//app/modules/shop/shop.tsx
const Shop = (props) => { const { state, actions, reducer } = useShopLogic(props); return ( <div id="shop"> <Header /> {!state?.loading && <Button onClick={actions?.buyNow}> <Translate contentKey="shop.buyNow"/> </Button>} <FeaturedProducts products={reducer?.products} /> <Footer /> </div> );
};
export default Shop;

Låt oss nu titta på ett exempel på kroken. Denna innehåller hela koden som är relevant för interaktionen med UI, samt kommunikation med API:t via Reducer / Redux.

//app/modules/shop/shop.logic.ts
export const useShopLogic = (props) => { const dispatch = useDispatch(); const history = useHistory(); const [loading, setLoading] = useState(false); const { products } = useSelector(({shop}) => { products: shop?.products; }); const buyNow = () => { history.push("/product/1"); }; const loadProducts = () => { setLoading(true); dispatch(ShopReducer.loadProducts()); }; useEffect(() => { if (ProductService.areValid(products)) setLoading(false); }, [products]); useEffect(() => { loadProducts(); }, []); return { state: { loading }, actions: { buyNow }, reducer: { products }, };
};

Som man tydligt kan se här har ansvaret mellan UI-definition och funktionalitet separerats. Detta gör att logiken kan återanvändas och gränssnittet enkelt kan anpassas.

Shop Reducer hanterar den globala tillståndslogiken genom Redux.
Här finns också våra API-funktioner, som sedan kan anropas var som helst.

//app/modules/shop/shop.reducer.tsexport const ACTION_TYPES = { FETCH_PRODUCTS: "shop/FETCH_PRODUCTS", RESET: "shop/RESET",
};
const initialState = { products: null, loading: false, success: false,
};
export type ShopState = Readonly<typeof initialState>;
export default (state: ShopState = initialState, action): ShopState => { switch (action.type) { case PENDING(ACTION_TYPES.FETCH_PRODUCTS): return { ...state, success: false, loading: true, }; case FAILURE(ACTION_TYPES.FETCH_PRODUCTS): return { ...state, success: false, loading: false, }; case SUCCESS(ACTION_TYPES.FETCH_PRODUCTS): return { ...state, products: action.payload.data, success: true, loading: false, };case ACTION_TYPES.RESET: return { ...initialState, }; default: return state; }
};
export const getProducts = () => async (dispatch) => { const requestUrl = `${SERVER_API_URL}${API_MAP.getProducts}`;const result = await dispatch({ type: ACTION_TYPES.FETCH_PRODUCTS, payload: axios.get < any > requestUrl, });return result;
};
export const reset = () => ({ type: ACTION_TYPES.RESET,
});

Jag tycker att det är mindre vettigt att lagra API-anropen i komponenten. API-förfrågningar används ofta på flera ställen och i olika moduler, så jag föredrar att hålla dem åtskilda från affärslogiken. Med rätt mellanprogram är reducerarna väldigt modulära och kräver lite kod. Hanteringen av förfrågningar kan också lösas av en central reducering, som i slutändan lämnar nästan bara API-anropsfunktionerna kvar.

Sammanfattning

I den här artikeln ville jag visa hur en frontend-arkitektur kan se ut. Frontend är den första ingången för våra användare.
Eftersom våra applikationer ständigt anpassar sig och växer blir de mer benägna att göra fel. Men buggar hindrar produkter från att släppas och nya funktioner förväntas skapas ännu snabbare.
Detta är helt enkelt inte möjligt utan någon form av struktur och ordning.
Med en bra frontend-arkitektur kan vi dock skapa en stabil grund med vilken vi kan tackla dessa utmaningar mycket bättre.

plats_img

Senaste intelligens

plats_img

Chatta med oss

Hallå där! Hur kan jag hjälpa dig?