Quando você inicia sua carreira em programação, pesquisar no código fonte de bibliotecas e estruturas abertas pode parecer um pouco assustador. Neste artigo, Karl Mungazi compartilha sua experiência de como ele superou seu medo e começou a usar o código-fonte para adquirir conhecimento e desenvolver habilidades. Ele também usa o Redux para mostrar como ele “analisa” a biblioteca.Você se lembra de quando mergulhou no código de uma biblioteca ou estrutura que você costuma usar? Na minha vida, esse momento surgiu no meu primeiro emprego como desenvolvedor front-end há três anos.
Acabamos de reescrever uma estrutura proprietária desatualizada que foi usada para criar cursos de treinamento interativos. No início do trabalho de reescrita, analisamos algumas soluções chave na mão, incluindo Mithril, Inferno, Angular, React, Aurelia, Vue e Polymer. Como eu ainda era um jovem Padawan (que havia acabado de mudar do jornalismo para o desenvolvimento da Web), fiquei com muito medo da complexidade de cada estrutura e da falta de compreensão de como elas funcionam.
O entendimento começou a surgir quando comecei a explorar cuidadosamente a estrutura do Mithril. Desde então, meu conhecimento de JavaScript - e programação em geral - foi reforçado significativamente graças às horas gastas em escavações nas bibliotecas internas, usadas diariamente no trabalho e em meus próprios projetos. Neste artigo, mostrarei como você pode usar sua biblioteca favorita como um tutorial
Comecei a ler o código fonte com a função hyperscript do MithrilProfissionais da análise do código fonte
Uma das principais vantagens de analisar o código-fonte é que você pode aprender muito. Quando comecei a analisar o código Mithril, eu tinha uma péssima idéia do que era o DOM virtual. Quando terminei, já sabia que o DOM virtual é uma técnica que envolve a criação de uma árvore de objetos que descrevem a interface do usuário. Essa árvore pode ser convertida em elementos DOM usando uma API DOM como document.createElement. Para atualizar, é criada uma nova árvore que descreve o estado futuro da interface e, em seguida, comparada com a versão anterior dessa árvore.
Eu li sobre isso em muitos artigos e manuais, mas o mais instrutivo foi observar tudo isso enquanto trabalhava em nosso aplicativo. Também aprendi a fazer as perguntas certas ao comparar estruturas. Em vez de comparar classificações, por exemplo, você pode fazer a pergunta "Como a maneira como essa estrutura funciona com as mudanças afeta o desempenho e a conveniência do usuário final?"
Outra vantagem é o desenvolvimento de um entendimento de uma boa arquitetura de aplicativos. Apesar do fato de que a maioria dos projetos de código aberto geralmente é mais ou menos semelhante em estrutura aos seus repositórios, eles ainda têm diferenças. A estrutura do Mithril é muito plana e, se você conhece bem sua API, pode fazer suposições bastante realistas sobre o código nas pastas de renderização, roteador e solicitação. A estrutura do React, por outro lado, reflete sua nova arquitetura. Os desenvolvedores separaram o módulo responsável por atualizar a interface do usuário (react-reconciler) do módulo responsável por renderizar os elementos DOM (react-dom).
Uma das vantagens dessa separação para os desenvolvedores é que eles podem escrever seus
próprios renderizadores usando ganchos no reat-reconciler. Parcel, o construtor de módulos que estudei recentemente, também possui uma pasta de pacotes, assim como o React. O módulo de chave é chamado de empacotador de pacotes, contém o código responsável pela criação de montagens, a operação do servidor de atualização de módulo (servidor de módulo quente) e a ferramenta de linha de comando.
A análise do código-fonte em breve leva você a ler as especificações do JavaScript.Outra vantagem, que foi uma grande surpresa para mim, é que facilita a leitura da especificação oficial do JavaScript. A primeira vez que me virei para ela quando estava tentando descobrir qual é a diferença entre lançar Erro e lançar novo Erro (spoiler -
nada ). Eu fiz essa pergunta porque o Mithril usou o throw Error na implementação da função m e me perguntei por que era melhor do que lançar o novo erro. Aprendi também que os operadores && e ||
não necessariamente retorna valores booleanos , encontrei as
regras pelas quais o operador de comparação não estrita == "resolve" os valores e a
razão pela qual Object.prototype.toString.call ({}) retorna '[objeto Object]'.
Como analisar o código fonte
Existem várias maneiras de analisar o código fonte. A maneira mais fácil me parece o seguinte: selecione um método da sua biblioteca e descreva o que acontece quando você o chama. Não vale a pena descrever cada etapa, basta tentar entender seus princípios e estrutura gerais.
Recentemente, analisei o ReactDOM.render dessa maneira e aprendi muito sobre o React Fiber e algumas das dificuldades em implementá-lo. Felizmente, o React é muito popular e a presença de um grande número de artigos sobre o mesmo tópico de outros desenvolvedores acelerou o processo.
Esse mergulho no código também me apresentou o conceito de
agendamento cooperativo , o método
window.requestIdleCallback e um
exemplo ao vivo
de uma lista vinculada (o React processa as atualizações enviando-as para a fila, que é uma lista priorizada de atualizações). No processo, seria bom criar um aplicativo simples usando a biblioteca. Isso facilita a depuração, pois você não precisa lidar com o rastreamento de pilha de outras bibliotecas.
Se eu não fizer uma revisão detalhada, vou abrir a pasta node_modules no projeto em que estou trabalhando ou dar uma olhada no GitHub. Eu sempre faço isso quando encontro um bug ou um recurso interessante. Ao ler o código no GitHub, verifique se esta é a versão mais recente. O código da versão mais recente pode ser visto clicando no botão para alterar as ramificações e selecionando "tags". As alterações nas bibliotecas e estruturas estão em andamento, portanto, é improvável que você queira analisar algo que pode não estar na próxima versão.
Uma versão mais superficial do aprendizado do código-fonte é o que chamo de "visualização rápida". De alguma forma, instalei o express.js, abri a pasta node_modules e passei pelas dependências. Se o README não me deu uma explicação satisfatória, li a fonte. Isso me levou a descobertas interessantes:
- O Express usa dois módulos para mesclar objetos, e a operação desses módulos é muito diferente. merge-descriptors adiciona apenas as propriedades encontradas no objeto de origem e também propriedades não enumeráveis, enquanto utils-merge repassa as propriedades enumeradas do objeto e toda a sua cadeia de protótipos. merge-descriptors usa Object.getOwnPropertyNames () e Object.getOwnPropertyDescriptor () e utils-merge usa for..in;
- O setprototypeof do módulo fornece uma opção de plataforma cruzada para especificar o protótipo do objeto criado (instanciado);
- escape-html é um módulo de escape de string de 78 linhas, após o qual o conteúdo pode ser inserido no HTML;
Embora essas descobertas provavelmente não sejam úteis imediatamente, é muito útil um entendimento geral das dependências da sua biblioteca ou estrutura.
As ferramentas do navegador de depuração são seus melhores amigos ao depurar código no frontend. Entre outras coisas, eles permitem que você pare o programa a qualquer momento e verifique ao mesmo tempo seu status, pule a função ou entre ou saia dela. No código minificado, isso não é possível - é por isso que eu descompacte esse código e o coloquei no arquivo correspondente na pasta node_modules.
Use o depurador como um aplicativo útil. Faça uma suposição e, em seguida, teste-a.Estudo de caso: função connect no Redux
O React-Redux é uma biblioteca para gerenciar o estado dos aplicativos React. Quando trabalho com bibliotecas populares como essa, começo procurando artigos sobre seu uso. Ao preparar este exemplo, revi este
artigo . Esse é outro ponto positivo do aprendizado do código-fonte - ele leva a artigos informativos como este que melhoram seu pensamento e entendimento.
Connect é uma função react-redux que vincula o componente react e o armazenamento redux de um aplicativo. Como De acordo com a
documentação, ela faz o seguinte:
"... retorna uma nova classe de componente relacionada, que é um invólucro do componente passado para ele."
Depois de ler isso, faço as seguintes perguntas:
- Conheço padrões ou conceitos em que funções retornam parâmetros de entrada agrupados com funcionalidades adicionais?
- Em caso afirmativo, como uso isso com base na descrição da documentação?
Normalmente, o próximo passo é criar um aplicativo primitivo usando a função de conexão. No entanto, nessa situação, usei um novo aplicativo no React, no qual trabalhamos, porque queria entender a conexão no contexto de um aplicativo que provavelmente chegaria em breve à produção.
O componente em que me concentrei é mais ou menos assim:
class MarketContainer extends Component {
Este é um componente de contêiner que serve como invólucro para os quatro componentes relacionados menores. Uma das primeiras coisas que você encontra no
arquivo que conecta as exportações é o comentário “connect é a fachada do connectAdvanced”. Já nesta fase podemos aprender algo: temos a oportunidade de observar o padrão de “fachada” em ação. No final do arquivo, vemos conectar exportando uma chamada para a função createConnect. Seus parâmetros são um conjunto de valores padrão que são desestruturados da seguinte maneira:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
E temos mais um momento instrutivo: a exportação da função chamada e a desestruturação dos argumentos da função por padrão. A reestruturação é instrutiva para nós, porque o código pode ser escrito assim:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
Como resultado, obteríamos um erro - TypeError não capturado: Não é possível desestruturar a propriedade 'connectHOC' de 'undefined' ou 'null'. Isso aconteceria porque a função não possui valores de argumento padrão.
Nota: para entender melhor a reestruturação dos argumentos, você pode ler o artigo de David Walsh . Alguns pontos podem parecer triviais, dependendo do seu conhecimento do idioma - então você pode se concentrar nos pontos que você não conhece.A função createConnect em si não faz nada. Apenas retorna a função de conexão que usei aqui:
export default connect(null, mapDispatchToProps)(MarketContainer)
São necessários quatro argumentos opcionais e os três primeiros passam pela função de
correspondência , que ajuda a determinar seu comportamento com base em quais argumentos são passados, bem como em seu tipo. Acontece que, como o segundo argumento passado para a correspondência é uma das três funções importadas para o connect, eu preciso escolher para onde ir.
Também há algo a ser aprendido com a
função de proxy usada para quebrar o primeiro argumento no connect, se esses argumentos são funções; do utilitário
isPlainObject usado para verificar objetos simples ou do módulo de
aviso , que mostra como você pode criar um depurador que interrompa todos os erros. Após a função match, passamos ao connectHOC, a função que pega nosso componente de reação e o associa ao redux. Há outra chamada de função que retorna
wrapWithConnect - uma função que realmente lida com a ligação do componente ao repositório.
Observando a implementação do connectHOC, posso adivinhar por que os detalhes da implementação do connect devem estar ocultos. Este é essencialmente o coração do react-redux e contém uma lógica que não deve ser acessível através do connect. Mesmo se insistirmos nisso, mais tarde, se precisarmos aprofundar, já teremos o material de origem com uma explicação detalhada do código.
Resumir
Aprender o código fonte é muito complicado no começo. Mas, como tudo o mais, fica mais fácil com o tempo. Sua tarefa não é entender tudo, mas trazer algo útil para si - um entendimento comum e novos conhecimentos. É muito importante ter cuidado durante todo o processo e se aprofundar nos detalhes.
Por exemplo, achei a função isPlainObject interessante porque usa esse retorno if (typeof obj! == 'objeto' || obj === null) false para garantir que o argumento passado seja um objeto simples. Quando li esse código pela primeira vez, pensei: por que não usar Object.prototype.toString.call (opts)! == '[objeto Objeto]' ', o que reduziria o código e separaria objetos de seus subtipos, como Data. Mas já na próxima linha está claro que, mesmo que de repente (de repente!), Um desenvolvedor usando connect retorne um objeto Date, por exemplo, verificando Object.getPrototypeOf (obj) === null pode lidar com isso.
Outro ponto inesperado em isPlainObject neste local:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
Encontrar uma resposta no Google me levou a
este tópico no StackOverflow e a
este comentário no Redux do GitHub, que explica como esse código lida com situações em que, por exemplo, um objeto é transferido de um iFrame.
-
Primeiro decidiu traduzir o artigo. Ficaria grato por esclarecimentos, conselhos e recomendações