Aplicando Design Patterns Criacionais em Componentes React
Neste artigo, vamos construir exemplos práticos de creational design patterns aplicados ao código React. Embora existam inumeros padrões, muitos podem precisar de modelos reais para compreender sua aplicabilidade. Se você nunca ouviu falar sobre design patterns antes, sugiro dar uma olhada aqui para ter uma visão geral. Segue três exemplos que considero muito uteis para aplicações React.
Factory Method
Ele fornece uma maneira de abstrair o processo de criação de componentes React, tornando-o flexível e reutilizável.
type ProfileType = "ADMIN" | "BUYER" | "SELLER";
/*
nossos componentes definidos por perfil podem ter
sua complexidade isolada
*/
const ProfileSeller = () => <h4>seller profile</h4>;
const ProfileBuyer = () => <h4>buyer profile</h4>;
const ProfileAdmin = () => <h4>admin profile</h4>;
/*
Usando o tipo do usuario conseguimos criar o componente correto
aplicando o conceito de factory method
*/
function ProfileFactory(type: ProfileType) {
const profilesType: Record<ProfileType, React.FC> = {
ADMIN: () => <ProfileAdmin />,
BUYER: () => <ProfileBuyer />,
SELLER: () => <ProfileSeller />,
};
return profilesType[type];
}
/*
Agora renderizamos o componente
nota-se que abstraimos completamante a complexidade de implementacao
*/
function Example(){
const Profile = ProfileFactory("ADMIN");
return (
<div>
<h3>My Profile</h3>
<Profile />
</div>
);
}
export default Example;
Abstract Factory Method
Podemos usar esse pattern quando precisamos criar uma família de componentes que compartilhem características e comportamentos. Esse pattern vai nos dar uma flexibilidade incrível.
/*
Caracteristicas compartilhas entre nossos componentes
criamos as interface abaixo para garantir que
nao vamos criar um componente invalido
*/
interface AbstractProfile {
style: string;
type: "ADMIN" | "BUYER" | "SELLER";
goToProfile: () => void;
}
interface AbstractProfileFactory {
createProfile: () => AbstractProfile;
}
interface ProfileProps {
factory: AbstractProfileFactory;
}
/*
Nossas funcoes fabricas abaixo criam
componentes que compartilhar caracteristicas
e garantimos a correta implementacao atraves de
interfaces.
*/
const AdminProfileFactory = (): AbstractProfileFactory => ({
createProfile: () => ({
style: "profile-admin",
type: "ADMIN",
goToProfile: () => location.assign("/perfil/admin"),
}),
});
const BuyerProfileFactory = (): AbstractProfileFactory => ({
createProfile: () => ({
style: "profile-buyer",
type: "BUYER",
goToProfile: () => location.assign("/perfil/buyer"),
}),
});
const SellerProfileFactory = (): AbstractProfileFactory => ({
createProfile: () => ({
style: "profile-seller",
type: "SELLER",
goToProfile: () => location.assign("/perfil/seller"),
}),
});
/*
Esse Componente tem a responsabilidade
de renderizar o comportamento padrao de qualquer
componente que compartilhe o comportamento
definido nas interfaces
*/
function ProfileUser(props: ProfileProps) {
const { factory } = props;
const { style, type, goToProfile } = factory.createProfile();
return (
<div className={style}>
<button onClick={() => goToProfile()}>{type}</button>
</div>
);
}
/*
Agora podemos carregar nossos perfils
em qualquer contexto necessario
Seguindo as interfaces podemos ate criar novos perfis
com seguranca que nao vamos quebrar o comportamento esperado
*/
function Example() {
return (
<div>
<ProfileUser factory={AdminProfileFactory()} />
<ProfileUser factory={BuyerProfileFactory()} />
<ProfileUser factory={SellerProfileFactory()} />
</div>
);
}
export default Example;
Builder
Podemos construir componentes complexos de forma modular e com configurações diferentes de acordo com contexto.
type UserType = "ADMIN" | "BUYER";
type HeaderProps = {
userType: UserType;
};
/**
Criamos nossos componentes que vao ser utilizados
de acordo com perfil do usuario assim renderizamos
somente o necessario
*/
const LinkMyProducts = () => <a>lista de produtos </a>;
const LinkMyWishList = () => <a>lista de desejos </a>;
const LinkManagerUsers = () => <a>gerenciar usuarios </a>;
/**
Aqui aplicamos o builder pattern
*/
function MenuBuild() {
const items: JSX.Element[] = [];
// Funcao responsavel por criar nosso menu
const build = () => <nav>{items.map((item) => item)}</nav>;
// Adiciona partes do nosso menu de forma dinamica
const addItem = (component: JSX.Element) => {
items.push(component);
return {
addItem,
build,
};
};
return { addItem, build };
}
function Header({ userType }: HeaderProps): JSX.Element {
/**
Adicionando componentes padroes no menu
esses podem ser acessos por qualquer perfil de usuario
*/
const Menu = MenuBuild()
.addItem(<LinkMyProducts />)
.addItem(<LinkMyWishList />);
// Nosso usuario admin tem acesso a um recurso exclusivo
if (userType === "ADMIN") {
Menu.addItem(<LinkManagerUsers />);
}
// aqui montamos o componente final que pode mudar baseado no perfil
return Menu.build();
}
function Example() {
return (
<div>
<Header userType="ADMIN" />
<Header userType="BUYER" />
</div>
);
}
export default Example;
Bem eu espero que esses exemplos te ajudem a entender melhor esses padrões e aplicá-los em seus projetos React. Segue link com repositório dos exemplos Repo.