Atualmente, o React é uma das bibliotecas JavaScript mais populares. Ela pode ser usada para criar aplicativos dinâmicos e responsivos, permite melhor desempenho e pode ser facilmente ampliada. A lógica subjacente é baseada em componentes que podem ser reutilizados em diferentes contextos, reduzindo a necessidade de escrever o mesmo código várias vezes. Em resumo, com o React você pode criar aplicativos eficientes e poderosos.

Portanto, nunca houve um momento melhor para aprender a criar aplicativos React.

No entanto, sem um entendimento sólido de alguns recursos-chave do JavaScript, construir aplicativos React pode ser difícil ou até mesmo impossível.

Por esse motivo, compilamos uma lista de recursos e conceitos do JavaScript que você precisa conhecer antes de começar com o React. Quanto melhor você entender esses conceitos, mais fácil será para você construir aplicativos React profissionais.

JavaScript e ECMAScript

JavaScript é uma linguagem de script popular usada em conjunto com HTML e CSS para criar páginas dinâmicas na web. Enquanto o HTML é usado para criar a estrutura de uma página da web e o CSS para criar o estilo e o layout de seus elementos, o JavaScript é a linguagem usada para adicionar comportamento à página, ou seja, para criar funcionalidade e interatividade.

Desde então, a linguagem foi adotada pelos principais navegadores e um documento foi escrito para descrever a maneira como o JavaScript deveria funcionar: o padrão ECMAScript.

A partir de 2015, uma atualização do padrão ECMAScript é lançada anualmente e, portanto, novos recursos são adicionados ao JavaScript todos os anos.

O ECMAScript 2015 foi a sexta versão do padrão e, portanto, também é conhecido como ES6. As versões seguintes são marcadas em progressão, de modo que nos referimos ao ECMAScript 2016 como ES7, ao ECMAScript 2017 como ES8 e assim por diante.

Para garantir que os recursos mais recentes do JavaScript adicionados ao seu aplicativo JS funcionem como esperado em todos os navegadores da web, você tem três opções:

  1. Esperar pelo Suporte dos Navegadores: Você pode aguardar até que todos os principais navegadores forneçam suporte para os novos recursos que você está usando. No entanto, isso pode não ser viável se você precisar dos recursos específicos para o seu aplicativo e não puder esperar pela adoção generalizada pelos navegadores.
  2. Usar um Polyfill: Um polyfill é “um snippet de código (geralmente JavaScript na web) usado para fornecer funcionalidade moderna em navegadores mais antigos que não a suportam nativamente” (consulte também a documentação da MDN (Mozilla Developer Network). Os polyfills preenchem as lacunas de compatibilidade, permitindo que você use os recursos mais recentes do JavaScript, mesmo em navegadores mais antigos.
  3. Usar um Transpilador JavaScript: Você pode usar um transpilador JavaScript, como o Babel ou o Traceur, que converte o código ECMAScript 2015+ em uma versão JavaScript compatível com todos os navegadores.

Declarações vs expressões

Entender a diferença entre declarações (statements) e expressões (expressions) é essencial ao construir aplicativos React. Vamos voltar aos conceitos básicos de programação por um momento.

Um programa de computador é uma lista de instruções a serem executadas por um computador. Essas instruções são chamadas de declarações.

Ao contrário das declarações, expressões são fragmentos de código que produzem um valor. Em uma declaração, uma expressão é uma parte que retorna um valor e geralmente a vemos no lado direito de um sinal de igual.

Considerando que:

As declarações em JavaScript podem ser blocos ou linhas de código que geralmente terminam com ponto e vírgula ou estão entre chaves.

Aqui está um exemplo simples de uma declaração em JavaScript:

document.getElementById("hello").innerHTML = "Hello World!";

A declaração acima escreve "Hello World!" em um elemento DOM com id="hello".

Como já mencionamos, as expressões produzem um valor ou são elas próprias um valor. Considere o exemplo a seguir:

msg = document.getElementById("hello").value;

document.getElementById("hello").value é uma expressão, pois retorna um valor.

Um exemplo adicional deve ajudar a esclarecer a diferença entre expressões e declarações:

const msg = "Hello World!";
function sayHello( msg ) {
	console.log( msg );
}

No exemplo acima,

  • a primeira linha é uma instrução, em que "Hello World!" é uma expressão,
  • a declaração da função é uma instrução, em que o parâmetro msg passado para a função é uma expressão,
  • a linha que imprime a mensagem no console é uma instrução, em que novamente o parâmetro msg é uma expressão.

Por que as expressões são importantes no React?

Ao criar um aplicativo React, você pode injetar expressões JavaScript em seu código JSX. Por exemplo, você pode passar uma variável, escrever um manipulador de eventos ou uma condição. Para fazer isso, você precisa incluir seu código JS entre parênteses.

Por exemplo, você pode passar uma variável:

const Message = () => {
	const name = "Carlo";
	return <p>Welcome {name}!</p>;
}

Em resumo, os parênteses informam ao transpilador que você deve processar o código entre parênteses como código JS. Tudo o que vem antes da tag de abertura <p> e depois da tag de fechamento </p> é código JavaScript normal. Tudo o que estiver dentro das tags de abertura <p> e de fechamento </p> é processado como código JSX.

Aqui está outro exemplo:

const Message = () => {	
	const name = "Ann";
	const heading = <h3>Welcome {name}</h3>;
	return (
		<div>
			{heading}
			<p>This is your dashboard.</p>
		</div>
	);
}

Você também pode passar um objeto:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22',
		description: 'Content Writer'
	}

	return (
		<div>
			<h2>Welcome {person.name}</h2>
			<img
				className="card"
				src={person.avatar}
				alt={person.name}
			/>
			<p>Description: {person.description}.</p>
		</div>
	);
}

E abaixo você encontra um exemplo mais abrangente:

render(){
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: {
			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)', width: '200px'
		}
	}

	return (
		<div style={person.theme}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

Observe os parênteses duplos nos atributos style nos elementos img e div. Usamos parênteses duplos para passar dois objetos que contêm estilos de cartão e imagem.

Um exemplo de cartão criado com o React
Um exemplo de cartão criado com o React

Você deve ter notado que, em todos os exemplos acima, incluímos expressões JavaScript em JSX.

Imutabilidade no React

Mutabilidade e imutabilidade são dois conceitos fundamentais na programação funcional e orientada a objetos.

Imutabilidade significa que um valor não pode ser alterado depois de ter sido criado. Mutabilidade significa, é claro, o oposto.

No Javascript, os valores primitivos são imutáveis, o que significa que, uma vez criado, ele não pode ser alterado. Por outro lado, arrays e objetos são mutáveis, pois suas propriedades e elementos podem ser alterados sem atribuir um novo valor.

Há vários motivos para você usar objetos imutáveis no JavaScript:

  • Melhor desempenho
  • Redução do consumo de memória
  • Segurança da thread (thread-safety)
  • Facilidade de codificação e depuração

Seguindo o padrão de imutabilidade, uma vez que uma variável ou objeto é atribuído, ele não pode ser reatribuído ou alterado. Quando precisar modificar dados, você deve criar uma cópia deles e modificar seu conteúdo, deixando o conteúdo original inalterado.

A imutabilidade também é um conceito fundamental no React.

A documentação do React afirma que:

O estado de um componente de classe está disponível como this.state. O campo state deve ser um objeto. Não altere o estado diretamente. Se você quiser alterar o estado, chame o método setState com o novo estado.

Sempre que o estado de um componente muda, o React calcula se você deve renderizar novamente o componente e atualizar o Virtual DOM. Se o React não tivesse o registro do estado anterior, não poderia determinar se o componente deveria ser renderizado novamente ou não. A documentação do React fornece um excelente exemplo disso.

Quais recursos JavaScript podemos usar para garantir a imutabilidade do objeto de estado no React? Vamos descobrir!

Declarando variáveis

Você tem três maneiras de declarar uma variável em JavaScript: var, let, e const.

A declaraçãovar existe desde o início do JavaScript. Ela é usada para declarar uma variável com escopo de função ou global, opcionalmente inicializando com um valor.

Ao declarar uma variável usando var, você pode declarar novamente e atualizar essa variável no escopo global e local. O código a seguir é permitido:

// Declare a variable
var msg = "Hello!";

// Redeclare the same variable
var msg = "Goodbye!"

// Update the variable
msg = "Hello again!"

As declarações var são processadas antes da execução de qualquer código. Como resultado, declarar uma variável em qualquer lugar do código é equivalente a declará-la no topo. Esse comportamento é chamado de hoisting.

Vale ressaltar que apenas a declaração da variável é “hoisted”, não a inicialização, que ocorre apenas quando o fluxo de controle alcança a instrução de atribuição. Até esse ponto, a variável é undefined:

console.log(msg); // undefined
var msg = "Hello!";
console.log(msg); // Hello!

O escopo de uma var declarada com var em uma função JavaScript abrange todo o corpo dessa função.

Isso significa que a variável não é definida no nível do bloco, mas no nível da função inteira. Isso leva a vários problemas que podem tornar seu código JavaScript problemático e de difícil manutenção.

Para corrigir esses problemas, o ES6 introduziu a palavra-chave let.

A declaração let declara uma variável local com escopo de bloco e, opcionalmente, a inicializa com um valor.

Quais são as vantagens de let em relação ao var? Aqui estão algumas:

  • let declara uma variável com escopo de bloco, enquanto var declara uma variável globalmente ou localmente para uma função inteira, independentemente do escopo de bloco.
  • Variáveis globais let não são propriedades do objeto window. Você não pode acessá-las usando window.nomeDaVariavel.
  • A variável let só pode ser acessada após à sua declaração ser alcançada. A variável não é inicializada até que o fluxo de controle alcance a linha de código onde ela é declarada (as declarações let não são “hoisted”).
  • Tentar redeclarar uma variável com let resulta em um erro de sintaxe (SyntaxError).

Como as variáveis declaradas usando var não podem ter escopo de bloco, se você definir uma variável usando var em um loop ou dentro de uma declaração if, ela poderá ser acessada de fora do bloco, o que pode levar a um código com erros.

O código no primeiro exemplo é executado sem erros. Agora, substitua var por let no bloco de código visto acima:

console.log(msg);
let msg = "Hello!";
console.log(msg);

No segundo exemplo, o uso de let em vez de var produz um Uncaught ReferenceError:

Uncaught ReferenceError no Chrome
Uncaught ReferenceError no Chrome

O ES6 também introduz uma terceira palavra-chave: const.

const é bastante semelhante a let, mas com uma diferença fundamental:

Considere o exemplo a seguir:

const MAX_VALUE = 1000;
MAX_VALUE = 2000;

O código acima resultaria em um TypeError:

Uncaught TypeError: Atribuição a uma variável constante no Google Chrome
Uncaught TypeError: Atribuição a uma variável constante no Google Chrome

Além disso:

Se você declarar um const sem dar a ele um valor, ocorrerá o seguinte SyntaxError (consulte também ES6 In Depth: let e const):

Uncaught TypeError: Assignment to constant variable no Chrome
Uncaught TypeError: Assignment to constant variable no Chrome

Mas se uma constante for uma array ou um objeto, você poderá editar propriedades ou itens dentro dessa array, ou objeto.

Por exemplo, você pode alterar, adicionar e remover itens da array:

// Declare a constant array
const cities = ["London", "New York", "Sydney"];

// Change an item
cities[0] = "Madrid";

// Add an item
cities.push("Paris");

// Remove an item
cities.pop();

console.log(cities);

// Array(3)
// 0: "Madrid"
// 1: "New York"
// 2: "Sydney"

Mas você não tem permissão para reatribuir a array:

const cities = ["London", "New York", "Sydney"];

cities = ["Athens", "Barcelona", "Naples"];

O código acima resultaria em um TypeError.

Uncaught TypeError: Assignment to constant variable no Chrome
Uncaught TypeError: Assignment to constant variable no Chrome

Você pode adicionar, reatribuir e remover propriedades e métodos de objetos:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language',
	content: 'JavaScript is a scripting language that enables you to create dynamically updating content.'
};

// add a new property
post.slug = "javascript-is-awesome";

// Reassign property
post.id = 5;

// Delete a property
delete post.excerpt;

console.log(post);

// {id: 5, name: 'JavaScript is awesome', content: 'JavaScript is a scripting language that enables you to create dynamically updating content.', slug: 'javascript-is-awesome'}

Mas você não tem permissão para reatribuir o próprio objeto. O código a seguir passaria por um Uncaught TypeError:

// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};

post = {
	id: 1,
	name: 'React is powerful',
	excerpt: 'React lets you build user interfaces'
};

Object.freeze()

Agora concordamos que o uso do const nem sempre garante uma imutabilidade forte (especialmente quando você trabalha com objetos arrays). Então, como você pode implementar o padrão de imutabilidade em seus aplicativos React?

Primeiro, quando quiser impedir que os elementos de uma array ou as propriedades de um objeto sejam modificados, você pode usar o método estático Object.freeze().

Congelar um objeto impede extensões e torna as propriedades existentes não graváveis e não configuráveis. Um objeto congelado não pode mais ser alterado: novas propriedades não podem ser adicionadas, propriedades existentes não podem ser removidas, sua enumerabilidade, configurabilidade, escrevibilidade ou valor não podem ser alterados, e o protótipo do objeto não pode ser reatribuído. O método freeze() retorna o mesmo objeto que foi passado como argumento.

Qualquer tentativa de adicionar, alterar ou remover uma propriedade falhará, seja silenciosamente ou lançando um TypeError, mais comumente no modo estrito.

Você pode usar o Object.freeze() dessa forma:

'use strict'
// Declare a constant obj
const post = {
	id: 1,
	name: 'JavaScript is awesome',
	excerpt: 'JavaScript is an awesome scripting language'
};
// Freeze the object
Object.freeze(post);

Se agora você tentar adicionar uma propriedade, receberá um Uncaught TypeError:

// Add a new property
post.slug = "javascript-is-awesome"; // Uncaught TypeError
Uncaught TypeError: can't define property
Uncaught TypeError: can’t define property “slug”: Object is not extensible no Firefox

Quando você tenta reatribuir uma propriedade, obtém outro tipo de TypeError:

// Reassign property
post.id = 5; // Uncaught TypeError
Reatribuir uma propriedade somente leitura gera um Uncaught TypeError
Reatribuir uma propriedade somente leitura gera um Uncaught TypeError
Uncaught TypeError: Cannot assign to read only property 'id' of object '#<Object>'  no Google Chrome
Uncaught TypeError: Cannot assign to read only property ‘id’ of object ‘#<Object>’  no Google Chrome

Você também pode tentar excluir uma propriedade. O resultado será outro TypeError:

// Delete a property
delete post.excerpt; // Uncaught TypeError
Uncaught TypeError: property
Uncaught TypeError: property “excerpt” is non-configurable and can’t be deleted no Firefox

Template Literals

Quando você precisa combinar strings de caracteres com a saída de expressões em JavaScript, geralmente usa o operador de adição +. No entanto, você também pode usar um recurso do JavaScript que permite incluir expressões em strings de caracteres sem usar o operador de adição: Template Literals.

Template Literals são um tipo especial de strings de caracteres delimitadas por caracteres de backtick (`).

Em Template Literals, você pode incluir placeholders, que são expressões incorporadas delimitadas por um caractere de dólar e envolvidas por parênteses.

Aqui está um exemplo:

const align = 'left';
console.log(`This string is ${ align }-aligned`);

As strings de caracteres e os placeholders são passados para uma função padrão que executa a interpolação de strings de caracteres para substituir os placeholders e juntar as partes em uma única string de caracteres. Você também pode substituir a função padrão por uma função personalizada.

Você pode usar Template Literals para:

Strings com várias linhas: os caracteres de nova linha fazem parte do template literal.

console.log(`Twinkle, twinkle, little bat!
How I wonder what you’re at!`);

Interpolação de strings: Sem os Template Literals, você só pode usar o operador de adição para combinar a saída de expressões com strings. Veja o exemplo a seguir:

const a = 3;
const b = 7;
console.log("The result of " + a + " + " + b + " is " + (a + b));

É um pouco confuso, não é? Mas você pode escrever esse código de uma forma mais legível e de fácil manutenção usando Template Literals:

const a = 3;
const b = 7;
console.log(`The result of ${ a } + ${ b } is ${ a + b }`);

Mas lembre-se de que há uma diferença entre as duas sintaxes:

Template literals se prestam a vários usos. No exemplo a seguir, usamos um operador ternário para atribuir um valor para um atributo class.

const page = 'archive';
console.log(`class=${ page === 'archive' ? 'archive' : 'single' }`);

Abaixo, estamos realizando um cálculo simples:

const price = 100;
const VAT = 0.22;

console.log(`Total price: ${ (price * (1 + VAT)).toFixed(2) }`);

Também é possível aninhar Template Literals, incluindo dentro de um espaço reservado ${expression} (mas use modelos aninhados com cuidado, pois as estruturas complexas de strings podem ser difíceis de ler e manter).

Modelos com tags: Como mencionamos acima, também é possível definir uma função personalizada para realizar a concatenação de strings. Esse tipo de Template Literals é chamado de Tagged Template.

As tags permitem que você analise os Template Literals com uma função. O primeiro argumento de uma função de tag contém uma array de valores de string de caracteres. Os argumentos restantes estão relacionados às expressões.

As tags permitem que você analise Template Literals com uma função personalizada. O primeiro argumento dessa função é uma array de strings de caracteres incluídas no Template Literals, e os outros argumentos são as expressões.

Você pode criar uma função personalizada para executar qualquer tipo de operação nos argumentos do template e retornar a string manipulada. Aqui está um exemplo muito básico de Tagged Template.

const name = "Carlo";
const role = "student";
const organization = "North Pole University";
const age = 25;

function customFunc(strings, ...tags) {
	console.log(strings); // ['My name is ', ", I'm ", ', and I am ', ' at ', '', raw: Array(5)]
	console.log(tags); // ['Carlo', 25, 'student', 'North Pole University']
	let string = '';
	for ( let i = 0; i < strings.length - 1; i++ ){
		console.log(i + "" + strings[i] + "" + tags[i]);
		string += strings[i] + tags[i];
	}
	return string.toUpperCase();
}

const output = customFunc`My name is ${name}, I'm ${age}, and I am ${role} at ${organization}`;
console.log(output);

O código acima imprime os elementos de arrays de strings e tags e, em seguida, coloca os caracteres da string em maiúsculas antes de imprimir a saída no console do navegador.

Arrow Functions

Arrow Functions são uma alternativa às funções anônimas (funções sem nomes) no JavaScript, mas com algumas diferenças e limitações.

As declarações a seguir são exemplos válidos de Arrow Functions:

// Arrow function without parameters
const myFunction = () => expression;

// Arrow function with one parameter
const myFunction = param => expression;

// Arrow function with one parameter
const myFunction = (param) => expression;

// Arrow function with more parameters
const myFunction = (param1, param2) => expression;

// Arrow function without parameters
const myFunction = () => {
	statements
}

// Arrow function with one parameter
const myFunction = param => {
	statements
}

// Arrow function with more parameters
const myFunction = (param1, param2) => {
	statements
}

Você pode omitir os parênteses se passar apenas um parâmetro para a função. Se você passar dois ou mais parâmetros, deverá colocá-los entre parênteses. Aqui está um exemplo disso:

const render = ( id, title, category ) => `${id}: ${title} - ${category}`;
console.log( render ( 5, 'Hello World!', "JavaScript" ) );

A Arrow function de uma linha retornam um valor por padrão. Se você usar a sintaxe de várias linhas, terá de retornar manualmente um valor:

const render = ( id, title, category ) => {
	console.log( `Post title: ${ title }` );
	return `${ id }: ${ title } - ${ category }`;
}
console.log( `Post details: ${ render ( 5, 'Hello World!', "JavaScript" ) }` );

Uma diferença fundamental entre funções normais e Arrow Functions é que Arrow Functions não possuem seu próprio vínculo com a palavra-chave this. Se você tentar usar this em uma Arrow Function, ele será buscado fora do escopo da função.

Para obter uma descrição mais detalhada das Arrow functions e exemplos de uso, leia também a documentação da MDN (Mozilla Developer Network).

Classes

As classes em JavaScript são um tipo especial de função para criar objetos que usam o mecanismo de herança prototípica.

De acordo com a documentação da MDN (Mozilla Developer Network),

Quando se trata de herança, o JavaScript possui apenas um mecanismo: objetos. Cada objeto tem uma propriedade privada que contém um link para outro objeto chamado de seu protótipo. Esse objeto protótipo tem seu próprio protótipo, e assim por diante, até que um objeto com null como protótipo seja alcançado.

Assim como acontece com as funções, você tem duas maneiras de definir uma classe:

  • Expressão de classe (class expression)
  • Declaração de classe (class declaration)

Você pode usar a palavra-chave class para definir uma classe dentro de uma expressão, conforme mostrado no exemplo a seguir:

const Circle = class {
	constructor(radius) {
		this.radius = Number(radius);
	}
	area() {
		return Math.PI * Math.pow(this.radius, 2);
	}
	circumference() {
		return Math.PI * this.radius * 2;
	}
}
console.log('Circumference: ' + new Circle(10).circumference()); // 62.83185307179586
console.log('Area: ' + new Circle(10).area()); // 314.1592653589793

Uma classe possui um corpo, que é o código incluído entre chaves. É nele que você irá definir o construtor e os métodos, que também são chamados de membros da classe. O corpo da classe é executado no modo estrito (strict mode), mesmo sem usar a diretiva 'strict mode'.

O método constructor é usado para criar e inicializar um objeto criado com uma classe sendo executado automaticamente quando a classe é instanciada. Se você não definir um método construtor em sua classe, o JavaScript usará automaticamente um construtor padrão.

Uma classe pode ser estendida usando a palavra-chave extends.

class Book {
	constructor(title, author) {
		this.booktitle = title;
		this.authorname = author;
	}
	present() {
		return this.booktitle + ' is a great book from ' + this.authorname;
	}
}

class BookDetails extends Book {
	constructor(title, author, cat) {
		super(title, author);
		this.category = cat;
	}
	show() {
		return this.present() + ', it is a ' + this.category + ' book';
	}
}

const bookInfo = new BookDetails("The Fellowship of the Ring", "J. R. R. Tolkien", "Fantasy");
console.log(bookInfo.show());

Um construtor pode usar a palavra-chave super para chamar o construtor principal. Se você passar um argumento para o método super(), esse argumento também estará disponível na classe do construtor principal.

Para que você possa se aprofundar mais nas classes JavaScript e em vários exemplos de uso, consulte também documentação da MDN (Mozilla Developer Network).

As classes são frequentemente usadas para criar componentes React. Normalmente, você não criará suas próprias classes, mas estenderá as classes React incorporadas.

Todas as classes no React têm um método render() que retorna um elemento React:

class Animal extends React.Component {
	render() {
		return <h2>Hey, I am a {this.props.name}!</h2>;
	}
}

No exemplo acima, Animal é um componente de classe. Lembre-se de que:

  • O nome do componente deve começar com uma letra maiúscula
  • O componente deve incluir a expressão extends React.Component. Isso dá acesso aos métodos do React.Component.
  • O método render() retorna o HTML e é obrigatório.

Após criar seu componente de classe, você pode renderizar o HTML na página:

const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Animal name="Rabbit" />;
root.render(element);

A imagem abaixo mostra o resultado na página (você pode vê-lo em ação no CodePen).

Um componente de classe React simples
Um componente de classe React simples

Observe, no entanto, que o uso de componentes de classe no React não é recomendado, sendo preferível definir componentes como funções.

A palavra-chave ‘this’

Em JavaScript, a palavra-chave this é um espaço reservado genérico normalmente usado dentro de objetos, classes e funções, e se refere a diferentes elementos, dependendo do contexto ou do escopo.

A palavra-chave this pode ser usada no escopo global. Se você digitar this no console do navegador, obterá:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Você pode acessar qualquer um dos métodos e propriedades do objeto Window. Portanto, se você executar this.location no console do navegador, obterá a seguinte saída:

Location {ancestorOrigins: DOMStringList, href: 'https://kinsta.com/', origin: 'https://kinsta.com', protocol: 'https:', host: 'kinsta.com', ...}

Quando você usa this em um objeto, ele se refere ao próprio objeto. Dessa forma, você pode se referir aos valores de um objeto nos métodos do próprio objeto:

const post = { 
	id: 5,
	getSlug: function(){
		return `post-${this.id}`;
	},
	title: 'Awesome post', 
	category: 'JavaScript' 
};
console.log( post.getSlug );

Agora vamos tentar usar this em uma função:

const useThis = function () {
	return this;
}
console.log( useThis() );

Se você não estiver no modo estrito, obterá:

Window {window: Window, self: Window, document: document, name: '', location: Location, ...}

Mas se você invocar o modo estrito, obterá um resultado diferente:

const doSomething = function () {
	'use strict';
	return this;
}
console.log( doSomething() );

Nesse caso, a função retorna undefined. Isso ocorre porque this em uma função se refere ao seu valor explícito.

Então, como você pode definir this em uma função?

Primeiro, você pode atribuir manualmente propriedades e métodos à função:

function doSomething( post ) {
	this.id = post.id;
	this.title = post.title;
	console.log( `${this.id} - ${this.title}` );
}
new doSomething( { id: 5, title: 'Awesome post' } );

Mas você também pode usar os métodos call, apply e bind, bem como as arrow functions.

const doSomething = function() {
	console.log( `${this.id} - ${this.title}` );
}
doSomething.call( { id: 5, title: 'Awesome post' } );

O método call() pode ser usado em qualquer função e faz exatamente o que diz: chama a função.

Além disso, call() aceita qualquer outro parâmetro definido na função:

const doSomething = function( cat ) {
	console.log( `${this.id} - ${this.title} - Category: ${cat}` );
}
doSomething.call( { id: 5, title: 'Awesome post' }, 'JavaScript' );
const doSomething = function( cat1, cat2 ) {
	console.log( `${this.id} - ${this.title} - Categories: ${cat1}, ${cat2}` );
}
doSomething.apply( { id: 5, title: 'Awesome post' }, ['JavaScript', 'React'] );
const post = { id: 5, title: 'Awesome post', category: 'JavaScript' };
const doSomething = function() {
	return `${this.id} - ${this.title} - ${this.category}`;
}
const bindRender = doSomething.bind( post );
console.log( bindRender() );

Uma alternativa às opções discutidas acima é o uso de arrow functions.

As expressões de arrow functions devem ser usadas somente para funções que não sejam de método, pois elas não têm seu próprio this.

Isso torna as arrow functions particularmente úteis com manipuladores de eventos.

Isso porque “quando o código é chamado a partir de um atributo de manipulador de eventos em linha, seu this é definido como o elemento DOM no qual o ouvinte está posicionado” (consulte documentação da MDN (Mozilla Developer Network).

Mas as coisas mudam com as arrow functions porque…

… as arrow functions estabelecem this com base no escopo em que a arrow function é definida, e o valor this não muda com base em como a função é chamada.

Vinculando “this” aos manipuladores de eventos no React

No contexto do React, você tem algumas maneiras de garantir que o manipulador de eventos não perca seu contexto:

1. Use bind() dentro do método render:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage.bind( this ) }>Show message from state!</button> );
	}
}
export default MyComponent;

2. Vincule o contexto ao manipulador de eventos no construtor:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	constructor(props) {
		super(props);
		this.showMessage = this.showMessage.bind( this );
	}

	showMessage(){
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={ this.showMessage }>Show message from state!</button> );
	}
}
export default MyComponent;

3. Defina o manipulador de eventos usando arrow functions:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage = () => {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={this.showMessage}>Show message from state!</button> );
	}
}
export default MyComponent;

4. Use arrow functions no método de renderização:

import React, { Component } from 'react';
class MyComponent extends Component {
	state = { message: 'Hello World!' };

	showMessage() {
		console.log( 'This refers to: ', this );
		console.log( 'The message is: ', this.state.message );
	}

	render(){
		return( <button onClick={()=>{this.showMessage()}}>Show message from state!</button> );
	}
}
export default MyComponent;

Independentemente do método escolhido, quando você clica no botão, o console do navegador mostra a seguinte saída:

This refers to:  MyComponent {props: {…}, context: {…}, refs: {…}, updater: {…}, state: {…}, …}
The message is:  Hello World!

Operador ternário

O operador condicional (ou operador ternário) permite que você escreva expressões condicionais simples em JavaScript. Ele usa três operandos:

  • uma condição seguida de um ponto de interrogação (?),
  • uma expressão a ser executada se a condição for verdadeira, seguida de dois pontos (:),
  • uma segunda expressão a ser executada se a condição for falsa.
const drink = personAge >= 18 ? "Wine" : "Juice";

Também é possível encadear várias expressões:

const drink = personAge >= 18 ? "Wine" : personAge >= 6 ? "Juice" : "Milk";

No entanto, tenha cuidado, pois encadear várias expressões pode levar a um código confuso e difícil de manter.

O operador ternário é particularmente útil no React, especialmente no código JSX, que aceita apenas expressões dentro de chaves.

Por exemplo, você pode usar o operador ternário para definir o valor de um atributo com base em uma condição específica:

render(){			
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/...',
		description: 'Content Writer',
		theme: 'light'
	}

	return (
		<div
			className='card' 
			style={
				person.theme === 'dark' ? 
				{ background: 'black', color: 'white' } : 
				{ background: 'white', color: 'black'} 
			}>
			<img
				src={person.avatar}
				alt={person.name}
				style={ { width: '100%' } }
			/>
			<div style={ { padding: '2px 16px' } }>
				<h3>{person.name}</h3>
				<p>{person.description}.</p>
			</div>
		</div>
	);
}

No código acima, verificamos a condição person.theme === 'dark' para definir o valor do atributo style do contêiner div.

Avaliação de curto-circuito

O operador lógico AND (&&) avalia os operandos da esquerda para a direita e retorna true somente se todos os operandos forem true.

O operador lógico AND é um operador de curto-circuito. Cada operando é convertido em um booleano e, se o resultado da conversão for false, o operador AND para e retorna o valor original do operando falso. Se todos os valores forem true, ele retornará o valor original do último operando.



A avaliação de curto-circuito é um recurso JavaScript comumente usado no React, pois permite que você produza blocos de código com base em condições específicas. Aqui está um exemplo:

{
	displayExcerpt &&
	post.excerpt.rendered && (
		<p>
			<RawHTML>
				{ post.excerpt.rendered }
			</RawHTML>
		</p>
	)
}

No código acima, se displayExcerpt AND post.excerpt.rendered for avaliado como true, o React retornará o bloco final de JSX.

Para recapitular, “se a condição for true, o elemento logo após && aparecerá na saída. Se for false, o React o ignorará e omitirá”.

Sintaxe de espalhamento (Spread Syntax)

No JavaScript, a sintaxe de espalhamento permite expandir um elemento iterável, como um array ou objeto, em argumentos de função, literais de array ou literais de objeto.

No exemplo a seguir, estamos desempacotando um array em uma chamada de função:

function doSomething( x, y, z ){
	return `First: ${x} - Second: ${y} - Third: ${z} - Sum: ${x+y+z}`;
}
const numbers = [3, 4, 7];
console.log( doSomething( ...numbers ) );

Você pode usar a sintaxe de propagação para duplicar uma array (até mesmo arrays multidimensionais) ou para concatenar arrays. Nos exemplos a seguir, concatenamos duas arrays de duas maneiras diferentes:

const firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray.push( ...secondArray );
console.log( firstArray );

Alternativamente:

let firstArray = [1, 2, 3];
const secondArray = [4, 5, 6];
firstArray = [ ...firstArray, ...secondArray];
console.log( firstArray );

Você também pode usar a sintaxe de propagação para clonar ou mesclar dois objetos:

const firstObj = { id: '1', title: 'JS is awesome' };
const secondObj = { cat: 'React', description: 'React is easy' };

// clone object
const thirdObj = { ...firstObj };

// merge objects
const fourthObj = { ...firstObj, ...secondObj }

console.log( { ...thirdObj } );
console.log( { ...fourthObj } );

Atribuição de desestruturação

Outra estrutura sintática que você encontrará com frequência usada no React é a sintaxe de atribuição de desestruturação.

No exemplo a seguir, desempacotamos valores de uma array:

const user = ['Carlo', 'Content writer', 'Kinsta'];
const [name, description, company] = user;
console.log( `${name} is ${description} at ${company}` );

E aqui está um exemplo simples de atribuição de desestruturação com um objeto:

const user = {
	name: 'Carlo',
	description: 'Content writer',
	company: 'Kinsta'
}
const { name, description, company } = user;
console.log( `${name} is ${description} at ${company}` );

Mas podemos fazer ainda mais. No exemplo a seguir, desempacotamos algumas propriedades de um objeto e atribuímos as propriedades restantes a outro objeto usando a sintaxe de propagação:

const user = {
	name: 'Carlo',
	family: 'Daniele',
	description: 'Content writer',
	company: 'Kinsta',
	power: 'swimming'
}
const { name, description, company, ...rest } = user;
console.log( rest ); // {family: 'Daniele', power: 'swimming'}

Você também pode atribuir valores a uma array:

const user = [];
const object = { name: 'Carlo', company: 'Kinsta' };
( { name: user[0], company: user[1] } = object );
console.log( user ); // (2) ['Carlo', 'Kinsta']

Observe que os parênteses ao redor da instrução de atribuição são necessários quando você usa a atribuição de desestruturação literal de objeto sem uma declaração.

Para obter uma análise mais detalhada da atribuição de desestruturação, com vários exemplos de uso, consulte documentação da MDN (Mozilla Developer Network).

filter(), map() e reduce()

O JavaScript fornece vários métodos úteis que você verá serem usados com frequência no React.

filter()

No exemplo a seguir, aplicamos o filtro à array numbers para obter uma array cujos elementos são números maiores que 5:

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.filter( number => number > 5);
console.log(result); // (4) [6, 8, 9, 23]

No exemplo a seguir, obtemos uma array de publicações com a palavra “JavaScript” incluída no título:

const posts = [
	{id: 0, title: 'JavaScript is awesome', content: 'your content'},
	{id: 1, title: 'WordPress is easy', content: 'your content'},
	{id: 2, title: 'React is cool', content: 'your content'},
	{id: 3, title: 'With JavaScript to the moon', content: 'your content'},
];

const jsPosts = posts.filter( post => post.title.includes( 'JavaScript' ) );

console.log( jsPosts );
Uma array de publicações em que o título inclui "JavaScript
Uma array de publicações em que o título inclui “JavaScript

map()

const numbers = [2, 6, 8, 2, 5, 9, 23];
const result = numbers.map( number => number * 5 );
console.log(result); // (7) [10, 30, 40, 10, 25, 45, 115]

Em um componente React, você encontrará com frequência o método map() usado para criar listas. No exemplo a seguir, estamos mapeando o objeto posts do WordPress para criar uma lista de artigos:

<ul>
	{ posts && posts.map( ( post ) => {
		return (
			<li key={ post.id }>
				<h5>
					<a href={ post.link }>
						{ 
							post.title.rendered ? 
							post.title.rendered :
							__( 'Default title', 'author-plugin' )
						}
					</a>
				</h5>
			</li>
		)
	})}
</ul>

reduce()

reduce() aceita dois parâmetros:

  • Uma função de callback a ser executada para cada elemento da array. Ela retorna um valor que se torna o valor do parâmetro acumulador na próxima chamada. Na última chamada, a função retorna o valor que será o valor de retorno de reduce().
  • Um valor inicial que é o primeiro valor do acumulador passado para a função de callback.

A função de callback recebe alguns parâmetros:

  • Um acumulador: O valor retornado da chamada anterior para a função de callback. Na primeira chamada, ele é definido como um valor inicial, se especificado. Caso contrário, ele assume o valor do primeiro item da array.
  • O valor do elemento atual: O valor é definido como o primeiro elemento da array (array[0]) se um valor inicial tiver sido definido; caso contrário, ele assume o valor do segundo elemento (array[1]).
  • O índice atual é a posição do índice do elemento atual.

Um exemplo deixará tudo mais claro.

const numbers = [1, 2, 3, 4, 5];
const initialValue = 0;
const sumElements = numbers.reduce(
	( accumulator, currentValue ) => accumulator + currentValue,
	initialValue
);
console.log( numbers ); // (5) [1, 2, 3, 4, 5]
console.log( sumElements ); // 15

Vamos descobrir em detalhes o que acontece em cada iteração. Volte ao exemplo anterior e altere o endereço initialValue:

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log('Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index);
		return accumulator + currentValue;
	},
	initialValue
);
console.log( sumElements );

A imagem a seguir mostra a saída no console do navegador:

usando reduce() com valor inicial definido como 5
usando reduce() com valor inicial definido como 5

Agora vamos ver o que acontece sem o parâmetro initialValue:

const numbers = [1, 2, 3, 4, 5];
const sumElements = numbers.reduce(
	( accumulator, currentValue, index ) => {
		console.log( 'Accumulator: ' + accumulator + ' - currentValue: ' + currentValue + ' - index: ' + index );
		return accumulator + currentValue;
	}
);
console.log( sumElements );
Usando reduce() sem valor inicial
Usando reduce() sem valor inicial

Mais exemplos e casos de uso são discutidos no site de documentação da MDN (Mozilla Developer Network).

Exportações e importações

A partir do ECMAScript 2015 (ES6), é possível exportar valores de um módulo JavaScript e importá-los para outro script. Você usará as importações e exportações extensivamente em seus aplicativos React e, portanto, é importante ter um bom entendimento de como elas funcionam.

O código a seguir cria um componente funcional. A primeira linha importa a biblioteca React:

import React from 'react';

function MyComponent() {
	const person = {
		name: 'Carlo',
		avatar: 'https://en.gravatar.com/userimage/954861/fc68a728946aac04f8531c3a8742ac22?size=original',
		description: 'Content Writer',
		theme: 'dark'
	}
 
	return (
		<div
			className = 'card'
			style = {
				person.theme === 'dark' ?
				{ background: 'black', color: 'white' } :
				{ background: 'white', color: 'black'}
			}>
			<img
				src = { person.avatar }
				alt = { person.name }
				style = { { width: '100%' } }
			/>
			<div
				style = { { padding: '2px 16px' } }
			>
				<h3>{ person.name }</h3>
				<p>{ person.description }.</p>
			</div>
		</div>
	);
}
export default MyComponent;

Usamos a palavra-chave import seguida pelo nome que queremos atribuir ao que estamos importando, seguido pelo nome do pacote que queremos instalar, conforme mencionado no arquivo package.json.

Observe que na função MyComponent() acima, usamos alguns dos recursos JavaScript discutidos nas seções anteriores. Incluímos valores de propriedade entre parênteses e atribuímos o valor da propriedade style usando a sintaxe do operador condicional.

O script termina com a exportação do nosso componente personalizado.

Agora que você sabe um pouco mais sobre importações e exportações, vamos dar uma olhada mais de perto em como elas funcionam.

Exportação

Cada módulo React pode ter dois tipos diferentes de exportação: exportação nomeada e exportação padrão.

Por exemplo, você pode exportar vários recursos de uma só vez com uma única instrução export:

export { MyComponent, MyVariable };

Você também pode exportar recursos individuais (function, class, const, let):

export function MyComponent() { ... };
export let myVariable = x + y;

Mas você só pode ter uma única exportação padrão:

export default MyComponent;

Você também pode usar a exportação padrão para recursos individuais:

export default function() { ... }
export default class { ... }

Importação

Depois que o componente tiver sido exportado, ele poderá ser importado para outro arquivo, por exemplo, um arquivo index.js, com outros módulos:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyComponent from './MyComponent';

const root = ReactDOM.createRoot( document.getElementById( 'root' ) );
root.render(
	<React.StrictMode>
		<MyComponent />
	</React.StrictMode>
);

No código acima, usamos a declaração de importação de várias maneiras.

Nas duas primeiras linhas, atribuímos um nome aos recursos importados; na terceira linha, não atribuímos um nome, mas simplesmente importamos o arquivo ./index.css. A última declaração import importa o arquivo ./MyComponent e atribui um nome.

Vamos descobrir as diferenças entre essas importações.

No total, há quatro tipos de importações:

Importação nomeada

import { MyFunction, MyVariable } from "./my-module";

Importação padrão

import MyComponent from "./MyComponent";

Importação de espaço de nome

import * as name from "my-module";

Importação de efeito colateral

import "module-name";

Após adicionar alguns estilos ao index.css, o cartão deverá ter a aparência da imagem abaixo, na qual você também pode ver o código HTML correspondente:

Um componente React simples
Um componente React simples

Observe que as declarações do import só podem ser usadas em módulos no nível superior (não dentro de funções, classes etc.).

Para obter uma visão geral mais abrangente das declarações import e export, você também pode consultar os seguintes recursos:

Resumo

O React é uma das bibliotecas JavaScript mais populares da atualidade e é uma das habilidades mais requisitadas no mundo do desenvolvimento da web.

Com o React, é possível criar aplicativos da web dinâmicos e interfaces avançadas. Criar aplicativos grandes, dinâmicos e interativos pode ser fácil graças a seus componentes reutilizáveis.

Mas o React é uma biblioteca JavaScript, e um bom entendimento dos principais recursos do JavaScript é essencial para começar sua jornada com o React. É por isso que reunimos em um só lugar alguns dos recursos do JavaScript mais comumente usados no React. Dominar esses recursos dará a você uma vantagem em sua jornada de aprendizado do React.

E quando se trata de desenvolvimento web, fazer a transição do JS/React para o WordPress requer muito pouco esforço.

Agora é a sua vez, quais recursos do JavaScript você acha mais úteis no desenvolvimento do React? Deixamos passar alguma coisa importante que você gostaria de ver em nossa lista? Compartilhe suas ideias conosco nos comentários abaixo.

Carlo Daniele Kinsta

Carlo é um apaixonado por webdesign e desenvolvimento frontend. Ele tem mais de 10 anos de experiência com WordPress e colaborou com diversas universidades e instituições educacionais na Itália e na Europa. Carlo já publicou inúmeros artigos e guias sobre WordPress, tanto em sites italianos quanto internacionais, além de revistas impressas. Você pode seguir ele no LinkedIn e no X.