Výhody a nevýhody modelu React
Proč se vyplatí používat React? Jaké výhody má tato knihovna JavaScript? Chcete-li se dozvědět odpovědi, ponořte se do tohoto článku a zjistěte skutečné výhody používání React.
Zjistěte, jak zjednodušit a zlepšit viditelnost komponent v React pomocí podmíněného vykreslování a ochranných komponent.
Dnes bych se rád věnoval tomu, jak ovládat viditelnost komponent v React. Ale než začneme, je tu malá
zřeknutí se odpovědnosti:
Prezentované řešení není příliš bezpečné ve smyslu ochrany aplikace před "hackery" (ať už jsou to kdokoli).
Nezapomeňte, že je třeba chránit koncové body a používat správné postupy při návrhu aplikace. Toto řešení pouze
vám jen trochu usnadní práci.
Jednou z nejběžnějších funkcí je zobrazování komponent pouze pro skupinu uživatelů, kteří mají určitá práva,
role nebo oprávnění. Běžným řešením je přidání některých pokuds na kód, ručně zkontrolovat podmínky a zobrazit nebo nezobrazit prvky.
Podívejme se na SimpleAppHeader komponentu, která obsahuje několik navigačních prvků.
<!-- wp:paragraph -->
<p><code>typescript jsx<br>
export const SimpleAppHeader: FC = () => {<br>
return (<br>
<header><br>
<nav><br>
<ul><br>
{<br>
currentUser.role === "ADMIN" &&<br>
<li>Pouze komponentaAdmin</li><br>
}<br>
{<br>
currentUser.role === "USER" &&<br>
<li>Pouze uživatelská komponenta</li><br>
}<br>
{<br>
(currentUser.role === "ADMIN" || currentUser.role === "USER") &&<br>
<li>Složka pouze pro administrátory a uživatele</li><br>
}<br>
<li>Prvek pro obecné použití</li><br>
</ul><br>
</nav><br>
</header><br>
)<br>
}<br>
</code></p>
<!-- /wp:paragraph -->
Nevypadá "špatně". Pravděpodobně byste mohli zapouzdřit kontroly do funkcí, abyste snížili složitost. currentUser je nějaký objekt
která má role ale může to být cokoli, co bychom chtěli použít jako předmět našeho kontrolního mechanismu.
Tento kód má některé problémy. Pokud projekt roste pravděpodobně používáme tyto konstrukce na mnoha místech. Musíme tedy kopírovat,
nějakým způsobem, podmínky. Tento kód je obtížné udržovat a v budoucnu měnit. Zvláště když se pravidla přístupu mění v průběhu
čas, např. musíme změnit currentUser do něčeho jiného. Je velmi těžké to otestovat. Musíte napsat mnoho testů
jen pro ověření, zda je váš stav v pořádku.
Je čas tento kód trochu zjednodušit. Mohli bychom z něj vyjmout některé metody a kód zkrátit a zkomplikovat:
const isAdmin = (role: string): boolean => role === "ADMIN";
const isUser = (role: string): boolean => role === "USER";
const isAdminOrUser = (role: string): boolean => isAdmin(role) || isUser(role);
export const SimplifiedAppHeader: FC = () => {
return (
{
isAdmin(currentUser.role) &&
Pouze komponenta Admin
}
{
isUser(currentUser.role) &&
Pouze komponenta User
}
{
(isAdminOrUser(currentUser.role)) &&
Pouze komponenta Admin a User
}
Prvek pro obecné použití
)
}
```
Vypadá to slibně. Snižujeme šum od opakovaných řádků. Kód je čitelnější a snadněji se udržuje. Ale podívejte se na
funkce isAdminOrUser. Máme pouze dvě role. Pokud zavedeme třetí roli, musíme vytvořit další sadu funkcí.
která kombinuje role. Čas na další verzi.
Zavedeme funkci hasRole která bude náhradou za naši isX funkce.
const hasRole = (role: string, requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => role === s);
return found !== undefined;
}
export const FilteringAppHeader: FC = () => {
return (
{
hasRole(currentUser.role, ["ADMIN"]) &&
Pouze komponenta Admin
}
{
hasRole(currentUser.role, ["USER"]) &&
Pouze komponenta User
}
{
hasRole(currentUser.role, ["ADMIN", "USER"]) &&
Pouze komponenta Admin a User
}
Prvek pro obecné použití
)
}
```
Nyní vypadá dobře. Stále máme podmínky v html části kódu, ale nyní můžeme testovat hasRole funkci a důvěru, že
se použije se správnou sadou parametrů. Přidávání nebo změna rolí je nyní jednodušší. Používáme pole, které obsahuje všechny
role, které potřebujeme.
Toto řešení je však vázáno na currentUser objekt. Předpokladem je, že objekt je nějakým způsobem globální nebo snadno přístupný v
libovolné místo v aplikaci. Můžeme se tedy pokusit o zapouzdření. Samozřejmě ji můžeme přesunout do hasRole funkce:
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role === s);
return found !== undefined;
}
Ale to dává nás téměř nic.
Strážce KomponentaJe čas vytvořit komponentu, která zapouzdří celou logiku. Pojmenoval jsem ji Strážce a chci ho použít takto.
export const GuardedAppHeader: FC = () => {
return (
);
}
Komponenta potřebuje dvě vlastnosti. První děti zodpovědný za obsah, který je střežen. Druhá stránka requiredRoles že
zpracovávat pole rolí, které nám poskytuje přístup.
Potřebujeme zastoupení této struktury. Je to velmi jednoduché. Rozšíříme typ React.PropsWithChildren (Rekvizity s dětmi) která má děti
nemovitosti. Tuto vlastnost můžete samozřejmě přidat do typu ručně a příponu vynechat.
rozhraní IGuardProps rozšiřuje React.PropsWithChildren {
requiredRoles: string[];
}
Samotná komponenta je také jednoduchá. Znovu použijeme hasRole zde.
export const Guard = (props: IGuardProps) => {
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role === s);
return found !== undefined;
}
if (hasRole(props.requiredRoles)) {
return (
{props.children}
);
} else {
return ;
}
}
"`
A já bych mohl říct, že tady přestanu, ale bylo by to příliš snadné. Teď máme komponentu a můžeme ji využít do krajnosti 😀.
Prvním krokem bude externalizace kontrol. currentUser je pevně zadaná hodnota. Rád bych tuto hodnotu zapouzdřil
do nějaké služby, která bude "umět" ověřovat role. Technicky to znamená, že přesuneme hasRole na jinou funkci
třída.
Vytvářím jednoduché rozhraní IGuardService která má pouze jednu vlastnost - hasRole.
export rozhraní IGuardService {
checkRole: (roles: string[]) => boolean;
}
Jednoduchá implementace by nyní mohla vypadat takto
třída SimpleGuard implementuje IGuardService {
checkRole(roles: string[]): boolean {
let found: string | undefined = roles.find(e => e === currentUser.role);
return found !== undefined;
}
}
Abychom ji mohli používat, musíme změnit IGuardProperties a případ použití.
export rozhraní IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
}
// ...
const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Komponenta pouze pro správce
Komponenta pouze pro uživatele
Komponenta pro správce a uživatele
Prvek obecného použití
);
}
```
Nyní komponenta vypadá takto:
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else {
return ;
}
}
Mnohem lepší. GuardService nás oddělit od logiky, která kontroluje role. Můžeme ji změnit bez následků pro naši
součást. Běžným případem použití je použití makety v testech a "skutečné" implementace v produkčním kódu.
Dalším vylepšením bude manipulace s vlastními Zakázané prvek. Současné řešení vykresluje prázdný prvek. Nejprve musíme
změna IGuardProps přidání funkce, která bude tento prvek vykreslovat.
export interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
Jedná se o nepovinnou vlastnost, název končí otazníkem. Může to tedy být funkce nebo neurčeno. Musíme
zpracovávat v Strážce součást.
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else if (props.forbidden === undefined) {
return ;
} else {
return (
{props.forbidden()}
);
}
}
// ...
export const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Komponenta pouze pro správce
Komponenta pouze pro uživatele
Zakázáno - vidí pouze moderátor
}>
Moderátorská komponenta
Komponenta Administrátor a uživatel
Prvek obecného použití
);
}
```
Čas na poslední velkou změnu. Současná verze komponenty podporuje pouze role jako řetězec. Mohli bychom mít více různých
typy majetku, které bychom chtěli zkontrolovat. Čísla nebo vlastní typy nejsou nic zvláštního. Přidám podporu generik.
Prvním krokem jsou změny v IGuardService rozhraní. Implementace bude záviset na typu testované hodnoty. Může to být
být cokoli, takže rozhraní by to mělo zvládnout.
export rozhraní IGuardService {
checkRole: (roles: ROLE[]) => boolean;
}
Nyní je třeba pole ROLE generický typ. Naše jednoduchá implementace se trochu změní.
třída SimpleGuard implementuje IGuardService {
checkRole(roles: string[]): boolean {
let found: string | undefined = roles.find(e => e === currentUser.role);
return found !== undefined;
}
}
Musíme přidat parametr typu, ale mohli bychom připravit implementaci, která podporuje IRole rozhraní.
rozhraní IRole {
name: string;
}
//...
class RoleGuardService implements IGuardService {
checkRole(roles: IRole[]): boolean {
let found: IRole | undefined = roles.find(e => e === userWithRole.role);
return found !== undefined;
}
}
```
Druhým krokem je propagace této změny do IGuardProps.
rozhraní IGuardProps extends React.PropsWithChildren {
requiredRoles: ROLE[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
A v tomto pořadí na Strážce součást.
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else if (props.forbidden === undefined) {
return ;
} else {
return (
{props.forbidden()}
);
}
}
A naše případy použití
export const AppHeader: FC = () => {
const guardService = new SimpleGuard();
const roleService = new RoleGuardService();
return (
<header>
<nav>
<ul>
<Guard<IRole> requiredRoles={[{name: "ADMIN"}]} guardService={roleService}>
<li>Komponenta pouze pro správce</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Pouze uživatelská komponenta</li>
</Guard>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Zakázáno - vidí pouze moderátor</div>}>
<li>Moderátorská složka</li>
</Guard>
<Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
<li>Komponenta správce a uživatele</li>
</Guard>
<li>Prvek obecného použití</li>
</ul>
</nav>
</header>
);
}
V tomto příkladu používám obě implementace IGuardservice jen pro ilustraci. V reálných případech použití
pravděpodobně používat pouze jeden.
Na stránkách Strážce by mohly být vnořené. Jen nezapomeňte, že přístup bude řešen v pořadí od většiny externích instancí.
export const NestAppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
<header>
<nav>
<ul>
<Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
<Guard<string> requiredRoles={["ADMIN"]} guardService={guardService}>
<li>Komponenta pouze pro správce</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Pouze uživatelská komponenta</li>
</Guard>
<li>Komponenta správce a uživatele</li>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Zakázáno - vidí pouze moderátor</div>}>
<li>Moderátorská složka</li>
</Guard>
</Guard>
<li>Prvek obecného použití</li>
</ul>
</nav>
</header>
);
}
Ve výše uvedeném příkladu Moderátorská složka se nikdy nemohl objevit, protože uživatel může pracovat pouze s jednou rolí. První Strážce omezení
rolí na ADMIN a UŽIVATEL, takže MODERÁTOR nikdy neprojde první kontrolou.
Můžeme vytvářet specializované komponenty, které skrývají některé vlastnosti.
export const AdminGuard = (props: Omit) => {
return
{props.children}
}
//...
export const SpecializedAppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Komponenta pouze pro správce
Komponenta pouze pro uživatele
Zakázáno - vidí pouze moderátor
}>
Komponenta Moderátor
Komponenta Admin a User
Prvek obecného použití
);
}
```
V tomto případě AdminGuard předdefinovat ADMIN role. V důsledcích je třeba explicitně definovat typ ROLE typ
parametr.
V tomto článku vám ukážu, jak vytvořit a používat Strážce součástka v React. Vycházíme ze složitého kódu, který je obtížné
číst a udržovat. Vyvíjíme ji do stavu, který je pro vývojáře přívětivější, a zavádíme vlastní funkční komponenty. Dále
rozšířit komponentu o další funkce, refaktorovat extrakční službu a nakonec přidat generické typy.
Konečně máme komponentu, která by mohla být vnořená, snadno se testuje a udržuje.
