Plusy i minusy React
Dlaczego warto korzystać z React? Jakie zalety ma biblioteka JavaScript? Aby poznać odpowiedzi, zanurz się w tym artykule i odkryj prawdziwe korzyści płynące z używania React.
Dowiedz się, jak uprościć i poprawić widoczność komponentów w React przy użyciu renderowania warunkowego i komponentów ochronnych.
Dzisiaj chciałbym omówić, jak kontrolować Widoczność komponentów w React. Ale zanim zaczniemy, jest mały
wyłączenie odpowiedzialności:
Prezentowane rozwiązanie nie jest zbyt bezpieczne w sensie ochrony aplikacji przed "hakerami" (kimkolwiek są).
Pamiętaj, że musisz chronić swoje punkty końcowe i stosować dobre praktyki w projektowaniu aplikacji. To rozwiązanie tylko
tylko ułatwia pracę.
Jedną z najczęstszych funkcjonalności jest wyświetlanie komponentu tylko dla grupy użytkowników, którzy mają określone uprawnienia,
ról lub uprawnień. Powszechnym rozwiązaniem jest dodanie jeśli
do kodRęcznie sprawdzaj warunki i pokazuj lub nie elementy.
Przyjrzyjmy się SimpleAppHeader
który zawiera kilka elementów nawigacyjnych.
<!-- 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>Komponent tylko dla administratorów</li><br>
}<br>
{<br>
currentUser.role == "USER" &&<br>
<li>Komponent tylko dla użytkowników</li><br>
}<br>
{<br>
(currentUser.role == "ADMIN" || currentUser.role == "USER") &&<br>
<li>Komponent tylko dla administratorów i użytkowników</li><br>
}<br>
<li>Ogólny element użycia</li><br>
</ul><br>
</nav><br>
</header><br>
)<br>
}<br>
</code></p>
<!-- /wp:paragraph -->
Wygląda "nieźle". Prawdopodobnie można zamknąć kontrole w funkcjach, aby zmniejszyć złożoność. currentUser
jest jakimś obiektem
który ma rola
ale może to być cokolwiek, co chcielibyśmy wykorzystać jako przedmiot naszego mechanizmu kontroli.
Ten kod ma pewne problemy. Jeśli projekt Prawdopodobnie używamy tej konstrukcji w wielu miejscach. Musimy więc je skopiować,
w jakiś sposób, warunki. Taki kod jest trudny do utrzymania i zmiany w przyszłości. Zwłaszcza, gdy reguły dostępu zmieniają się podczas
czas np. musimy zmienić currentUser
w coś innego. Jest to bardzo trudne do przetestowania. Trzeba napisać wiele testów
tylko po to, aby sprawdzić, czy twój stan jest w porządku.
Czas trochę uprościć ten kod. Możemy wyodrębnić niektóre metody i uczynić kod krótszym i mniej złożonym:
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) &&
Komponent tylko dla administratora
}
{
isUser(currentUser.role) &&
Komponent tylko dla użytkownika
}
{
(isAdminOrUser(currentUser.role)) &&
Komponent tylko dla administratorów i użytkowników
}
Element ogólnego zastosowania
)
}
```
Wygląda obiecująco. Redukujemy szum lub powtarzające się linie. Kod jest bardziej czytelny i łatwiejszy w utrzymaniu. Ale spójrzmy na
funkcja isAdminOrUser
. Mamy tylko dwie role. Jeśli wprowadzimy trzecią rolę, musimy utworzyć kolejny zestaw funkcji
który łączy role. Czas na kolejną wersję.
Wprowadźmy funkcję hasRole
który zastąpi nasz isX
funkcje.
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"]) &&
Komponent tylko dla administratorów
}
{
hasRole(currentUser.role, ["USER"]) &&
Komponent tylko dla użytkownika
}
{
hasRole(currentUser.role, ["ADMIN", "USER"]) &&
Komponent tylko dla administratorów i użytkowników
}
Element ogólnego zastosowania
)
}
```
Teraz wygląda to dobrze. Nadal mamy warunki w części kodu html, ale teraz możemy testować hasRole
i ufać, że
zostanie użyta z prawidłowym zestawem parametrów. Dodawanie lub zmienianie ról jest teraz łatwiejsze. Używamy tablicy, która zawiera wszystkie
ról, których potrzebujemy.
Rozwiązanie to jest jednak związane z currentUser
obiekt. Zakłada się, że obiekt jest w jakiś sposób globalny lub łatwo dostępny w
w dowolnym miejscu aplikacji. Możemy więc spróbować to enkapsulować. Oczywiście, możemy przenieść to do hasRole
funkcja:
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role == s);
return found !== undefined;
}
Ale to nie daje nam prawie nic.
Strażnik
KomponentCzas stworzyć komponent, który hermetyzuje całą logikę. Nazwałem go Strażnik
i tak właśnie chcę go używać.
export const GuardedAppHeader: FC = () => {
return (
);
}
Komponent wymaga dwóch właściwości. Po pierwsze dzieci
odpowiedzialny za treści, które są chronione. Po drugie requiredRoles
że
obsługuje tablicę ról, która daje nam dostęp.
Potrzebujemy reprezentacji tej struktury. Jest to bardzo proste. Rozszerzamy typ React.PropsWithChildren
który ma dzieci
property. Oczywiście możesz ręcznie dodać tę właściwość do swojego typu i pominąć rozszerzenie.
interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
}
Sam komponent również jest prosty. Użyjemy ponownie hasRole
funkcja tutaj.
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 ;
}
}
"`
Mógłbym powiedzieć "stop", ale to byłoby zbyt proste. Teraz mamy komponent i możemy go wykorzystać do ekstremum 😀
Pierwszym krokiem będzie eksternalizacja kontroli. currentUser
jest zakodowaną wartością. Chciałbym enkapsulować tę wartość
do jakiejś usługi, która będzie "wiedziała", jak zweryfikować role. Technicznie oznacza to, że przenosimy hasRole
do innej funkcji
klasa.
Tworzę prosty interfejs IGuardService
która ma tylko jedną właściwość - hasRole
.
export interface IGuardService {
checkRole: (role: string[]) => boolean;
}
Prosta implementacja mogłaby wyglądać następująco
class SimpleGuard implementuje IGuardService {
checkRole(role: string[]): boolean {
let found: string | undefined = roles.find(e => e == currentUser.role);
return found !== undefined;
}
}
Aby go użyć, musimy zmienić IGuardProperties
i przypadek użycia.
export interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
}
// ...
const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Komponent tylko dla administratora
Składnik tylko dla użytkownika
Komponent administratora i użytkownika
Ogólny element użytkowania
);
}
```
Teraz komponent wygląda następująco:
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else {
return ;
}
}
Znacznie lepiej. GuardService
oddziela nas od logiki, która sprawdza role. Możemy to zmienić bez konsekwencji dla naszych
komponent. Typowym przypadkiem użycia jest użycie makiety w testach i "prawdziwej" implementacji w kodzie produkcyjnym.
Kolejnym ulepszeniem będzie obsługa niestandardowych Zakazane
element. Obecne rozwiązanie renderuje pusty element. Najpierw musimy
zmiana IGuardProps
dodając funkcję, która będzie renderować ten element.
export interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
Jest to właściwość opcjonalna, której nazwa kończy się znakiem zapytania. Więc może to być funkcja lub niezdefiniowany
. Musimy
obsłużyć go w Strażnik
składnik.
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 (
Komponent tylko dla administratora
Składnik tylko dla użytkownika
Zabronione - tylko moderator może to zobaczyć
}>
Komponent moderatora
Komponent administratora i użytkownika
Ogólny element użytkowania
);
}
```
Czas na ostatnią dużą zmianę. Obecna wersja komponentu obsługuje tylko role ciąg
. Możemy mieć wiele różnych
typy właściwości, które chcemy sprawdzić. Liczby lub niestandardowe typy to nic specjalnego. Dodam obsługę generycznych.
Pierwszym krokiem są zmiany w IGuardService
interfejs. Implementacje będą zależeć od typu testowanej wartości. Może to być
może być cokolwiek, więc interfejs powinien sobie z tym poradzić.
export interface IGuardService {
checkRole: (roles: ROLE[]) => boolean;
}
Teraz pobiera tablicę ROLA
typ ogólny. Nasza prosta implementacja nieco się zmieni.
class SimpleGuard implementuje IGuardService {
checkRole(role: string[]): boolean {
let found: string | undefined = roles.find(e => e == currentUser.role);
return found !== undefined;
}
}
Musimy dodać parametr typu, ale możemy przygotować implementację, która obsługuje IRole
interfejs.
interfejs IRole {
name: string;
}
//...
class RoleGuardService implements IGuardService {
checkRole(roles: IRole[]): boolean {
let found: IRole | undefined = roles.find(e => e == userWithRole.role);
return found !== undefined;
}
}
```
Drugim krokiem jest propagacja tej zmiany do IGuardProps
.
interface IGuardProps extends React.PropsWithChildren {
requiredRoles: ROLE[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
I odpowiednio do Strażnik
składnik.
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else if (props.forbidden == undefined) {
return ;
} else {
return (
{props.forbidden()}
);
}
}
I nasze przypadki użycia
export const AppHeader: FC = () => {
const guardService = new SimpleGuard();
const roleService = new RoleGuardService();
return (
<header>
<nav>
<ul>
<Guard<IRole> requiredRoles={[{nazwa: "ADMIN"}]} guardService={roleService}>
<li>Komponent tylko dla administratora</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Komponent tylko dla użytkownika</li>
</Guard>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Zakazane - tylko moderator może to zobaczyć</div>}>
<li>Komponent moderatora</li>
</Guard>
<Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
<li>Komponent administratora i użytkownika</li>
</Guard>
<li>Ogólny element użytkowania</li>
</ul>
</nav>
</header>
);
}
W tym przykładzie używam obu implementacji IGuardservice
tylko w celach ilustracyjnych. W rzeczywistych przypadkach użycia
prawdopodobnie używać tylko jednego.
The Strażnik
mogą być zagnieżdżone. Pamiętaj tylko, że dostęp będzie rozwiązywany w kolejności od najbardziej zewnętrznej instancji.
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>Komponent tylko dla administratora</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Komponent tylko dla użytkownika</li>
</Guard>
<li>Komponent administratora i użytkownika</li>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Zakazane - tylko moderator może to zobaczyć</div>}>
<li>Komponent moderatora</li>
</Guard>
</Guard>
<li>Ogólny element użytkowania</li>
</ul>
</nav>
</header>
);
}
W powyższym przykładzie Komponent moderatora
nigdy nie może się pojawić, ponieważ użytkownik może obsługiwać tylko jedną rolę. Pierwszy Strażnik
limity
role do ADMIN
i UŻYTKOWNIK
więc MODERATOR
nigdy nie przejdzie pierwszej kontroli.
Możemy budować wyspecjalizowane komponenty, które ukrywają niektóre właściwości.
export const AdminGuard = (props: Omit) => {
return
{props.children}
}
//...
export const SpecializedAppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Komponent tylko dla administratora
Składnik tylko dla użytkownika
Forbidden - tylko moderator może to zobaczyć
}>
Komponent moderatora
Komponent administratora i użytkownika
Ogólny element użytkowania
);
}
```
W tym przypadku AdminGuard
predefiniować ADMIN
rola. W konsekwencji musimy wyraźnie zdefiniować typ ROLA
typ
parametr.
W tym artykule pokażę, jak tworzyć i używać Strażnik
komponent w React. Zaczynamy od złożonego kodu, który jest trudny do
czytać i utrzymywać. Ewoluujemy go do stanu bardziej przyjaznego dla programistów i wprowadzamy niestandardowy komponent funkcjonalny. Następnie
rozszerzenie komponentu o dodatkowe funkcjonalności, refaktoryzacja usługi ekstrakcji i wreszcie dodanie typów generycznych.
Wreszcie mamy komponent, który może być zagnieżdżony, jest łatwy do testowania i utrzymania.