Revisão de bibliotecas front-end

--- id: 6724e2dbf723fe1c8883cc69 title: Revisão de bibliotecas front-end challengeType: 31 dashedName: review-front-end-libraries --- # --description--

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.
Aqui está um exemplo de um componente React simples que renderiza uma mensagem de saudação:
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.
Exportações padrão
  • 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 import sem 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:5173 para ver sua aplicação React rodando.
  • Para realmente ver o código do template inicial, você pode entrar no seu projeto dentro da pasta src e deverá ver o arquivo App.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 color e fontSize são definidos condicionalmente com base na prop isImportant.

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, keydown e submit. Manipuladores de eventos em React usam a convenção camelcase para nomeação. (Ex. onClick, onSubmit, etc)
Aqui está um exemplo de uso do atributo 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 hook useState é 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:
  • stateVariable mantém o valor atual do estado
  • setStateFunction (a função setter) atualiza a variável de estado
  • initialValue define o estado inicial
Aqui está um exemplo completo para um componente 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.
A etapa trigger ocorre quando o React detecta que algo mudou e a interface do usuário pode precisar ser atualizada. Essa mudança geralmente é devido a uma atualização no estado ou nas props. Uma vez que o trigger acontece, o React entra na etapa render. Aqui, o React reavalia seus componentes e decide o que mostrar. Para isso, o React usa uma cópia leve do DOM "real" chamada DOM virtual. Com o DOM virtual, o React pode rapidamente verificar o que precisa mudar no componente. A etapa commit é onde o React pega as mudanças preparadas no DOM virtual e as aplica no DOM real. Em outras palavras, essa é a etapa onde você vê o resultado final na tela.

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() ou pop(). 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 atributo ref. Aqui está um exemplo que mostra um ref para focar um elemento input. A propriedade current é usada para acessar o valor atual desse ref:
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 hook useEffect para permitir que você lide com esses efeitos colaterais. useEffect permite 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 dependencies for um array que inclui um ou mais valores reativos, o efeito será executado sempre que eles mudarem.
  • Se dependencies for um array vazio, useEffect executa 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.
Aqui está um exemplo de criação de um hook 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 um ref.
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 hook useActionState:
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.
  • actionFunction parâ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.
Aqui está a sintaxe básica:
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.
Aqui está um exemplo de uso do hook 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. O expensiveSortFunction só será executado quando largeArray mudar:
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 react e react-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 arquivo package.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.
Aqui está um exemplo de criação de um manipulador de requisição personalizado:
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 Image estende o elemento HTML nativo img e 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 componente Image servirá 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 useStore para 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.
Algumas melhores práticas para ordem do código fonte incluem:
  • 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.
Aqui está um exemplo de boa ordem do código fonte, usando as melhores práticas que acabamos de ver:
<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.
Algumas formas de reduzir a latência incluem:
  • 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.
Aqui estão algumas formas de melhorar o INP:
  • 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.
Aqui está um exemplo de uso do Tailwind CSS para estilizar um botão.
<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.
Aqui está um exemplo de uso do Bootstrap para criar um grupo de lista. Em vez de aplicar pequenas classes aos seus elementos HTML, você adicionará o componente inteiro, incluindo a estrutura HTML.
<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 como sm:, 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 como flex, 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, como grid, 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 como mt-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 como uppercase, 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, como text-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-300 adiciona bordas com espessura e cores especificadas.
  • Anéis: ring-1 ring-gray-300 cria efeitos semelhantes a contornos, frequentemente usados para foco ou cartões.
  • Cantoneiras arredondadas e escalonamento: Classes como rounded-md, rounded-xl e scale-105 adicionam acabamento.
<div class="p-6 rounded-xl ring-2 ring-fuchsia-500 scale-105">
  Highlighted card
</div>

Gradientes

Tailwind suporta utilitários de gradiente como bg-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.
Aqui está um exemplo de definição e uso de variável em 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".
Aqui está um exemplo de definição e uso de variável na sintaxe indentada.
$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.
Aqui está um exemplo de definição de um mixin em sintaxe SCSS. Nesse caso, o mixin se chama 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.
Aqui está um exemplo de testes unitários usando Jest. Primeiro, você pode criar uma função responsável por retornar uma string formatada:
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ção expect é 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.
Para usar Jest, você primeiro precisa instalar o pacote 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.js e expect.js.
Aqui está um exemplo de uma assertiva usando Chai que verifica se o valor de retorno da função 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.
Aqui está um exemplo de testes E2E da base de código G.E.A.R ACADEMY usando Playwright. O hook 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.
Por exemplo, mesmo que sua função JavaScript espere um array, você ainda pode chamá-la com um número:
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.
Por exemplo, você pode definir um tipo para o parâmetro 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, boolean e bigint, 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: any indica que um valor pode ter qualquer tipo. Diz ao compilador para parar de se preocupar com o tipo dessa variável.
  • unknown: unknown diz ao TypeScript que você _se importa_ com o tipo do valor, mas não sabe exatamente qual é. unknown é geralmente preferido sobre any.
  • 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 retorno void.
  • never: Representa um tipo que nunca existirá.
  • Palavra-chave type: Essa palavra-chave é como const, mas em vez de declarar uma variável, você pode declarar um tipo.
É útil para declarar tipos personalizados como tipos união ou tipos que incluem apenas valores específicos:
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ção type.
interface wowie {
  zowie: boolean;
  method: () => void;
}
  • Definindo tipo de retorno: Você também pode definir o _tipo de retorno_ da função.
O exemplo abaixo define o valor de retorno como string. Se tentar retornar qualquer outra coisa, o TypeScript dará um erro de compilaçã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.
Aqui está um exemplo de definição de tipo genérico para uma 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.
Aqui está um exemplo de passar um argumento de tipo para uma chamada de função:
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 value de email porque email _pode_ ser null.
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 valor email for null.
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 operador typeof.
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-chave instanceof para 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 arquivo tsconfig.json no 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 propriedade compilerOptions é onde você controla como o compilador TypeScript se comporta.
  • rootDir e outDir: rootDir e outDir dizem ao TypeScript qual diretório contém seus arquivos fonte e qual diretório deve conter o código JavaScript transpilado.
  • lib: A propriedade lib determina quais definições de tipo o compilador usa, e permite incluir suporte para versões específicas do ES, DOM e mais.
  • module e moduleResolution: module e moduleResolution funcionam em conjunto para gerenciar como seu pacote usa módulos — seja CommonJS ou ECMAScript.
  • esModuleInterop: esModuleInterop permite uma interoperabilidade mais suave entre módulos CommonJS e ES criando automaticamente objetos namespace para imports.
  • skipLibCheck: A opção skipLibCheck pula a validação de arquivos .d.ts que não são referenciados por imports no seu código.
  • strict: A flag strict habilita várias checagens, como garantir o tratamento adequado de tipos anuláveis e avisar quando o TypeScript recorre a any.
  • exclude: A propriedade de nível superior exclude diz ao compilador para ignorar esses arquivos TypeScript durante a compilação.
# --assignment-- Revise os tópicos e conceitos sobre bibliotecas front-end.