Sob o capô do React. Escrevemos nossa implementação do zero

Nesta série de artigos, criaremos nossa própria implementação do React do zero. No final, você entenderá como o React funciona, quais métodos do ciclo de vida do componente ele chama e por que. O artigo é direcionado para aqueles que já usaram o React e desejam aprender sobre seu dispositivo, ou para os mais curiosos.

imagem

Este artigo é uma tradução de React Internals, Part One: rendering basic

Este é realmente o primeiro artigo de cinco


  1. Rendering Basics <- estamos aqui
  2. ComponentWillMount e componentDidMount
  3. Update
  4. setState
  5. Transações

O material foi criado quando o React 15.3 foi relevante, em particular o uso do ReactDOM e o reconciliador de pilhas. A reação 16 e acima tem algumas alterações. No entanto, esse material permanece relevante, pois fornece uma idéia geral do que está acontecendo "sob o capô".

Parte 1. Noções básicas de renderização


Elementos e componentes


Existem três tipos de entidades no React: um elemento DOM nativo, um elemento React virtual e um componente.

Elementos DOM nativos


Esses são os elementos DOM que o navegador usa para criar a página da web, por exemplo, div, span, h1. O React os cria chamando document.createElement () e interage com a página usando métodos da API DOM baseados no navegador, como element.insertBefore (), element.nodeValue e outros.

Elemento de reação virtual


Um elemento React virtual (geralmente chamado simplesmente de "elemento") é um objeto javascript que contém as propriedades necessárias para criar ou atualizar um elemento DOM nativo ou uma árvore desses elementos. Com base no elemento React virtual, são criados elementos DOM nativos, como div, span, h1 e outros. Podemos dizer que um elemento React virtual é uma instância de um componente composto definido pelo usuário, mais sobre isso abaixo.

Componente


Componente é um termo bastante geral em React. Componentes são entidades com as quais o React faz várias manipulações. Componentes diferentes servem a propósitos diferentes. Por exemplo, o ReactDomComponent da biblioteca ReactDom é responsável pela vinculação entre os elementos React e seus elementos DOM nativos correspondentes.

Componentes compostos personalizados


Provavelmente você já encontrou esse tipo de componente. Ao chamar React.createClass () ou usar as classes ES6 por meio da extensão React.Component, você cria um componente composto personalizado. Esse componente possui métodos de ciclo de vida, como componentWillMount, shouldComponentUpdate e outros. Podemos redefini-los para adicionar algum tipo de lógica. Além disso, outros métodos são criados, como mountComponent, receiveComponent. Esses métodos são usados ​​apenas pelo React para fins internos; não interagimos com eles de forma alguma.

ZanudaMode = on
De fato, os componentes criados pelo usuário inicialmente não estão completos. O React os agrupa em um ReactCompositeComponentWrapper, que adiciona todos os métodos de ciclo de vida aos nossos componentes, após os quais o React pode gerenciá-los (inserir, atualizar etc.).

Reagir declarativo


Quando se trata de componentes personalizados, nossa tarefa é definir as classes desses componentes, mas não instanciamos essas classes. O React os cria quando necessário.

Além disso, não criamos elementos explicitamente usando um estilo imperativo; em vez disso, escrevemos em um estilo declarativo usando JSX:

class MyComponent extends React.Component { render() { return <div>hello</div>; } } 

Este código com marcação JSX é traduzido pelo compilador no seguinte:

 class MyComponent extends React.Component { render() { return React.createElement('div', null, 'hello'); } } 

Isto é, em essência, ele se transforma em uma construção imperativa para criar um elemento por meio de uma chamada explícita para React.createElement (). Mas essa construção está dentro do método render (), que não chamamos explicitamente, o React chamará esse método quando necessário. Portanto, perceber React é igualmente declarativo: descrevemos o que queremos receber e React determina como fazê-lo.

Escreva seu pequeno React


Tendo recebido a base técnica necessária, começaremos a criar nossa própria implementação do React. Esta será uma versão muito simplificada, vamos chamá-la de Feact.

Suponha que desejemos criar um aplicativo Feact simples cujo código se pareceria com isso:

 Feact.render(<h1>hello world</h1>, document.getElementById('root')); 

Primeiro, vamos discordar sobre JSX. Este é precisamente um "recuo", porque a análise JSX é um grande tópico separado que omitiremos como parte de nossa implementação do Feact. Se estivéssemos lidando com JSX processado, veríamos o seguinte código:

 Feact.render( Feact.createElement('h1', null, 'hello world'), document.getElementById('root') ); 

Ou seja, usamos Feact.createElement em vez de JSX. Então, implementamos este método:

 const Feact = { createElement(type, props, children) { const element = { type, props: props || {} }; if (children) { element.props.children = children; } return element; } }; 

O elemento retornado é um objeto simples que representa o que queremos renderizar.

O que o Feact.render () faz?


Ao chamar Feact.render (), passamos dois parâmetros: o que queremos renderizar e onde. Este é o ponto de partida de qualquer aplicativo React. Vamos escrever uma implementação do método render () para Feact:

 const Feact = { createElement() { /*   */ }, render(element, container) { const componentInstance = new FeactDOMComponent(element); return componentInstance.mountComponent(container); } }; 

Após a conclusão de render (), obtemos uma página da web finalizada. Elementos DOM são criados por FeactDOMComponent. Vamos escrever sua implementação:

 class FeactDOMComponent { constructor(element) { this._currentElement = element; } mountComponent(container) { const domElement = document.createElement(this._currentElement.type); const text = this._currentElement.props.children; const textNode = document.createTextNode(text); domElement.appendChild(textNode); container.appendChild(domElement); this._hostNode = domElement; return domElement; } } 

O método mountComponent cria um elemento DOM e o armazena neste._hostNode. Não o usaremos agora, mas retornaremos a isso nas seguintes partes.

A versão atual do aplicativo pode ser visualizada em um violino .

Literalmente, 40 linhas de código foram suficientes para fazer uma implementação primitiva do React. O Feact que criamos dificilmente conquistará o mundo, mas reflete bem a essência do que está acontecendo sob o capô do React.

Adicionando componentes personalizados


Nosso Feact deve ser capaz de renderizar não apenas elementos em HTML (div, span, etc.), mas também componentes compostos definidos pelo usuário:
O método Feact.createElement () descrito anteriormente está bom atualmente, então não o repetirei na listagem de códigos.
 const Feact = { createClass(spec) { function Constructor(props) { this.props = props; } Constructor.prototype.render = spec.render; return Constructor; }, render(element, container) { //      //   , //    } }; const MyTitle = Feact.createClass({ render() { return Feact.createElement('h1', null, this.props.message); } }; Feact.render({ Feact.createElement(MyTitle, { message: 'hey there Feact' }), document.getElementById('root') ); 

Deixe-me lembrá-lo, se JSX estivesse disponível, chamar o método render () ficaria assim:

 Feact.render( <MyTitle message="hey there Feact" />, document.getElementById('root') ); 

Passamos a classe de componente personalizado para createElement. Um elemento React virtual pode representar um elemento DOM regular ou um componente personalizado. Nós os distinguiremos da seguinte maneira: se passarmos um tipo de string, então este é um elemento DOM; se uma função, esse elemento representa um componente personalizado.

Melhorando o Feact.render ()


Se você observar atentamente o código no momento, verá que Feact.render () não pode processar componentes personalizados. Vamos consertar isso:

 Feact = { render(element, container) { const componentInstance = new FeactCompositeComponentWrapper(element); return componentInstance.mountComponent(container); } } class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); const element = componentInstance.render(); const domComponentInstance = new FeactDOMComponent(element); return domComponentInstance.mountComponent(container); } } 

Criamos um wrapper para o item passado. Dentro do wrapper, criamos uma instância da classe de componentes do usuário e chamamos seu método componentInstance.render (). O resultado desse método pode ser passado para o FeactDOMComponent, onde os elementos DOM correspondentes serão criados.

Agora podemos criar e renderizar componentes personalizados. O Feact criará nós DOM com base em componentes personalizados e os mudará dependendo das propriedades (acessórios) de nossos componentes personalizados. Esta é uma melhoria significativa em nosso Feact.
Observe que o FeactCompositeComponentWrapper cria diretamente o FeactDOMComponent. Um relacionamento tão próximo é ruim. Nós vamos corrigir isso mais tarde. Se o React tivesse a mesma conexão estreita, apenas os aplicativos da web poderiam ser criados. A adição de uma camada adicional do ReactCompositeComponentWrapper permite separar a lógica do React para gerenciar elementos virtuais e a exibição final dos elementos nativos, o que permite usar o React não apenas ao criar aplicativos da Web, mas também, por exemplo, React Native para dispositivos móveis.

Aprimoramento de componente personalizado


Os componentes personalizados criados podem retornar apenas elementos DOM nativos. Se tentarmos retornar outros componentes personalizados, ocorreremos um erro. Corrija esta falha. Imagine que gostaríamos de executar o seguinte código sem erros:

 const MyMessage = Feact.createClass({ render() { if (this.props.asTitle) { return Feact.createElement(MyTitle, { message: this.props.message }); } else { return Feact.createElement('p', null, this.props.message); } } } 

O método render () de um componente personalizado pode retornar um elemento DOM nativo ou outro componente personalizado. Se a propriedade asTitle for verdadeira, o FeactCompositeComponentWrapper retornará o componente personalizado para o FeactDOMComponent onde o erro ocorrerá. Corrija FeactCompositeComponentWrapper:

 class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); let element = componentInstance.render(); while (typeof element.type === 'function') { element = (new element.type(element.props)).render(); } const domComponentInstance = new FeactDOMComponent(element); domComponentInstance.mountComponent(container); } } 

Na verdade, agora fizemos uma muleta para atender às necessidades atuais. Uma chamada para o método render retornará componentes filhos até retornar um elemento DOM nativo. Isso é ruim porque esses componentes filhos não participarão do ciclo de vida. Por exemplo, nesse caso, não poderemos implementar a chamada componentWillMount. Nós vamos corrigir isso mais tarde.

E, novamente, corrigimos Feact.render ()


A primeira versão do Feact.render () só podia processar elementos DOM nativos. Agora, apenas os componentes definidos pelo usuário são processados ​​corretamente sem suporte nativo. É necessário lidar com os dois casos. Você pode escrever uma fábrica que criará um componente dependendo do tipo de elemento passado, mas o React escolheu uma maneira diferente: basta agrupar qualquer componente recebido em outro componente:

 const TopLevelWrapper = function(props) { this.props = props; }; TopLevelWrapper.prototype.render = function() { return this.props; }; const Feact = { render(element, container) { const wrapperElement = this.createElement(TopLevelWrapper, element); const componentInstance = new FeactCompositeComponentWrapper(wrapperElement); //   } }; 

TopLevelWrapper é essencialmente um componente personalizado. Também pode ser definido chamando Feact.createClass (). Seu método de renderização simplesmente retorna o elemento passado para ele. Agora, cada elemento é envolvido em TopLevelWrapper, e FeactCompositeComponentWrapper sempre receberá um componente personalizado como entrada.

Conclusão da primeira parte


Implementamos o Feact, que pode renderizar componentes. O código gerado mostra os conceitos básicos de renderização. A renderização real no React é muito mais complicada e abrange eventos, foco, rolagem de janelas, desempenho, etc.

O violão final da primeira parte.

Source: https://habr.com/ru/post/pt458916/


All Articles