Revisão de bibliotecas front-end
---
id: 6724e2dbf723fe1c8883cc69
title: Revisão de bibliotecas front-end
challengeType: 31
dashedName: review-front-end-libraries
---
# --description--
Trabalhando com estado e o hook
Trabalhando com o hook
Trabalhando com o hook
Trabalhando com o hook
Trabalhando com o hook
Trabalhando com o hook
Arquivo
Bibliotecas e frameworks JavaScript
- Bibliotecas e frameworks JavaScript oferecem soluções rápidas para problemas comuns e aceleram o desenvolvimento ao fornecer código pré-construído.
- Bibliotecas geralmente são mais focadas em fornecer soluções para tarefas específicas, como manipular o DOM, lidar com eventos ou gerenciar requisições AJAX.
- Alguns exemplos de bibliotecas JavaScript são jQuery e React.
- Frameworks, por outro lado, fornecem uma estrutura mais definida para construir aplicações. Eles frequentemente vêm com um conjunto de regras e convenções que os desenvolvedores precisam seguir.
- Exemplos de frameworks incluem Angular e Next.js, um meta framework para React.
- Aplicações de página única (SPAs) são aplicações web que carregam uma única página HTML e atualizam essa página dinamicamente conforme o usuário interage com a aplicação, sem recarregar a página inteira.
- SPAs usam JavaScript para gerenciar o estado da aplicação e renderizar conteúdo. Isso é frequentemente feito usando frameworks que fornecem ótimas ferramentas para construir interfaces de usuário complexas.
- Alguns problemas relacionados às SPAs incluem:
- Leitores de tela têm dificuldade com conteúdo atualizado dinamicamente.
- A URL não muda quando o usuário navega dentro da aplicação, o que pode dificultar marcar, voltar ou compartilhar páginas específicas.
- O tempo de carregamento inicial pode ser lento se a aplicação for grande, pois todos os assets precisam ser carregados de uma vez.
React
- React é uma biblioteca JavaScript popular para construir interfaces de usuário e aplicações web.
- Um conceito central do React é a criação de componentes de UI reutilizáveis que podem atualizar e renderizar independentemente conforme os dados mudam.
- React permite que desenvolvedores descrevam como a UI deve parecer com base no estado da aplicação. React então atualiza e renderiza os componentes corretos quando os dados ou o estado mudam.
Componentes React
- Componentes são os blocos de construção das aplicações React que permitem aos desenvolvedores dividir interfaces de usuário complexas em partes menores e gerenciáveis.
- A UI é descrita usando JSX, uma extensão da sintaxe JavaScript, que permite escrever código parecido com HTML dentro do JavaScript.
- Componentes são basicamente funções JS ou classes que retornam um pedaço de UI.
function Greeting() {
const name = 'Anna';
return <h1>Welcome, {name}!</h1>;
}
Para usar o componente, você pode simplesmente chamar:
<Greeting />
Importando e exportando componentes React
- Uma exportação torna um componente disponível para outros arquivos. Uma importação permite que um arquivo use um componente que foi exportado em outro lugar.
- Existem dois tipos de exportações em React: exportações padrão e exportações nomeadas.
- Um arquivo pode ter apenas uma exportação padrão. É ideal para um arquivo que contém principalmente um único componente.
- Você pode exportar um componente como exportação padrão assim:
// City.js
function City() {
return <p>New York</p>;
}
export default City;
- Você também pode exportar na mesma linha da definição do componente:
export default function City() {
return <p>New York</p>;
}
- Para importar uma exportação padrão, use a palavra-chave
importsem chaves:
// App.js
import City from './City';
function App() {
return (
<div>
<h1>My favorite city is:</h1>
<City />
</div>
);
}
Exportações nomeadas
- Exportações nomeadas permitem que um arquivo compartilhe múltiplos componentes. Diferente das exportações padrão, elas devem ser importadas usando exatamente o nome com que foram exportadas.
- Você pode exportar múltiplos componentes do mesmo arquivo assim:
// Animals.jsx
export function Cat() {
return <h2>Mr. Whiskers</h2>;
}
export function Dog() {
return <h2>Fido</h2>;
}
- Para importar exportações nomeadas, use chaves com os nomes exatos exportados:
import { Cat, Dog } from './Animals';
- Você pode renomear uma exportação nomeada durante a importação usando a palavra-chave
as:
import { Cat as Kitty } from './Animals';
Exportações padrão e nomeadas misturadas
- Um arquivo pode ter uma exportação padrão e múltiplas exportações nomeadas ao mesmo tempo:
// Animals.jsx
export default function Cat() {
return <h2>Mr. Whiskers</h2>;
}
export function Dog() {
return <h2>Fido</h2>;
}
- Ao importar exportações mistas, a exportação padrão vem primeiro (sem chaves), seguida pelas exportações nomeadas (dentro de chaves):
import Cat, { Dog } from './Animals';
Configurando um projeto React usando Vite
- Ferramentas de configuração de projeto e CLIs fornecem uma maneira rápida e fácil de iniciar novos projetos, permitindo que desenvolvedores foquem em escrever código em vez de lidar com configuração.
- Vite é uma ferramenta popular de configuração de projeto e pode ser usada com React.
- Para criar um novo projeto com Vite, você pode usar o seguinte comando no terminal:
npm create vite@latest my-react-app -- --template react
Esse comando cria um novo projeto React chamado my-react-app usando o template React do Vite. No diretório do projeto, você verá um arquivo package.json com as dependências e comandos listados.
- Para executar o projeto, navegue até o diretório do projeto e execute os seguintes comandos:
cd my-react-app # path to the project directory
npm install # installs the dependencies listed in the package.json file
- Após as dependências serem instaladas, você deve notar uma nova pasta no seu projeto chamada
node_modules. - A pasta
node_modulesé onde todos os pacotes e bibliotecas necessários pelo seu projeto são armazenados. - Para rodar seu projeto, use o seguinte comando:
npm run dev
- Depois disso, abra seu navegador e navegue até
http://localhost:5173para ver sua aplicação React rodando. - Para realmente ver o código do template inicial, você pode entrar no seu projeto dentro da pasta
srce deverá ver o arquivoApp.jsx.
Passando props em componentes React
- Em React, props (abreviação de propriedades) são uma forma de passar dados de um componente pai para um componente filho. Esse mecanismo é necessário para criar elementos de UI reutilizáveis e dinâmicos.
- Props podem ser qualquer valor JavaScript. Para passar props de um componente pai para um filho, você adiciona as props como atributos quando usa o componente filho no JSX do pai. Aqui está um exemplo simples:
// Parent component
function Parent() {
const name = 'Anna';
return <Child name={name} />;
}
// Child component
function Child(props) {
return <h1>Hello, {props.name}!</h1>;
}
Você pode passar múltiplas props usando o operador spread (...), após convertê-las em um objeto. Aqui está um exemplo:
// Parent component
function Parent() {
const person = {
name: 'Anna',
age: 25,
city: 'New York'
};
return <Child {...person} />;
}
Neste código, o operador spread {...person} converte o objeto person em props individuais que são passadas para o componente Child.
Renderização condicional em React
- A renderização condicional em React permite criar interfaces de usuário dinâmicas. É usada para mostrar conteúdos diferentes com base em certas condições ou estados dentro da aplicação.
- Existem várias formas de renderizar conteúdo condicionalmente em React. Uma abordagem comum é usar o operador ternário. Aqui está um exemplo:
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in</h1>}
</div>
);
}
- Outra forma de renderizar conteúdo condicionalmente é usar o operador lógico AND (
&&). Isso é útil quando você quer renderizar conteúdo apenas se uma certa condição for verdadeira. Aqui está um exemplo:
function Greeting({ user }) {
return (
<div>
{user && <h1>Welcome, {user.name}!</h1>}
</div>
);
}
No código acima, o elemento h1 é renderizado somente se o objeto user for truthy.
Você também pode usar uma declaração if direta assim:
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in</h1>;
}
Renderizando listas em React
- Renderizar listas em React é uma tarefa comum ao construir interfaces de usuário.
- Listas podem ser renderizadas usando o método
map()do array JS para iterar sobre um array de itens e retornar um novo array de elementos JSX. - Por exemplo, se você tem um array de nomes que quer renderizar como uma lista, pode fazer o seguinte:
function NameList({ names }) {
return (
<ul>
{names.map((name, index) => (
<li key={${name}-${index}}>{name}</li>
))}
</ul>
);
}
- Sempre lembre de fornecer uma key única para cada item da lista para ajudar o React a gerenciar as atualizações e renderizações. Com essas técnicas, você pode criar listas flexíveis, eficientes e dinâmicas em suas aplicações React.
Estilos inline em React
- Estilos inline em React permitem aplicar estilos CSS diretamente aos elementos JSX usando objetos JavaScript.
- Para aplicar estilos inline em React, você pode usar o atributo style nos elementos JSX. O atributo style recebe um objeto onde as chaves são propriedades CSS em camelCase e os valores são os valores correspondentes. Aqui está um exemplo:
function Greeting() {
return (
<h1
style={{ color: 'blue', fontSize: '24px', backgroundColor: 'lightgray' }}
>
Hello, world!
</h1>
);
}
export default Greeting;
Você também pode extrair os estilos para um objeto separado e referenciá-lo no atributo style assim:
function Greeting() {
const styles = {
color: 'blue',
fontSize: '24px',
backgroundColor: 'lightgray'
};
return <h1 style={styles}>Hello, world!</h1>;
}
export default Greeting;
- Estilos inline suportam estilização dinâmica permitindo aplicar estilos condicionalmente com base em props ou estado. Aqui está um exemplo de como aplicar estilos condicionalmente com base em uma prop:
function Greeting({ isImportant }) {
const styles = {
color: isImportant ? 'red' : 'black',
fontSize: isImportant ? '24px' : '16px'
};
return <h1 style={styles}>Hello, world!</h1>;
}
export default Greeting;
- No código acima, os estilos
colorefontSizesão definidos condicionalmente com base na propisImportant.
Trabalhando com eventos em React
- Sistema de eventos sintéticos: Essa é a forma do React de lidar com eventos. Ele funciona como um wrapper em torno dos eventos nativos como
click,keydownesubmit. Manipuladores de eventos em React usam a convenção camelcase para nomeação. (Ex.onClick,onSubmit, etc)
onClick para um elemento button em React:
function handleClick() {
console.log("Button clicked!");
}
<button onClick={handleClick}>Click Me</button>;
Em React, funções manipuladoras de eventos geralmente começam com o prefixo handle para indicar que são responsáveis por lidar com eventos, como handleClick ou handleSubmit.
Quando uma ação do usuário dispara um evento, o React passa um objeto Synthetic Event para seu manipulador. Esse objeto se comporta de forma semelhante ao objeto Event nativo do JavaScript puro, fornecendo propriedades como type, target e currentTarget.
Para prevenir comportamentos padrão como o refresh do navegador durante um evento onSubmit, por exemplo, você pode chamar o método preventDefault():
function handleSubmit(event) {
event.preventDefault();
console.log("Form submitted!");
}
<form onSubmit={handleSubmit}>
<input type="text" />
<button>Submit</button>
</form>;
Você também pode envolver uma função manipuladora em uma arrow function assim:
function handleDelete(id) {
console.log("Deleting item:", id);
}
<button onClick={() => handleDelete(1)}>Delete Item</button>;
Trabalhando com estado e o hook useState
- Definição de estado: Em React, estado é um objeto que contém dados para um componente. Quando o estado é atualizado, o componente será re-renderizado. React trata o estado como imutável, o que significa que você não deve modificá-lo diretamente.
- Hook
useState(): O hookuseStateé uma função que permite declarar variáveis de estado em componentes funcionais. Aqui está a sintaxe básica:
const [stateVariable, setStateFunction] = useState(initialValue);
Na variável de estado você tem o seguinte:
stateVariablemantém o valor atual do estadosetStateFunction(a função setter) atualiza a variável de estadoinitialValuedefine o estado inicial
Counter:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>{count}</h2>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
Renderização e componentes React
- Definição: Em React, renderizar é o processo pelo qual os componentes aparecem na interface do usuário (UI), geralmente no navegador. O processo de renderização consiste em três etapas: trigger, render e commit.
Atualizando objetos e arrays no estado
- Atualizando objetos no estado: Se você precisa atualizar um objeto no estado, deve criar um novo objeto ou copiar um objeto existente primeiro, depois definir o estado para esse novo objeto. Qualquer objeto colocado no estado deve ser considerado somente leitura. Aqui está um exemplo de definir o nome, idade e cidade de um usuário. A função
handleChangeé usada para lidar com atualizações das informações do usuário:
import { useState } from "react";
function Profile() {
const [user, setUser] = useState({ name: "John Doe", age: 31, city: "LA" });
const handleChange = (e) => {
const { name, value } = e.target;
setUser((prevUser) => ({...prevUser, [name]: value}));
};
return (
<div>
<h1>User Profile</h1>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>City: {user.city}</p>
<h2>Update User Age </h2>
<input type="number" name="age" value={user.age} onChange={handleChange} />
<h2>Update User Name </h2>
<input type="text" name="name" value={user.name} onChange={handleChange} />
<h2>Update User City </h2>
<input type="text" name="city" value={user.city} onChange={handleChange} />
</div>
);
}
export default Profile;
- Atualizando arrays no estado: Ao atualizar arrays no estado, é importante não modificar diretamente o array usando métodos como
push()oupop(). Em vez disso, você deve criar um novo array ao atualizar o estado:
const addItem = () => {
const newItem = {
id: items.length + 1,
name: Item ${items.length + 1},
};
// Creates a new array
setItems((prevItems) => [...prevItems, newItem]);
};
Se quiser remover itens de um array, deve usar o método filter(), que retorna um novo array após filtrar o que deseja remover:
const removeItem = (id) => {
setItems((prevItems) => prevItems.filter((item) => item.id !== id));
};
Referenciando valores usando refs
- Atributo
ref: Você pode acessar um nó DOM em React usando o atributoref. Aqui está um exemplo que mostra umrefpara focar um elementoinput. A propriedadecurrenté usada para acessar o valor atual desseref:
import { useRef } from "react";
const Focus = () => {
const inputRef = useRef(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Enter text" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
};
export default Focus;
Trabalhando com o hook useEffect
- Hook
useEffect(): Em React, um efeito é qualquer coisa que acontece fora do processo de renderização do componente. Ou seja, qualquer coisa que o React não gerencia diretamente como parte da renderização da UI. Exemplos comuns incluem buscar dados, atualizar o título da aba do navegador, ler ou escrever no armazenamento local do navegador, obter a localização do usuário e muito mais. Essas operações interagem com o mundo externo e são conhecidas como efeitos colaterais. React fornece o hookuseEffectpara permitir que você lide com esses efeitos colaterais.useEffectpermite executar uma função após o componente renderizar ou atualizar.
import { useEffect } from "react";
useEffect(() => {
// Your side effect logic (usually a function) goes here
}, [dependencies]);
A função de efeito é executada após o componente renderizar, enquanto o argumento opcional dependencies controla quando o efeito é executado.
Note que dependencies pode ser um array de "valores reativos" (estado, props, funções, variáveis, etc), um array vazio ou omitido completamente. Veja como essas opções controlam o funcionamento do useEffect:
- Se
dependenciesfor um array que inclui um ou mais valores reativos, o efeito será executado sempre que eles mudarem. - Se
dependenciesfor um array vazio,useEffectexecuta apenas uma vez quando o componente renderiza pela primeira vez. - Se você omitir
dependencies, o efeito executa toda vez que o componente renderiza ou atualiza.
Como criar hooks personalizados
- Hooks personalizados: Um hook personalizado permite extrair lógica reutilizável de componentes, como busca de dados, gerenciamento de estado, alternância e efeitos colaterais como rastreamento do status online. Em React, todos os hooks embutidos começam com a palavra
use, então seu hook personalizado deve seguir a mesma convenção.
useDebounce:
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
export { useDebounce };
Trabalhando com formulários em React
- Entradas controladas: Isso ocorre quando você armazena o valor do campo de entrada no estado e o atualiza através de eventos
onChange. Isso dá controle completo sobre os dados do formulário e permite validação instantânea e renderização condicional.
import { useState } from "react";
function App() {
const [name, setName] = useState("");
const handleChange = (e) => {
setName(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(name);
};
return (
<>
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your name</label> <br />
<input value={name} id="name" onChange={handleChange} type="text" />
<button type="submit">Submit</button>
</form>
</>
);
}
export default App;
- Entradas não controladas: Em vez de lidar com as entradas através do hook
useState, entradas não controladas em HTML mantêm seu próprio estado interno com a ajuda do DOM. Como o DOM controla os valores das entradas, você precisa obter os valores dos campos de entrada com umref.
import { useRef } from "react";
function App() {
const nameRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log(nameRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Your</label>{" "}
<input type="text" ref={nameRef} id="name" />
<button type="submit">Submit</button>
</form>
);
}
export default App;
Trabalhando com o hook useActionState
- Ações do servidor: São funções que rodam no servidor para permitir o manuseio de formulários diretamente no servidor sem a necessidade de endpoints API. Aqui está um exemplo de uma aplicação Next.js:
"use server";
async function submitForm(formData) {
const name = formData.get("name");
return { message: Hello, ${name}! };
}
A diretiva "use server" marca a função como uma ação do servidor.
- Hook
useActionState: Esse hook atualiza o estado com base no resultado de uma submissão de formulário. Aqui está a sintaxe básica do hookuseActionState:
const [state, action, isPending] = useActionState(actionFunction, initialState, permalink);
stateé o estado atual que a ação retorna.actioné a função que dispara a ação do servidor.isPendingé um booleano que indica se a ação está rodando no momento.actionFunctionparâmetro é a própria ação do servidor.initialStateé o parâmetro que representa o ponto inicial para o estado antes da ação rodar.permalinké uma string opcional que contém a URL única da página que o formulário modifica.
Busca de dados em React
- Opções para buscar dados: Existem várias formas de buscar dados em React. Você pode usar a Fetch API nativa ou ferramentas de terceiros como Axios ou SWR.
- Variáveis de estado comumente usadas ao buscar dados: Independentemente da forma que escolher para buscar dados em React, há algumas variáveis de estado que você precisará acompanhar. A primeira é os próprios dados. A segunda rastreia se os dados ainda estão sendo buscados. A terceira é uma variável de estado que captura quaisquer erros que possam ocorrer durante o processo de busca.
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Como a busca de dados é um efeito colateral, é melhor usar o Fetch API dentro de um hook useEffect.
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!res.ok) {
throw new Error("Network response was not ok");
}
const data = await res.json();
setData(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
Então você pode renderizar uma mensagem de carregamento se a busca não estiver completa, uma mensagem de erro se houve erro na busca, ou os resultados.
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>{error.message}</p>;
}
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
Se quiser usar Axios, você precisa instalá-lo e importá-lo:
npm i axios
import axios from "axios";
Depois, você pode buscar os dados usando axios.get:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
setData(res.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
Para buscar dados usando o hook useSWR, você precisa primeiro instalá-lo e importá-lo.
npm i swr
import useSWR from "swr";
Aqui está como usar o hook para buscar dados:
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((res) => res.json());
const FetchTodos = () => {
const { data, error } = useSWR(
"https://jsonplaceholder.typicode.com/todos",
fetcher
);
if (!data) {
return <h2>Loading...</h2>;
}
if (error) {
return <h2>Error: {error.message}</h2>;
}
return (
<>
<h2>Todos</h2>
<div>
{data.map((todo) => (
<h3 key={todo.id}>{todo.title}</h3>
))}
</div>
</>
);
};
export default FetchTodos;
Trabalhando com o hook useOptimistic
- Hook
useOptimistic: Esse hook é usado para manter as UIs responsivas enquanto espera que uma ação assíncrona seja concluída em segundo plano. Ele ajuda a gerenciar "atualizações otimistas" na UI, uma estratégia na qual você fornece atualizações imediatas para a UI com base no resultado esperado de uma ação, como aguardar uma resposta do servidor.
const [optimisticState, addOptimistic] = useOptimistic(actualState, updateFunction);
optimisticStateé o estado temporário que atualiza imediatamente para uma melhor experiência do usuário.addOptimisticé a função que aplica a atualização otimista antes que o estado real mude.actualStateé o valor real do estado que vem do resultado de uma ação, como buscar dados de um servidor.updateFunctioné a função que determina como o estado otimista deve ser atualizado quando chamada.
useOptimistic em um componente TaskList:
"use client";
import { useOptimistic } from "react";
export default function TaskList({ tasks, addTask }) {
const [optimisticTasks, addOptimisticTask] = useOptimistic(
tasks,
(state, newTask) => [...state, { text: newTask, pending: true }]
);
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
addOptimisticTask(formData.get("task"));
addTask(formData);
e.target.reset();
}
return <>{/* UI */}</>;
}
startTransition: Esse é usado para renderizar parte da UI e marcar uma atualização de estado como uma transição não urgente. Isso permite que a UI seja responsiva durante atualizações custosas. Aqui está a sintaxe básica:
startTransition(action)
O action executa uma atualização de estado ou dispara alguma lógica relacionada à transição. Isso garante que atualizações urgentes da UI (como digitar ou clicar) não sejam bloqueadas.
Trabalhando com o hook useMemo
- Memoização: Essa é uma técnica de otimização na qual o resultado de chamadas de função custosas é armazenado em cache (lembrado) com base em argumentos específicos. Quando os mesmos argumentos são fornecidos novamente, o resultado em cache é retornado em vez de recalcular a função.
- Hook
useMemo: Esse hook é usado para memorizar valores computados. Aqui está um exemplo de memorizar o resultado de ordenar um array grande. OexpensiveSortFunctionsó será executado quandolargeArraymudar:
const memoizedSortedArray = useMemo(
() => expensiveSortFunction(largeArray),
[largeArray]
);
Trabalhando com o hook useCallback
- Hook
useCallback: Esse é usado para memorizar referências de funções.
const handleClick = useCallback(() => {
// code goes here
}, [dependency]);
React.memo: Esse é usado para memorizar um componente para evitar re-renderizações desnecessárias quando sua prop não mudou.
const MemoizedComponent = React.memo(({ prop }) => {
return (
<>
{/* Presentation */}
</>
)
});
Ferramentas de gerenciamento de dependências
- Definição de dependência: Em software, uma dependência é quando um componente ou módulo em uma aplicação depende de outro para funcionar adequadamente. Dependências são comuns em aplicações de software porque permitem que desenvolvedores usem funções ou ferramentas pré-construídas criadas por outros. As duas dependências principais necessárias para um projeto React serão os pacotes
reactereact-dom:
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
- Definição de gerenciador de pacotes: Para gerenciar dependências de software em um projeto, você precisará usar um gerenciador de pacotes. Um gerenciador de pacotes é uma ferramenta usada para instalação, atualizações e remoção de dependências. Muitas linguagens populares como JavaScript, Python, Ruby e Java usam gerenciadores de pacotes. Gerenciadores populares para JavaScript incluem npm, Yarn e pnpm.
- Arquivo
package.json: Esse é um arquivo de configuração chave em projetos que contém metadados sobre seu projeto, incluindo nome, versão e dependências. Ele também define scripts, informações de licença e outras configurações que ajudam a gerenciar o projeto e suas dependências. - Arquivo
package-lock.json: Esse arquivo bloqueia as versões exatas de todos os pacotes que seu projeto está usando. Quando você atualiza um pacote, as novas versões também são atualizadas no arquivo de bloqueio. - Pasta
node_modules: Essa pasta contém o código real das dependências listadas no seu arquivopackage.json, incluindo tanto as dependências diretas do seu projeto quanto as dependências dessas dependências. - Dependências de desenvolvimento: São pacotes usados apenas para desenvolvimento e não em produção. Um exemplo disso seria uma biblioteca de testes como Jest. Você instalaria Jest como uma dependência de desenvolvimento porque ele é necessário para testar sua aplicação localmente, mas não para rodar a aplicação em produção.
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"vite": "^6.0.5"
}
React Router
- Introdução: React Router é uma biblioteca de terceiros que permite adicionar roteamento às suas aplicações React. Para começar, você precisará instalar o React Router em um projeto React existente assim:
npm i react-router
Então, dentro do arquivo main.jsx ou index.jsx, você precisará configurar a estrutura de rotas assim:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>
</StrictMode>
);
Os componentes path e element são usados para ligar a URL e os componentes da UI juntos. Neste caso, estamos configurando uma rota para a página inicial que aponta para o componente App.
- Múltiplas views e configuração de rotas: É comum em aplicações maiores ter múltiplas views e rotas configuradas assim:
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products">
<Route index element={<ProductsHome />} />
<Route path=":category" element={<Category />} />
<Route path=":category/:productId" element={<ProductDetail />} />
<Route path="trending" element={<Trending />} />
</Route>
</Routes>
A prop index nesses exemplos representa a rota padrão para um dado segmento de caminho. Então o componente Home será mostrado na raiz /, enquanto o componente ProductsHome será mostrado no caminho /products.
- Aninhamento de rotas: Você pode aninhar rotas dentro de outras rotas, o que resulta no caminho da rota filho sendo anexado ao caminho da rota pai.
<Route path="products">
<Route path="trending" element={<Trending />} />
</Route>
No exemplo acima, o caminho para os produtos em alta será products/trending.
- Segmentos dinâmicos: Um segmento dinâmico é quando qualquer parte do caminho da URL é dinâmica.
<Route path=":category" element={<Category />} />
Neste exemplo temos um segmento dinâmico chamado category. Quando um usuário navega para uma URL como products/brass-instruments, a view muda para o componente Category e você pode buscar dinamicamente os dados apropriados com base no segmento.
- Hook
useParams: Esse hook é usado para acessar os parâmetros dinâmicos de um caminho de URL.
import { useParams } from "react-router";
export default function Category() {
let params = useParams();
{/* Accessing the category param: params.category */}
{/* rest of code goes here */}
}
Frameworks React
- Introdução: Frameworks React fornecem recursos como roteamento, otimização de imagens, busca de dados, autenticação e mais. Isso significa que você pode não precisar configurar aplicações front-end e back-end separadas para certos casos de uso. Exemplos de frameworks React incluem Next.js e Remix.
- Roteamento Next.js: Esse sistema de roteamento inclui suporte para rotas dinâmicas, rotas paralelas, manipuladores de rota, redirecionamentos, internacionalização e mais.
export async function GET() {
const res = await fetch("https://example-api.com");
const data = await res.json();
return Response.json({ data });
}
- Otimização de imagens Next.js: O componente
Imageestende o elemento HTML nativoimge permite carregamentos de página mais rápidos e otimizações de tamanho. Isso significa que imagens só serão carregadas quando entrarem na viewport e o componenteImageservirá automaticamente imagens no tamanho correto para cada dispositivo.
import Image from "next/image";
export default function Page() {
return (
<Image src="link-to-image-goes-here" alt="descriptive-title-goes-here" />
);
}
Prop drilling
- Definição: Prop drilling é o processo de passar props de um componente pai para componentes filhos profundamente aninhados, mesmo quando alguns desses filhos não precisam das props.
Gerenciamento de estado
- Context API: Contexto refere-se a quando um componente pai torna informações disponíveis para componentes filhos sem precisar passá-las explicitamente via props.
createContexté usado para criar um objeto de contexto que representa o contexto que outros componentes irão ler.Provideré usado para fornecer valores de contexto aos componentes filhos.
import { useState, createContext } from "react";
const CounterContext = createContext();
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContext, CounterProvider };
- Redux: Redux gerencia o estado fornecendo uma store central e controle rígido sobre atualizações de estado. Ele usa um padrão previsível com ações, reducers e middleware. Ações são payloads de informação que enviam dados da sua aplicação para a store Redux, frequentemente disparadas por interações do usuário. Reducers são funções que especificam como o estado deve mudar em resposta a essas ações, garantindo que o estado seja atualizado de forma imutável. Middleware, por outro lado, atua como uma ponte entre o disparo da ação e o reducer, permitindo estender a funcionalidade do Redux (ex.: logging, operações assíncronas) sem modificar o fluxo principal.
- Zustand: Essa solução de gerenciamento de estado é ideal para aplicações de pequeno a médio porte. Funciona usando um hook
useStorepara acessar o estado diretamente em componentes e páginas. Isso permite modificar e acessar dados sem precisar de ações, reducers ou um provider.
Depuração de componentes React usando React DevTools
- Ferramentas de desenvolvedor React: Essa é uma extensão de navegador que você pode usar no Chrome, Firefox e Edge para inspecionar componentes React e identificar problemas de desempenho. Para Safari, você precisará instalar o pacote npm
react-devtools. Após instalar o React DevTools e abrir uma aplicação React no navegador, abra as ferramentas de desenvolvedor para acessar as duas abas extras fornecidas para depurar React – Components e Profiler. - Aba Components: Essa aba exibe cada componente para você em formato de árvore. Aqui estão algumas coisas que você pode fazer nessa aba:
- visualizar a hierarquia de componentes da aplicação
- verificar e modificar props, estados e valores de contexto em tempo real
- verificar o código fonte de cada componente selecionado
- registrar os dados do componente no console
- inspecionar os elementos DOM do componente
- Aba Profiler: Essa aba ajuda a analisar o desempenho dos componentes. Você pode gravar o desempenho dos componentes para identificar re-renderizações desnecessárias, visualizar durações de commit e otimizar componentes lentos.
Componentes React Server
- Definição: Componentes React Server são componentes React que renderizam exclusivamente no servidor, enviando apenas o HTML final para o cliente. Isso significa que esses componentes podem acessar diretamente recursos do lado servidor e reduzir drasticamente a quantidade de JavaScript enviada para o navegador.
Diferenças entre desempenho real e percebido
- Desempenho percebido: É como os usuários percebem o desempenho de um site. É como eles avaliam em termos de responsividade e confiabilidade. Essa é uma medida subjetiva, então é difícil quantificar, mas é muito importante, pois a experiência do usuário determina o sucesso ou fracasso de um site.
- Desempenho real: É o desempenho objetivo e mensurável do site. É medido usando métricas como tempo de carregamento da página, tempo de resposta do servidor e tempo de renderização. Essas medições são influenciadas por múltiplos fatores relacionados à rede e ao próprio código.
Técnicas para melhorar o desempenho percebido
- Lazy loading: Essa técnica reduz o tempo de carregamento inicial ao máximo possível carregando recursos não essenciais em segundo plano.
- Minimizar atrasos de fontes: Se seu site usa fontes personalizadas, você também deve tentar minimizar atrasos no carregamento das fontes, pois isso pode resultar em flickering ou na exibição da fonte fallback enquanto a fonte personalizada está sendo carregada. Uma sugestão é usar uma fonte fallback semelhante à fonte personalizada, para que a mudança seja mais sutil caso isso aconteça.
- Uso de indicadores de carregamento: Mostrar um indicador de carregamento para um processo longo assim que o usuário clicar em um elemento pode ajudar o usuário a se sentir conectado e engajado com o processo, fazendo o tempo de espera parecer menor.
Conceitos centrais de desempenho
- Ordem do código fonte: Refere-se à forma como os elementos HTML são estruturados no documento. Isso determina o que carrega primeiro e pode impactar significativamente o desempenho e a acessibilidade.
- Colocar conteúdo crítico como títulos, navegação ou texto principal mais alto na estrutura HTML.
- Adiar scripts não essenciais, como os de analytics ou widgets de terceiros, para que não bloqueiem a renderização.
- Usar aprimoramento progressivo, para garantir que a experiência principal funcione mesmo antes dos estilos e scripts carregarem. Aprimoramento progressivo é uma forma de construir sites e aplicações baseada na ideia de que sua página deve funcionar com HTML primeiro.
<h1>Welcome to FastSite!</h1>
<p>Critical information loads first.</p>
<script src="slow-script.js" defer></script>
- Caminho crítico de renderização: É a sequência de passos que o navegador segue para converter código em pixels na tela.
- Latência: É o tempo que uma requisição leva para viajar entre o navegador e o servidor. Ou seja, alta latência significa páginas lentas.
- Usar CDNs, ou redes de entrega de conteúdo, para servir arquivos de locais mais próximos.
- Habilitar compressão usando coisas como Gzip para reduzir o tamanho dos arquivos.
- Otimizar imagens e usar lazy loading.
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
Melhorando o INP
- Definição: INP (Interaction to Next Paint) avalia a responsividade geral de uma página medindo o tempo desde quando o usuário interage, como um clique ou pressionar tecla, até a próxima vez que o navegador atualiza a exibição. Um INP menor indica uma página mais responsiva.
- Reduzir o trabalho da thread principal dividindo tarefas longas de JavaScript.
- Usar
requestIdleCallback()para scripts não críticos. Isso enfileira uma função para ser chamada durante períodos ociosos do navegador. - Adiar ou carregar assets pesados de forma preguiçosa, como visto anteriormente.
- Otimizar manipuladores de eventos. Se esses manipuladores rodarem com muita frequência ou realizarem operações pesadas, podem deixar a página lenta e aumentar o INP. A solução para isso é debouncing. Debouncing garante que a função só rode depois que o usuário parar de digitar por um curto atraso - por exemplo, 300ms. Isso previne cálculos desnecessários e melhora o desempenho.
Como a renderização funciona no navegador
- Como a renderização funciona: Primeiro o navegador analisa o HTML e constrói o DOM. Depois, o navegador processa o CSS, construindo o CSS Object Model, ou CSSOM. Essa é outra estrutura em árvore que dita como os elementos devem ser estilizados. Finalmente, o navegador pinta os pixels na tela, renderizando cada elemento com base nos estilos e layout calculados. Em páginas complexas, isso pode envolver múltiplas camadas que são compostas juntas para formar a saída visual final.
Como o desempenho impacta a sustentabilidade
- Informações de fundo: A internet responde por cerca de 2% das emissões globais de carbono — o mesmo que a indústria aérea! Cada byte transferido requer eletricidade, desde data centers até dispositivos dos usuários. Arquivos maiores e scripts ineficientes significam mais consumo de energia. Um site de alto desempenho não é apenas mais rápido, ele também reduz processamento e uso de energia desnecessários.
Formas de reduzir o tempo de carregamento da página
- Otimizar assets de mídia: Imagens e vídeos grandes são culpados comuns por carregamentos lentos. Otimizando esses assets, você pode acelerar significativamente seu site. Isso inclui comprimir imagens, usar formatos modernos como WebP e usar lazy loading para assets.
- Aproveitar cache do navegador: Cache permite que navegadores armazenem partes do seu site localmente, reduzindo tempos de carregamento para visitantes que retornam.
- Minificar e comprimir arquivos: Reduzir o tamanho dos seus arquivos pode levar a downloads mais rápidos. Isso inclui reduzir o tamanho dos arquivos transmitidos e minificar arquivos CSS e JavaScript.
Melhorando o "tempo até usável"
- Definição: "tempo até usável" é o intervalo desde quando um usuário solicita uma página até quando ele pode interagir de forma significativa com ela. Para melhorar o "tempo até usável" você pode carregar seus assets de forma preguiçosa ou minimizar recursos que bloqueiam a renderização.
Métricas chave para medir desempenho
- First Contentful Paint ou FCP: Mede quão rápido o primeiro pedaço de conteúdo — texto ou imagem — aparece na tela. Um bom FCP é considerado abaixo de 1,8 segundos, e um FCP ruim é acima de 3 segundos. Você pode verificar seu FCP usando o Chrome DevTools na aba de desempenho.
- Total Blocking Time: Mostra quanto tempo a thread principal fica bloqueada por tarefas pesadas de JavaScript. Se o Total Blocking Time (TBT) for alto, os usuários experimentam interações lentas. Para melhorar o TBT, divida tarefas longas e adie scripts não essenciais.
- Bounce Rate: É a porcentagem de visitantes que saem sem interagir. Se seu site tem altas taxas de bounce, pode ser porque sua página está lenta.
- Usuários únicos: Essa métrica rastreia quantos visitantes individuais acessam seu site. Para ver Bounce Rate e Usuários Únicos, você pode usar o Google Analytics. Ele permite monitorar essas métricas e melhorar o engajamento.
Ferramentas comuns para medir desempenho
- Chrome DevTools: Chrome DevTools é uma ferramenta embutida no Google Chrome que permite analisar e depurar desempenho em tempo real. DevTools mostra tempos de carregamento, uso da CPU e atrasos na renderização. É especialmente útil para medir o First Contentful Paint, ou FCP, que indica quão rápido o usuário vê o primeiro conteúdo visível. Se seu site parecer lento, DevTools ajuda a identificar os gargalos.
- Lighthouse: Essa é uma ferramenta automatizada que verifica desempenho, SEO e acessibilidade.
- WebPageTest: Essa ferramenta permite testar como seu site carrega de diferentes locais e dispositivos. Ela fornece uma análise detalhada do Speed Index, Total Blocking Time e outras métricas chave de desempenho. Se quiser saber como usuários reais experimentam seu site globalmente, WebPageTest é a ferramenta ideal.
- PageSpeed Insights: Essa ferramenta analisa seu site e sugere melhorias rápidas para mobile e desktop. Ela indica o que está deixando seu site lento e dá recomendações específicas como otimizar imagens, remover scripts que bloqueiam renderização e reduzir tempos de resposta do servidor. PageSpeed Insights é uma forma rápida e fácil de verificar como o Google vê o desempenho do seu site.
- Monitoramento de usuários reais: Ferramentas RUM rastreiam o comportamento real dos usuários, mostrando como visitantes reais experimentam seu site. Ferramentas RUM populares incluem Google Analytics, que monitora tempos de carregamento e taxas de bounce, e New Relic ou Datadog, que monitoram problemas de desempenho em tempo real. Se quiser dados de usuários reais, ferramentas RUM são essenciais.
Trabalhando com Web APIs de desempenho
- Definição: Web APIs de desempenho permitem que desenvolvedores monitorem quão eficientemente uma página web carrega e responde diretamente no código. Essas APIs permitem medir tempos de carregamento, rastrear atrasos na renderização e interação e analisar o tempo de execução do JavaScript.
performance.now(): Essa API fornece timestamps de alta precisão (em milissegundos) para medir quanto tempo diferentes partes do seu site levam para carregar.
const start = performance.now();
// Run some code here
const end = performance.now();
console.log(Execution time: ${end - start}ms);
- Performance Timing API: Essa API fornece uma análise detalhada de cada etapa do carregamento da página, desde a resolução DNS até
DOMContentLoaded.
const timing = performance.timing;
const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
console.log(Page load time: ${pageLoadTime}ms);
PerformanceObserver: Essa API escuta eventos de desempenho como mudanças de layout, tarefas longas e interações do usuário.
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(Long task detected: ${entry.duration}ms);
});
});
observer.observe({ type: "longtask", buffered: true });
Técnicas para melhorar o desempenho do CSS
- Animações CSS: Animar certas propriedades CSS, como dimensões, posição e layout, dispara um processo chamado "reflow", durante o qual o navegador recalcula a posição e geometria de certos elementos na página. Isso requer uma repintura, que é computacionalmente custosa. Portanto, recomenda-se reduzir o número de animações CSS ao máximo possível ou pelo menos dar ao usuário a opção de ativá-las ou desativá-las.
Técnicas para melhorar o desempenho do JavaScript
- Divisão de código: Dividir seu código JavaScript em módulos que executam tarefas críticas e não críticas também é útil. Assim, você poderá pré-carregar os críticos o quanto antes e adiar os não críticos para renderizar a página o mais rápido possível.
- Manipulação do DOM: Lembre-se que manipulação do DOM refere-se ao processo de alterar dinamicamente o conteúdo de uma página com JavaScript ao interagir com o Document Object Model (DOM). Manipular o DOM é computacionalmente custoso. Reduzir a quantidade de manipulação do DOM no seu código JavaScript melhora o desempenho.
Frameworks CSS
- Frameworks CSS: Frameworks CSS podem acelerar seu fluxo de trabalho, criar um estilo visual uniforme em um site, fazer seu design parecer consistente em múltiplos navegadores e manter seu código CSS mais organizado.
- Frameworks CSS populares: Alguns frameworks CSS populares são Tailwind CSS, Bootstrap, Materialize e Foundation.
- Possíveis desvantagens:
- O CSS fornecido pelo framework pode entrar em conflito com seu CSS personalizado.
- Seu site pode parecer semelhante a outros sites que usam o mesmo framework.
- Frameworks grandes podem causar problemas de desempenho.
Dois tipos de frameworks CSS
- Frameworks CSS utility-first: Esses frameworks têm pequenas classes com propósitos específicos, como definir margem, preenchimento ou cor de fundo. Você pode atribuir essas pequenas classes diretamente aos elementos HTML conforme necessário. Tailwind CSS é categorizado como um framework CSS utility-first.
<button class="bg-blue-500 text-white font-bold py-2 px-4 rounded-full hover:bg-blue-700">
Button
</button>
- Frameworks CSS baseados em componentes: Esses frameworks têm componentes pré-construídos com estilos pré-definidos que você pode adicionar ao seu site. Os componentes estão disponíveis na documentação oficial do framework CSS, e você pode copiá-los e colá-los no seu projeto. Bootstrap é categorizado como um framework CSS baseado em componentes.
<div class="card" style="width: 25rem;">
<ul class="list-group list-group-flush">
<li class="list-group-item">HTML</li>
<li class="list-group-item">CSS</li>
<li class="list-group-item">JavaScript</li>
</ul>
</div>
Tailwind CSS
Tailwind é um framework CSS utility-first. Em vez de escrever regras CSS personalizadas, você constrói seus designs combinando pequenas classes utilitárias diretamente no seu HTML.Utilitários de design responsivo
Tailwind usa prefixos comosm:, md: e lg: para aplicar estilos em diferentes tamanhos de tela.
<div class="w-full md:w-1/2 lg:flex-row">
Responsive layout
</div>
Utilitários Flexbox
Classes comoflex, flex-col, justify-around e items-center facilitam a criação de layouts flexíveis.
<div class="flex flex-col md:flex-row justify-around items-center">
<p>Column on small screens</p>
<p>Row on medium and larger screens</p>
</div>
Utilitários Grid
Tailwind inclui utilitários para CSS Grid, comogrid, grid-cols-1 e md:grid-cols-3.
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="bg-gray-100 p-4">Column 1</div>
<div class="bg-gray-100 p-4">Column 2</div>
<div class="bg-gray-100 p-4">Column 3</div>
</div>
Utilitários de espaçamento
Utilitários comomt-8, mx-auto, p-4 e gap-4 ajudam a criar espaçamentos consistentes sem escrever CSS.
<div class="mt-8 p-4 bg-indigo-600 text-white">
Spaced content
</div>
Utilitários de tipografia
Utilitários comouppercase, font-bold, font-semibold e text-4xl controlam a aparência do texto.
Você pode definir tamanhos de fonte que se ajustam em pontos de interrupção, como text-3xl e md:text-5xl.
<h1 class="text-3xl md:text-5xl font-semibold text-center">
Responsive Heading
</h1>
Cores e estados de hover
Tailwind oferece uma ampla paleta de cores, comotext-red-700, bg-indigo-600 e bg-gray-100.
Classes como hover:bg-pink-600 tornam efeitos interativos simples.
<a href="#" class="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-md">
Hover Me
</a>
Bordas, anéis e efeitos
- Bordas:
border-2 border-red-300adiciona bordas com espessura e cores especificadas. - Anéis:
ring-1 ring-gray-300cria efeitos semelhantes a contornos, frequentemente usados para foco ou cartões. - Cantoneiras arredondadas e escalonamento: Classes como
rounded-md,rounded-xlescale-105adicionam acabamento.
<div class="p-6 rounded-xl ring-2 ring-fuchsia-500 scale-105">
Highlighted card
</div>
Gradientes
Tailwind suporta utilitários de gradiente comobg-gradient-to-r from-fuchsia-500 to-indigo-600.
<div class="p-4 text-white bg-gradient-to-r from-fuchsia-500 to-indigo-600">
Gradient background
</div>
Pré-processadores CSS
- Pré-processador CSS: Um pré-processador CSS é uma ferramenta que estende o CSS padrão. Ele compila o código com sintaxe estendida em um arquivo CSS nativo. Pode ser útil para escrever CSS mais limpo, reutilizável, menos repetitivo e escalável para projetos complexos.
- Recursos: Alguns recursos fornecidos por pré-processadores CSS são variáveis, mixins, aninhamento e herança de seletores.
- Pré-processadores CSS populares: Alguns pré-processadores CSS populares são Sass, Less e Stylus.
- Possíveis desvantagens:
- Compilar as regras CSS em CSS padrão pode causar overhead.
- O código compilado pode ser difícil de depurar.
Sass
- Sass: É um dos pré-processadores CSS mais populares. Sass significa "Syntactically Awesome Style Sheets".
- Recursos suportados pelo Sass: Sass suporta recursos como variáveis, regras CSS aninhadas, módulos, mixins, herança e operadores para operações matemáticas básicas.
Duas sintaxes suportadas pelo Sass
- Sintaxe SCSS: O SCSS (Sassy CSS) expande a sintaxe básica do CSS. É a sintaxe mais usada para Sass. Arquivos SCSS têm a extensão
.scss.
$primary-color: #3498eb;
header {
background-color: $primary-color;
}
- Sintaxe indentada: A sintaxe indentada foi a sintaxe original do Sass e também é conhecida como "sintaxe Sass".
$primary-color: #3498eb
header
background-color: $primary-color
Mixins
- Mixins: Mixins permitem agrupar múltiplas propriedades CSS e seus valores sob um nome e reutilizar esse bloco de código CSS em toda sua folha de estilos.
center-flex. Ele tem três propriedades CSS para centralizar elementos usando flexbox.
@mixin center-flex {
display: flex;
justify-content: center;
align-items: center;
}
Aqui está um exemplo de uso do mixin que você definiu.
section {
@include center-flex;
height: 500px;
background-color: #3289a8;
}
Testes manuais e automatizados
- Teste manual: No teste manual, um testador percorre manualmente cada parte da aplicação e testa diferentes funcionalidades para garantir que funcionem corretamente. Se bugs forem encontrados no processo, o testador reporta esses bugs para a equipe de software para que sejam corrigidos.
- Teste automatizado: No teste automatizado, você pode automatizar seus testes escrevendo um programa separado que verifica se sua aplicação se comporta como esperado.
Testes unitários
- Teste unitário: No teste unitário, você testa cada função para garantir que tudo está funcionando como esperado. Testes unitários também podem servir como forma de documentação para sua aplicação porque representam o comportamento esperado do seu código.
- Princípio da responsabilidade única: O princípio da responsabilidade única recomenda manter cada função pequena e responsável por uma única coisa.
- Frameworks comuns de teste JavaScript: Alguns frameworks comuns de teste incluem Jest, Mocha e Vitest. Jest é um framework popular para testes unitários.
export function getFormattedWord(str) {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
}
Em um arquivo getFormattedWord.test.js separado, você pode escrever alguns testes para verificar se a função está funcionando como esperado. O arquivo getFormattedWord.test.js ficará assim:
import { getFormattedWord } from "./getFormattedWord.js";
import { test, expect } from "jest";
test('capitalizes the first letter of a word', () => {
expect(getFormattedWord('hello')).toBe('Hello');
});
- Função
expect: A funçãoexpecté usada para testar um valor. - Matcher: Matcher é uma função que verifica se o valor se comporta como esperado. No exemplo acima, o matcher é
toBe(). Jest tem uma variedade de matchers.
jest usando npm i jest. Também precisará adicionar um script ao seu arquivo package.json assim:
"scripts": {
"test": "jest"
},
Então, você pode rodar o comando npm run test para executar seus testes.
Ciclo de vida do desenvolvimento de software
- Diferentes etapas do ciclo de vida do desenvolvimento de software:
- Etapa de planejamento: A equipe de desenvolvimento coleta requisitos para o trabalho proposto dos stakeholders.
- Etapa de design: A equipe de software divide os requisitos e decide as melhores abordagens para as soluções.
- Etapa de implementação: A equipe de software divide os requisitos em tarefas gerenciáveis e as implementa.
- Etapa de testes: Envolve testes manuais e automatizados para o novo trabalho. Às vezes, a equipe testa a aplicação durante toda a etapa de desenvolvimento para detectar e corrigir quaisquer problemas.
- Etapa de implantação: A equipe implanta as novas mudanças em um ambiente de build ou teste.
- Etapa de manutenção: Envolve corrigir quaisquer problemas que surgirem dos clientes na aplicação em produção.
- Diferentes modelos do ciclo de vida do desenvolvimento de software:
- Modelo waterfall: O modelo waterfall é onde cada fase do ciclo de vida precisa ser concluída antes que a próxima fase possa começar.
- Modelo ágil: O modelo ágil foca no desenvolvimento iterativo dividindo o trabalho em sprints.
BDD e TDD
- TDD: Desenvolvimento orientado a testes é uma metodologia que enfatiza escrever testes primeiro. Escrever testes antes de construir funcionalidades fornece feedback em tempo real para desenvolvedores durante o processo de desenvolvimento.
- BDD: Desenvolvimento orientado a comportamento é a abordagem de alinhar uma série de testes com objetivos de negócio. Os cenários de teste em BDD devem ser escritos em uma linguagem que possa ser entendida tanto por pessoas técnicas quanto não técnicas. Um exemplo dessa sintaxe é Gherkin.
- Frameworks de teste BDD: Exemplos de frameworks de teste BDD incluem Cucumber, JBehave e SpecFlow.
Assertivas em testes unitários
- Assertiva: Assertivas são usadas para testar se o código está se comportando como esperado.
- Bibliotecas de assertiva: Chai é uma biblioteca de assertiva comumente usada. Outras bibliotecas comuns de assertiva JavaScript são
should.jseexpect.js.
addThreeAndFour é igual ao número 7:
assert.equal(addThreeAndFour(), 7);
- Melhores práticas: Independentemente da biblioteca de assertiva que você usar, deve escrever mensagens claras de assertiva e falha que ajudem a entender quais testes estão falhando e por quê.
Mocking, faking e stubbing
- Mocking: Mocking é o processo de substituir dados reais por dados falsos que simulam o comportamento de componentes reais. Por exemplo, você pode mockar a resposta da API em testes em vez de fazer chamadas contínuas à API para buscar os dados.
- Stubbing: Stubs são objetos que retornam respostas pré-definidas ou dados fictícios para um comportamento esperado em uma aplicação. Por exemplo, você pode stubar o comportamento de uma conexão de banco de dados nos seus testes sem precisar depender de uma conexão real.
- Faking: Fakes são versões simplificadas de componentes reais sem a complexidade ou efeitos colaterais. Por exemplo, você pode fakear um banco de dados armazenando os dados na memória em vez de interagir com o banco real. Isso permite simular operações de banco de dados na memória, que será muito mais rápido do que lidar com o banco real.
Testes funcionais
- Teste funcional: Teste funcional verifica se as funcionalidades e funções da aplicação funcionam como esperado. O objetivo do teste funcional é testar o sistema como um todo contra múltiplos cenários.
- Teste não funcional: Teste não funcional foca em aspectos como desempenho e confiabilidade.
- Teste smoke: Teste smoke é uma verificação preliminar no sistema para problemas básicos ou críticos antes de iniciar testes mais extensos.
Testes end-to-end
- Teste end-to-end: Teste end-to-end, ou E2E, testa cenários do mundo real do ponto de vista do usuário. Testes end-to-end ajudam a garantir que sua aplicação se comporte corretamente e seja previsível para os usuários. Porém, é demorado configurar, projetar e manter.
- Frameworks de teste end-to-end: Playwright é um framework popular de teste end-to-end desenvolvido pela Microsoft. Outros exemplos de ferramentas de teste end-to-end incluem Cypress, Selenium e Puppeteer.
beforeEach será executado antes de cada teste. Os testes verificam que o doador tem um link de apoiador na barra de menu, assim como uma borda estilizada especial ao redor do avatar:
test.beforeEach(async ({ page }) => {
execSync("node ./tools/scripts/seed/seed-demo-user --set-true isDonating");
await page.goto("/donate");
});
...
test("The menu should have a supporters link", async ({ page }) => {
const menuButton = page.getByTestId("header-menu-button");
const menu = page.getByTestId("header-menu");
await expect(menuButton).toBeVisible();
await menuButton.click();
await expect(menu).toBeVisible();
await expect(page.getByRole("link", { name: "Supporters" })).toBeVisible();
});
test("The Avatar should have a special border for donors", async ({ page }) => {
const container = page.locator(".avatar-container");
await expect(container).toHaveClass("avatar-container gold-border");
});
Testes de usabilidade
- Teste de usabilidade: Teste de usabilidade é quando você tem usuários reais interagindo com a aplicação para descobrir se há problemas de design, experiência do usuário ou funcionalidade no app. Teste de usabilidade foca na intuitividade da aplicação pelos usuários.
- Quatro tipos comuns de teste de usabilidade:
- Exploratório: Teste exploratório envolve usuários interagindo com diferentes funcionalidades da aplicação para entender melhor como elas funcionam.
- Comparativo: Teste comparativo é onde você compara a experiência do usuário da sua aplicação com aplicações similares no mercado.
- Avaliação: Teste de avaliação é onde você estuda quão intuitiva a aplicação é para usar.
- Validação: Teste de validação é onde você identifica quaisquer problemas graves que impediriam o usuário de usar a aplicação efetivamente.
- Ferramentas de teste de usabilidade: Exemplos de ferramentas para teste de usabilidade incluem Loop11, Maze, Userbrain, UserTesting e UXTweak.
Testes de compatibilidade
- Teste de compatibilidade: O objetivo do teste de compatibilidade é garantir que sua aplicação funcione em diferentes ambientes computacionais.
- Diferentes tipos de teste de compatibilidade:
- Compatibilidade retroativa: Compatibilidade retroativa refere-se a quando o software é compatível com versões anteriores.
- Compatibilidade futura: Compatibilidade futura refere-se a quando o software e sistemas funcionarão com versões futuras.
- Compatibilidade de hardware: Compatibilidade de hardware é a capacidade do software de funcionar adequadamente em diferentes configurações de hardware.
- Compatibilidade de sistemas operacionais: Compatibilidade de sistemas operacionais é a capacidade do software de funcionar em diferentes sistemas operacionais, como macOS, Windows e distribuições Linux como Ubuntu e Fedora.
- Compatibilidade de rede: Compatibilidade de rede significa que o software pode funcionar em diferentes condições de rede, como diferentes velocidades, protocolos, configurações de segurança, etc.
- Compatibilidade de navegador: Compatibilidade de navegador significa que a aplicação web pode funcionar consistentemente em diferentes navegadores, como Google Chrome, Safari, Firefox, etc.
- Compatibilidade móvel: É importante garantir que suas aplicações funcionem em uma variedade de dispositivos Android e iOS, incluindo telefones e tablets.
Testes de desempenho
- Teste de desempenho: No teste de desempenho, você testa a velocidade, responsividade, escalabilidade e estabilidade de uma aplicação sob diferentes cargas de trabalho. O objetivo é resolver qualquer tipo de gargalo de desempenho.
- Diferentes tipos de teste de desempenho:
- Teste de carga: Teste de carga determina como um sistema se comporta durante cargas normais e picos.
- Teste de estresse: Teste de estresse é onde você testa sua aplicação em cargas extremas e observa como o sistema responde à carga maior.
- Teste de resistência (soak testing): Teste de resistência é um tipo de teste de carga onde você testa o sistema com carga alta por um período prolongado.
- Teste de pico (spike testing): Teste de pico é onde você aumenta e diminui dramaticamente as cargas e analisa as reações do sistema às mudanças.
- Teste de ponto de interrupção (breakpoint testing): Teste de ponto de interrupção ou teste de capacidade é onde você incrementa lentamente a carga ao longo do tempo até o ponto em que o sistema começa a falhar ou degradar.
Testes de segurança
- Teste de segurança: Teste de segurança ajuda a identificar vulnerabilidades e fraquezas.
- Princípios de segurança:
- Confidencialidade: Protege contra o vazamento de informações sensíveis para destinatários não autorizados.
- Integridade: Impede que usuários maliciosos modifiquem informações do usuário.
- Autenticação: Verifica a identidade do usuário para garantir que ele tenha permissão para usar o sistema.
- Autorização: Processo de determinar quais ações usuários autenticados podem realizar ou quais partes do sistema podem acessar.
- Disponibilidade: Garante que informações e serviços estejam disponíveis para usuários autorizados quando necessário.
- Não repúdio: Garante que tanto o remetente quanto o destinatário tenham prova de entrega e verificação da identidade do remetente. Protege contra o remetente negar ter enviado a informação.
- Ameaças comuns de segurança:
- Cross-Site Scripting (XSS): Ataques XSS ocorrem quando um invasor injeta scripts maliciosos em uma página web e os executa no contexto do navegador da vítima.
- Injeção SQL: Injeção SQL permite que usuários maliciosos injetem código malicioso em um banco de dados.
- Ataque de negação de serviço (DoS): Ataque DoS ocorre quando usuários maliciosos inundam um site com um alto número de requisições ou tráfego, causando lentidão ou queda do servidor, tornando o site indisponível para usuários.
- Categorias de ferramentas de teste de segurança:
- Teste estático de segurança de aplicação: Essas ferramentas avaliam o código fonte de uma aplicação para identificar vulnerabilidades de segurança.
- Teste dinâmico de segurança de aplicação: Essas ferramentas interagem com o front-end da aplicação para descobrir possíveis fraquezas de segurança. Ferramentas DAST não têm acesso ao código fonte.
- Teste de penetração (pentest): Teste de penetração é um tipo de teste de segurança que envolve criar ataques cibernéticos simulados na aplicação para identificar vulnerabilidades no sistema.
Testes A/B
- Teste A/B: Teste A/B envolve comparar duas versões de uma página ou aplicação e estudar qual versão tem melhor desempenho. Também é conhecido como bucket ou split testing. Teste A/B permite tomar decisões mais orientadas a dados e melhorar continuamente a experiência do usuário.
- Ferramentas para teste A/B: Exemplos de ferramentas para teste A/B incluem GrowthBook e LaunchDarkly.
Testes alfa e beta
Após o desenvolvimento inicial e testes de software estarem completos, é importante que a aplicação seja testada por testadores e usuários reais. É aí que entram os testes alfa e beta.- Teste alfa: Teste alfa é feito por um grupo seleto de testadores que percorrem a aplicação para garantir que não haja bugs antes de ser lançada no mercado. Teste alfa faz parte do teste de aceitação e utiliza técnicas de caixa branca e caixa preta.
- Teste beta: Teste beta é quando a aplicação é disponibilizada para usuários reais. Usuários podem interagir com a aplicação e fornecer feedback. Teste beta também é uma forma de teste de aceitação do usuário.
- Teste de aceitação: Teste de aceitação garante que a aplicação de software atenda aos requisitos de negócio e às necessidades dos usuários antes do lançamento.
- Teste de caixa preta: Teste de caixa preta foca apenas no comportamento esperado da aplicação.
- Teste de caixa branca: Teste de caixa branca envolve o testador conhecer os componentes internos e realizar testes neles.
Testes de regressão
- Regressão: Regressão refere-se a situações onde mudanças novas quebram funcionalidades existentes sem querer.
- Teste de regressão: Teste de regressão ajuda a detectar problemas de regressão. No teste de regressão, você reexecuta testes funcionais em partes da sua aplicação para garantir que tudo ainda funcione como esperado.
- Ferramentas para teste de regressão: Ferramentas que você pode usar para realizar testes de regressão incluem Puppeteer, Playwright, Selenium e Cypress.
- Técnicas para teste de regressão:
- Teste unitário de regressão: É quando você tem uma lista de itens que precisam ser testados toda vez que mudanças ou correções importantes são implementadas no app.
- Teste parcial de regressão: Envolve abordagens direcionadas para garantir que mudanças novas não tenham quebrado aspectos específicos da aplicação.
- Teste completo de regressão: Executa testes em todas as funcionalidades na base de código. É a opção mais demorada e detalhada.
- Reteste: Reteste é usado para verificar problemas conhecidos e garantir que foram resolvidos. Em contraste, teste de regressão busca problemas desconhecidos que possam ter ocorrido por mudanças recentes no código.
O que é TypeScript
- JavaScript: JavaScript é uma linguagem de tipagem dinâmica. Isso significa que variáveis podem receber quaisquer valores em tempo de execução. O desafio de uma linguagem de tipagem dinâmica é que a falta de segurança de tipos pode introduzir erros.
const getRandomValue = (array) => {
return array[Math.floor(Math.random() * array.length)];
}
console.log(getRandomValue(10));
A saída console para o exemplo acima será undefined.
- TypeScript: TypeScript estende a linguagem JavaScript para incluir tipagem estática, o que ajuda a detectar erros causados por incompatibilidades de tipo antes de executar seu código.
array assim:
const getRandomValue = (array: string[]) => {
return array[Math.floor(Math.random() * array.length)];
}
Essa definição de tipo diz ao TypeScript que o parâmetro array deve ser um array de strings. Então, quando você chama getRandomValue e passa um número, você recebe um erro chamado erro de compilação.
- Compilador: Você precisa primeiro compilar o código TypeScript em JavaScript comum. Quando executa o compilador, o TypeScript avalia seu código e lança um erro para quaisquer problemas onde os tipos não correspondem.
Tipos de dados em TypeScript
- Tipos primitivos em TypeScript: Para os tipos primitivos
string,null,undefined,number,booleanebigint, TypeScript oferece palavras-chave de tipo correspondentes.
let str: string = "Naomi";
let num: number = 42;
let bool: boolean = true;
let nope: null = null;
let nada: undefined = undefined;
- Array: Você pode definir um array de tipo específico com duas sintaxes diferentes.
const arrOne: string[] = ["Naomi"];
const arrTwo: Array<string> = ["Camperchan"];
- Objetos: Você pode definir a estrutura exata de um objeto.
const obj: { a: string, b: number } = { a: "Naomi", b: 42 };
Se quiser um objeto com quaisquer chaves, mas onde todos os valores devem ser strings, há duas formas de defini-lo:
const objOne: Record<string, string> = {};
const objTwo: { [key: string]: string } = {};
- Outros tipos úteis em TypeScript:
any:anyindica que um valor pode ter qualquer tipo. Diz ao compilador para parar de se preocupar com o tipo dessa variável.unknown:unknowndiz ao TypeScript que você _se importa_ com o tipo do valor, mas não sabe exatamente qual é.unknowné geralmente preferido sobreany.void: Esse é um tipo especial que você normalmente usa apenas ao definir funções. Funções que não têm valor de retorno usam o tipo de retornovoid.never: Representa um tipo que nunca existirá.- Palavra-chave
type: Essa palavra-chave é comoconst, mas em vez de declarar uma variável, você pode declarar um tipo.
type stringOrNumber = string | number;
type bot = "camperchan" | "camperbot" | "naomi";
interface: Interfaces são como classes para tipos. Elas podem implementar ou estender outras interfaces, são especificamente tipos de objeto e geralmente são preferidas a menos que você precise de um recurso específico oferecido por uma declaraçãotype.
interface wowie {
zowie: boolean;
method: () => void;
}
- Definindo tipo de retorno: Você também pode definir o _tipo de retorno_ da função.
const getRandomValue = (array: string[]): string => {
return array[Math.floor(Math.random() * array.length)];
}
Genéricos
- Definindo tipo genérico: Você pode definir um tipo genérico e referenciá-lo na sua função. Pode ser pensado como um parâmetro especial que você fornece a uma função para controlar o comportamento da definição de tipo da função.
const getRandomValue = <T>(array: T[]): T => {
return array[Math.floor(Math.random() * array.length)];
}
const val = getRandomValue([1, 2, 4])
A sintaxe <T> diz ao TypeScript que você está definindo um tipo genérico T para a função. T é um nome comum para tipos genéricos, mas você pode usar qualquer nome.
Então, você diz ao TypeScript que o parâmetro array é um array de valores que correspondem ao tipo genérico, e que o valor retornado é um único elemento desse mesmo tipo.
- Especificando argumento de tipo na chamada da função: Você pode passar um argumento de tipo para uma chamada de função usando colchetes angulares entre o nome da função e seus parâmetros.
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email.value);
Isso diz ao TypeScript que o elemento que você espera encontrar será um elemento de entrada.
Narrowing de tipo
- Narrowing por truthiness: No exemplo abaixo, você recebe um erro de compilação ao tentar acessar a propriedade
valuedeemailporqueemail_pode_ sernull.
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email.value);
Você pode usar uma declaração condicional para confirmar que email é _truthy_ antes de acessar a propriedade:
const email = document.querySelector<HTMLInputElement>("#email");
if (email) {
console.log(email.value);
}
Checagens de truthiness também podem funcionar na direção oposta:
const email = document.querySelector<HTMLInputElement>("#email");
if (!email) {
throw new ReferenceError("Could not find email element!")
}
console.log(email.value);
Lançar um erro termina a execução lógica desse código, o que significa que quando você chega na chamada console.log(), o TypeScript sabe que email _não pode_ ser null.
- Encadeamento opcional: Encadeamento opcional
?.também é uma forma de narrowing de tipo, sob a mesma premissa de que o acesso à propriedade não pode acontecer se o valoremailfornull.
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email?.value);
- Operador
typeof: Você pode usar uma condicional para checar o tipo da variável usando o operadortypeof.
const myVal = Math.random() > 0.5 ? 222 : "222";
if (typeof myVal === "number") {
console.log(myVal / 10);
}
- Palavra-chave
instanceof: Se o objeto vem de uma classe, você pode usar a palavra-chaveinstanceofpara narrow o tipo.
const email = document.querySelector("#email");
if (email instanceof HTMLInputElement) {
console.log(email.value);
}
- Casting: Quando o TypeScript não consegue determinar automaticamente o tipo de um valor, como o resultado do método
request.json()no exemplo abaixo, você receberá um erro de compilação. Uma forma de resolver isso é fazendo casting do tipo, mas isso enfraquece a capacidade do TypeScript de detectar erros potenciais.
interface User {
name: string;
age: number;
}
const printAge = (user: User) =>
console.log(${user.name} is ${user.age} years old!)
const request = await fetch("url")
const myUser = await request.json() as User;
printAge(myUser);
- Type guard: Em vez de fazer casting do tipo, você pode escrever um type guard:
interface User {
name: string;
age: number;
}
const isValidUser = (user: unknown): user is User => {
return !!user &&
typeof user === "object" &&
"name" in user &&
"age" in user;
}
A sintaxe user is User indica que sua função retorna um valor booleano, que quando verdadeiro significa que o valor user satisfaz a interface User.
Arquivo tsconfig
tsconfig.json: As configurações do compilador TypeScript ficam em um arquivotsconfig.jsonno diretório raiz do seu projeto.
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./prod",
"lib": ["ES2023"],
"target": "ES2023",
"module": "ES2022",
"moduleResolution": "Node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true
},
"exclude": ["test/"]
}
Aqui estão descrições das opções do compilador usadas no exemplo acima:
compilerOptions: A propriedadecompilerOptionsé onde você controla como o compilador TypeScript se comporta.rootDireoutDir:rootDireoutDirdizem ao TypeScript qual diretório contém seus arquivos fonte e qual diretório deve conter o código JavaScript transpilado.lib: A propriedadelibdetermina quais definições de tipo o compilador usa, e permite incluir suporte para versões específicas do ES, DOM e mais.moduleemoduleResolution:moduleemoduleResolutionfuncionam em conjunto para gerenciar como seu pacote usa módulos — seja CommonJS ou ECMAScript.esModuleInterop:esModuleInteroppermite uma interoperabilidade mais suave entre módulos CommonJS e ES criando automaticamente objetos namespace para imports.skipLibCheck: A opçãoskipLibCheckpula a validação de arquivos.d.tsque não são referenciados por imports no seu código.strict: A flagstricthabilita várias checagens, como garantir o tratamento adequado de tipos anuláveis e avisar quando o TypeScript recorre aany.exclude: A propriedade de nível superiorexcludediz ao compilador para ignorar esses arquivos TypeScript durante a compilação.