Last active
July 11, 2024 19:00
-
-
Save nicolasmelo1/7a3af5192cede071456efcd0bf6bbd14 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Isso é uma factory function, basicamente é como se tivesse instanciando uma classe, mas faço isso com function, | |
* prefiro esse padrão do que usar classes. Eu tenho que chamar essa função globalmente antes de começar o app. | |
* | |
* IMPORTANTE: Não demorei nem 10 minutos fazendo essa porcaria, então sei lá, recomendo algo mais parrudão pra usar em prod. | |
* Principalmente algo com tipagem melhor que vc consiga listar os eventos disponiveis e os parâmetros que cada callback recebe. | |
*/ | |
function simplePubSubFactory() { | |
const subscribed = new Map<string, Map<symbol, (...args: any[]) => void>> | |
/** | |
* Sobrescreve um callback a um evento, idealmente o nome dos eventos tem nomes únicos. | |
* | |
* @param event - Nome do evento | |
* @param callback - O que vai ser executado quando o evento for publicado | |
* | |
* @returns - Retorna uma função que remove o callback do evento | |
*/ | |
function subscribe(event: string, callback: (...args: any[]) => void) { | |
if (!subscribed.has(event)) { | |
subscribed.set(event, new Map()) | |
} | |
const id = Symbol() | |
subscribed.get(event).set(id, callback) | |
return () => { | |
subscribed.get(event).delete(id) | |
} | |
} | |
/** | |
* Publica um evento, todos os callbacks registrados para esse evento serão executados. | |
* | |
* @param event - Nome do evento | |
* @param args - Argumentos que serão passados para os callbacks | |
*/ | |
function publish(event: string, ...args: any[]) { | |
if (!subscribed.has(event)) { | |
return | |
} | |
for (const callback of subscribed.get(event).values()) { | |
callback(...args) | |
} | |
} | |
return { subscribe, publish } | |
} | |
// O que vc usa idealmente | |
const pubsub = simplePubSubFactory() | |
function App() { | |
// Faz o fetch dos usuários globalmente e publica para todos os callbacks registrados que o evento aconteceu | |
// Idealmente vc faria isso dentro de um contexto global, mas aqui é só um exemplo. | |
useEffect(() => { | |
fetch('/users').then(response => response.json()).then(users => { | |
pubsub.publish('user', users) | |
}) | |
}, []); | |
return ( | |
<div> | |
<Navbar/> | |
<Sidebar /> | |
<div> | |
<Users /> | |
</div> | |
</div> | |
) | |
} | |
function Navbar() { | |
return ( | |
<nav> | |
<a href="/home">Home</a> | |
<a href="/about">About</a> | |
</nav> | |
) | |
} | |
function Sidebar() { | |
return ( | |
<div> | |
<Users /> | |
</div> | |
) | |
} | |
/** | |
* O componente Users mantém controle dos seus próprios usuários | |
* Removeu um usuário? Publica o evento para todos os componentes que estão escutando que esse usuáro foi removido | |
* | |
* Seus componentes se "conversam", mas sem vc ter acesso direto a eles. O acesso ao dado é direto, não precisa esperar | |
* rerender nenhum executar, quando publico uma mensagem, apenas o componente <Users /> que está escutando o evento | |
* triggará o re-render, deixando a aplicação mais performática. | |
* | |
* Seu código fica co-localizado. Se eu precisar de users em outro lugar, é só eu "escutar" o evento e criar o | |
* const [users, setUsers] = useState([]) e já era. | |
* | |
* Mas isso causa um problema, consegue ver? Beleza, vamos dizer que eu fiz o fetch dos usuários, mas o componente 2 renderizou | |
* só depois que eu fiz o fetch. Ou seja, vai renderizar sem os usuários. Como resolver isso? | |
* | |
* Simples, aqui a gente usa o recurso nativo do React: props | |
* (Vc pode usar um contexto global tbm ou libs como o Zustand, vc ainda mantém sua lib de estado global (redux, zustand, etc), | |
* mas idealmente apenas no primeiro render, depois modifica o dado localmente) | |
* OU, vc pode manter o estado global fora do React (imagina um const globalState = { users: [] }), e no primeiro render vc pega | |
* o dado desse objetão | |
* | |
* ```jsx | |
* function Users(props) { | |
* const [users, setUsers] = useState(props.initialUsers) | |
* | |
* // resto do código | |
* } | |
* ``` | |
* | |
* Ou seja, no primeio render, usa o dado vindo da prop pra carregar o dado local, a partir dai vc gerencia ele localmente | |
* dentro do seu próprio componente. | |
* | |
* Com event bus um componente pode "conversar" com outro independente de onde ele tiver na árvore, componente filho | |
* se comunica com o pai e componente pai se comunica com o filho (esse segundo, só é possível com passagem de props). | |
*/ | |
function Users() { | |
const [users, setUsers] = useState([]) | |
useEffect(() => { | |
const getUsersUnsubscribe = pubsub.subscribe('user', (users) => { | |
setUsers(users) | |
}) | |
const removeUserUnsubscribe = pubsub.subscribe('remove-user', (userId) => { | |
setUsers(users.filter(user => user.id !== userId)) | |
}) | |
return () => { | |
removeUserUnsubscribe(); | |
getUsersUnsubscribe() | |
} | |
}, []) | |
return ( | |
<div> | |
{users.map(user => ( | |
<div key={user.id}> | |
<span>{user.name}</span> | |
<button onClick={() => pubsub.publish('remove-user', user.id)}>Remove</button> | |
</div> | |
))} | |
</div> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment