Prós e contras do React
Porque é que vale a pena utilizar o React? Que vantagens tem esta biblioteca JavaScript? Para saber as respostas, mergulhe neste artigo e descubra os benefícios reais da utilização do React.
Saiba como simplificar e melhorar a visibilidade de componentes no React usando renderização condicional e componentes de proteção.
Hoje gostaria de falar sobre como controlar visibilidade dos componentes no React. Mas antes de começarmos, há uma pequena
isenção de responsabilidade:
A solução apresentada não é muito segura no sentido de proteger a sua aplicação contra "hackers" (sejam eles quem forem).
Lembre-se de que precisa de proteger os seus pontos finais e de utilizar boas práticas na conceção da aplicação. Esta solução apenas
torna o seu trabalho um pouco mais fácil.
Uma das funcionalidades mais comuns é mostrar o componente apenas para um grupo de utilizadores que têm alguns direitos específicos,
funções ou privilégios. A solução comum é adicionar alguns ses para código, verificar manualmente as condições e mostrar ou não os elementos.
Vamos dar uma olhadela a SimpleAppHeader que contém poucos elementos de navegação.
<!-- 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>Componente apenas para administradores</li><br>
}<br>
{<br>
currentUser.role === "USER" &&<br>
<li>Componente apenas de utilizador</li><br>
}<br>
{<br>
(currentUser.role === "ADMIN" || currentUser.role === "USER") &&<br>
<li>Componente somente para administrador e usuário</li><br>
}<br>
<li>Elemento de uso geral</li><br>
</ul><br>
</nav><br>
</header><br>
)<br>
}<br>
</code></p>
<!-- /wp:paragraph -->
Não parece "mau". Provavelmente, poderia encapsular as verificações em funções para reduzir a complexidade. utilizador atual é um objeto
que tem papel mas pode ser qualquer coisa que queiramos utilizar como objeto do nosso mecanismo de controlo.
Este código tem alguns problemas. Se projeto cresce, provavelmente usamos essas construções em muitos sítios. Por isso, precisamos de copiar,
de alguma forma, condições. Este código é difícil de manter e de alterar no futuro. Especialmente quando as regras de acesso mudam durante
tempo, por exemplo, precisamos de mudar utilizador atual noutra coisa. É muito difícil de testar. É preciso escrever muitos testes
apenas para verificar se o seu estado está bem.
É altura de simplificar um pouco este código. Podemos extrair alguns métodos e tornar o código mais curto e menos complexo:
const isAdmin = (role: string): booleano => role === "ADMIN";
const isUser = (função: string): booleano => função === "USUÁRIO";
const isAdminOrUser = (role: string): boolean => isAdmin(role) || isUser(role);
export const SimplifiedAppHeader: FC = () => {
return (
{
isAdmin(currentUser.role) &&
Componente apenas para administradores
}
{
isUser(currentUser.role) &&
Componente apenas do utilizador
}
{
(isAdminOrUser(currentUser.role)) &&
Componente apenas para administradores e utilizadores
}
Elemento de utilização geral
)
}
```
Parece prometedor. Reduzimos o ruído e a repetição de linhas. O código é mais legível e mais fácil de manter. Mas veja o
função isAdminOrUser. Só temos duas funções. Se introduzirmos uma terceira função, teremos de criar outro conjunto de funções
que combina papéis. Está na altura de outra versão.
Vamos introduzir a função hasRole que será o substituto do nosso isX funções.
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"]) &&
Componente apenas para administradores
}
{
hasRole(currentUser.role, ["USER"]) &&
Componente apenas do utilizador
}
{
hasRole(currentUser.role, ["ADMIN", "USER"]) &&
Componente apenas para administradores e utilizadores
}
Elemento de utilização geral
)
}
```
Agora está com bom aspeto. Continuamos a ter condições na parte html do código, mas agora podemos testar hasRole função e confiar que
será utilizado com o conjunto correto de parâmetros. Adicionar ou alterar funções é agora mais fácil. Utilizamos um array que contém todos os
funções de que necessitamos.
No entanto, esta solução está ligada a utilizador atual objeto. Parte-se do princípio de que o objeto é de alguma forma global ou de fácil acesso em
em qualquer lugar da aplicação. Por isso, podemos tentar encapsular isto. Claro que podemos movê-lo para hasRole função:
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role === s);
return found !== undefined;
}
Mas isso dá nós quase nada.
Guarda ComponenteEstá na altura de criar um componente que encapsule toda a lógica. Dei-lhe o nome de Guarda e é assim que o quero utilizar.
export const GuardedAppHeader: FC = () => {
return (
);
}
O componente precisa de duas propriedades. Primeiro crianças responsável pelo conteúdo que é guardado. Segundo funções necessárias que
manipular o conjunto de funções que nos dá acesso.
Precisamos de representar esta estrutura. É muito simples. Estendemos o tipo React.AdereçosComCrianças que tem crianças
propriedade. É claro que pode adicionar manualmente essa propriedade ao seu tipo e omitir a extensão.
interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
}
O componente em si também é simples. Vamos reutilizar hasRole função aqui.
export const Guard = (props: IGuardProps) => {
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role === s);
return found !== undefined;
}
se (hasRole(props.requiredRoles)) {
return (
{props.children}
);
} else {
return ;
}
}
"`
E eu podia dizer para pararmos aqui, mas seria demasiado fácil. Agora temos componente, e podemos usá-lo ao extremo 😀
O primeiro passo será a externalização dos controlos. utilizador atual é um valor codificado. Gostaria de encapsular este valor
para algum serviço, que "saberá" como verificar os papéis. Tecnicamente, isso significa que mudamos hasRole para outra função
classe.
Eu crio uma interface simples IGuardService que tem apenas uma propriedade - hasRole.
interface de exportação IGuardService {
checkRole: (roles: string[]) => boolean;
}
Agora, a implementação simples poderia ser assim
class SimpleGuard implements IGuardService {
checkRole(roles: string[]): boolean {
let found: string | undefined = roles.find(e => e === currentUser.role);
return found !== undefined;
}
}
Para o utilizar, é necessário alterar IGuardProperties e caso de utilização.
exportar interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
}
// ...
const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Componente apenas para administradores
Componente apenas para utilizadores
Componente de administrador e utilizador
Elemento de utilização geral
);
}
```
Agora o componente tem o seguinte aspeto:
export const Guard = (props: IGuardProps) => {
se (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else {
return ;
}
}
Muito melhor. GuardService separam-nos da lógica que verifica os papéis. Podemos alterá-la sem consequências para a nossa
componente. Um caso de utilização comum é a utilização de uma simulação nos testes e uma implementação "real" no código de produção.
A próxima melhoria será o tratamento de dados personalizados Proibido elemento. A solução atual apresenta um elemento vazio. Primeiro precisamos de
mudança IGuardProps função de adição que será processada nesse elemento.
exportar interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
Esta propriedade é opcional e o seu nome termina com um carácter de ponto de interrogação. Por isso, pode ser uma função ou indefinido. Precisamos de
tratá-lo em Guarda componente.
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 (
Componente apenas para administradores
Componente só para utilizadores
Proibido - apenas o moderador pode ver isto
}>
Componente do moderador
Componente Admin e Utilizador
Elemento de utilização geral
);
}
```
Chegou a altura da última grande mudança. A versão atual do componente suporta apenas funções como corda. Poderíamos ter vários
tipos de propriedades que gostaríamos de verificar. Números ou tipos personalizados não são nada de especial. Irei adicionar suporte para genéricos.
O primeiro passo é a mudança de IGuardService interface. As implementações dependerão do tipo de valor testado. Pode ser
ser qualquer coisa, por isso a interface deve tratar disso.
exportar interface IGuardService {
checkRole: (roles: ROLE[]) => boolean;
}
Agora é necessário um conjunto de PAPEL tipo genérico. A nossa implementação simples vai mudar um pouco.
class SimpleGuard implements IGuardService {
checkRole(roles: string[]): boolean {
let found: string | undefined = roles.find(e => e === currentUser.role);
return found !== undefined;
}
}
Precisamos de adicionar um parâmetro de tipo, mas podemos preparar uma implementação que suporte IRole interface.
interface IRole {
nome: string;
}
//...
class RoleGuardService implements IGuardService {
checkRole(roles: IRole[]): boolean {
let found: IRole | undefined = roles.find(e => e === userWithRole.role);
return found !== undefined;
}
}
```
O segundo passo é propagar esta alteração para IGuardProps.
interface IGuardProps extends React.PropsWithChildren {
requiredRoles: ROLE[];
guardService: IGuardService;
forbidden?: () => React.ReactNode;
}
E, respetivamente, para Guarda componente.
export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
{props.children}
);
} else if (props.forbidden === undefined) {
return ;
} else {
return (
{props.forbidden()}
);
}
}
E os nossos casos de utilização
export const AppHeader: FC = () => {
const guardService = new SimpleGuard();
const roleService = new RoleGuardService();
return (
<header>
<nav>
<ul>
<Guard<IRole> requiredRoles={{[{nome: "ADMIN"}]} guardService={roleService}>
<li>Componente apenas para administradores</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Componente apenas do utilizador</li>
</Guard>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Proibido - apenas o moderador pode ver isto</div>}>
<li>Componente moderador</li>
</Guard>
<Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
<li>Componente Admin e Utilizador</li>
</Guard>
<li>Elemento de utilização geral</li>
</ul>
</nav>
</header>
);
}
Neste exemplo, utilizo ambas as implementações de IGuardservice apenas para fins ilustrativos. Em casos reais de utilização
provavelmente utilizar apenas um.
O Guarda pode ser aninhado. Lembre-se apenas de que o acesso será resolvido por ordem a partir da instância mais externa.
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>Componente apenas para administradores</li>
</Guard>
<Guard<string> requiredRoles={["USER"]} guardService={guardService}>
<li>Componente apenas do utilizador</li>
</Guard>
<li>Componente Admin e Utilizador</li>
<Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() => <div>Proibido - apenas o moderador pode ver isto</div>}>
<li>Componente moderador</li>
</Guard>
</Guard>
<li>Elemento de utilização geral</li>
</ul>
</nav>
</header>
);
}
No exemplo acima Componente moderador nunca poderia aparecer, porque o utilizador só pode lidar com uma função. Primeiro Guarda limites
papéis para ADMIN e UTILIZADOR, portanto MODERADOR nunca passará no primeiro controlo.
Podemos criar componentes especializados que ocultam algumas propriedades.
export const AdminGuard = (props: Omit) => {
return
{props.children}
}
//...
export const SpecializedAppHeader: FC = () => {
const guardService = new SimpleGuard();
return (
Componente apenas para administradores
Componente apenas para utilizadores
Proibido - apenas o moderador pode ver isto
}>
Componente moderador
Componente Admin e Utilizador
Elemento de utilização geral
);
}
```
Neste caso AdminGuard predefinir ADMIN função. Em consequência, é necessário definir explicitamente o tipo de PAPEL tipo
parâmetro.
Neste artigo, mostro-lhe como criar e utilizar Guarda componente no React. Partimos de um código complexo que é difícil de
ler e manter. Desenvolvemo-lo para um estado mais amigável para o programador e introduzimos um componente funcional personalizado. De seguida
estender o componente adicionando funcionalidades extra, refactorizar o serviço de extração e, finalmente, adicionar tipos genéricos.
Finalmente, temos um componente que pode ser aninhado e é fácil de testar e manter.
