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ślido 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ŻYTKOWNIKwię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.
