De Angular para React

Um guia prático com código lado a lado, explicações objetivas e perguntas de entrevista. Pra quem já domina Angular (até v18) e quer se sentir seguro em entrevistas React.

Angular ≤ 18React 18 / 19TypeScript

Mudança de mentalidade

Angular é um framework opinativo: router, forms, HTTP, DI, testing, CLI vêm na caixa. React é uma biblioteca de UI. O time escolhe as peças.

A diferença central: No Angular, Zone.js detecta mudanças e atualiza o DOM. No React, o componente roda de novo do zero a cada mudança de estado, e o React reconcilia a diferença com o DOM real via Virtual DOM. Se internalizar isso, 80% das pegadinhas somem.
Angular moderno (17+): a comunidade está migrando pra API funcional baseada em signals: input(), output(), model(), viewChild(), contentChild(). Os exemplos deste guia usam a API clássica (decorators) por ser mais difundida. Em entrevista pra vaga já em Angular 17+, demonstrar consciência das duas APIs é diferencial.

A stack mais comum em 2025/2026:

  • Vite (SPA) ou Next.js (SSR/RSC) no lugar do Angular CLI
  • TanStack Query no lugar de HttpClient + RxJS pra server state
  • Zustand ou Redux Toolkit no lugar de NgRx
  • React Hook Form + Zod no lugar de Reactive Forms
  • Tailwind CSS pra estilo utilitário (sem ViewEncapsulation)
  • Vitest + React Testing Library no lugar de Karma/Jasmine

Mapa rápido de conceitos

Referência rápida: o equivalente React pra cada conceito Angular que você já conhece.

AngularReactNota
@Componentfunction Component()Função que retorna JSX
@Input()propsParâmetros da função
@Output() + EventEmittercallback propPai passa função
ng-contentchildren propProjeção de conteúdo
NgModuleNão existe; import direto
@ViewChilduseRefReferência ao DOM
ngOnInituseEffect(…, [])Roda 1x ao montar
ngOnDestroyuseEffect cleanupreturn () => {...}
ngOnChangesuseEffect(…, [dep])Ou derivar no render
signal()useState (+ Compiler)Reativo no Angular; React faz pull no re-render
computed()useMemoValor derivado memoizado
Pipesfunções purasChamadas no JSX
Services + DICustom hooks + ContextSem DI formal
RxJS ObservablesuseState / TanStack QueryRxJS opcional (observable-hooks)
NgRx StoreRedux Toolkit / ZustandRTK ≈ NgRx mais enxuto
Reactive FormsReact Hook Form + ZodUncontrolled por default
Angular RouterReact Router / Next.jsLoaders = Resolvers
ChangeDetection.OnPushReact.memo()Shallow compare de props
trackBykey propIdentidade na lista
Angular CLIVite / Next.jsVite só faz scaffold; sem ng g xxx

Componentes & JSX

No Angular, componente = classe com decorator e template separado. No React, componente = função que retorna JSX (JavaScript XML). Marcação e lógica vivem juntas no mesmo arquivo.

Angular
@Component({
  selector: 'app-greeting',
  standalone: true,
  template: `
    <h1 class="title">
      Olá, {{ name }}
    </h1>
  `,
})
export class GreetingComponent {
  @Input() name!: string;
}
React
type GreetingProps = {
  name: string;
};

export function Greeting({ name }: GreetingProps) {
  return (
    <h1 className="title">
      Olá, {name}
    </h1>
  );
}
Diferenças-chave: class vira className em JSX. Interpolação é {expr}, não {{ expr }}. Nomes de componentes começam com maiúscula. Sem selector; o nome do import é o "seletor".

Props, children & eventos

No Angular: @Input() pra dados de entrada, @Output() + EventEmitter pra eventos, e ng-content pra projeção. No React: tudo é props, inclusive callbacks e children.

Angular
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
      <header>
        <h2>{{ title }}</h2>
        <button *ngIf="closable"
          (click)="closed.emit()">
          X
        </button>
      </header>
      <ng-content />
    </div>
  `,
})
export class CardComponent {
  @Input() title!: string;
  @Input() closable = false;
  @Output() closed = new EventEmitter();
}

// uso:
<app-card
  title="Fatura"
  [closable]="true"
  (closed)="onClose()">
  <p>Total: R$ 200</p>
</app-card>
React
type CardProps = {
  title: string;
  onClose?: () => void;
  children: React.ReactNode;
};

export function Card({
  title, onClose, children
}: CardProps) {
  return (
    <div className="card">
      <header>
        <h2>{title}</h2>
        {onClose && (
          <button onClick={onClose}>
            X
          </button>
        )}
      </header>
      {children}
    </div>
  );
}

// uso:
<Card
  title="Fatura"
  onClose={() => console.log('fechou')}>
  <p>Total: R$ 200</p>
</Card>
Não existe EventEmitter no React. Eventos são funções callback passadas como props. O pai decide o que fazer. Projeção de conteúdo (ng-content) vira a prop children.

Estado com useState

No Angular (clássico) você muta propriedades da classe e Zone.js detecta. Com signals (v16+), usa set() / update(). No React: useState retorna [valor, setter]. Estado é imutável.

Angular (Signals)
import { signal } from '@angular/core';

@Component({
  template: `
    <p>Valor: {{ count() }}</p>
    <button (click)="inc()">+1</button>
  `,
})
export class CounterComponent {
  count = signal(0);

  inc() {
    // update funcional
    this.count.update(c => c + 1);
  }
}

// Pra objetos/arrays: .update(o => { ... })
// .mutate() existiu mas foi removido em 17.2+
React
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  // <>...</> = Fragment: agrupa múltiplos
  // elementos sem criar wrapper no DOM.
  return (
    <>
      <p>Valor: {count}</p>
      <button onClick={() =>
        setCount(c => c + 1)
      }>+1</button>
    </>
  );
}

// Estado é IMUTÁVEL. Crie novo:
// setUser(prev => ({...prev, age: 31}))
Armadilha: No React, mutar o objeto e chamar o setter não funciona: user.age = 31; setUser(user). React compara por referência (Object.is), vê o mesmo objeto e ignora. Sempre crie um novo: setUser(prev => ({...prev, age: 31})).

Efeitos com useEffect

No Angular, o lifecycle é distribuído: ngOnInit, ngOnChanges, ngOnDestroy. No React, useEffect faz o papel de todos, variando pelo array de dependências.

Angular
export class SearchComponent
  implements OnInit, OnChanges, OnDestroy {

  @Input() query!: string;
  results: Item[] = [];
  private sub!: Subscription;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    console.log('montou');
  }

  ngOnChanges(ch: SimpleChanges) {
    if (ch['query']) {
      this.sub?.unsubscribe();
      this.sub = this.http
        .get<Item[]>(`/api?q=${this.query}`)
        .subscribe(r => this.results = r);
    }
  }

  ngOnDestroy() {
    this.sub?.unsubscribe();
  }
}
React
import { useState, useEffect } from 'react';

export function Search({
  query
}: { query: string }) {
  const [results, setResults] =
    useState<Item[]>([]);

  // ngOnInit + ngOnChanges + ngOnDestroy
  // tudo num bloco só:
  useEffect(() => {
    let cancelled = false;

    fetch(`/api?q=${query}`)
      .then(r => r.json())
      .then(data => {
        if (!cancelled) setResults(data);
      });

    // cleanup (= ngOnDestroy)
    return () => { cancelled = true; };
  }, [query]); // roda quando query muda

  // ...
}
Regra mental: [] vazio = mount + unmount · [dep] = mount + sempre que dep mudar · sem array = todo render (raramente útil). Pegadinha clássica: em StrictMode dev, useEffect roda duas vezes ao montar (mount → cleanup → mount) pra forçar você a escrever cleanups corretos. Em produção, roda 1x. E pra data fetching, prefira TanStack Query em vez de useEffect.

Refs, useMemo & useCallback

Angular
// @ViewChild: referência ao DOM
@ViewChild('inputEl')
inputRef!: ElementRef<HTMLInputElement>;

ngAfterViewInit() {
  this.inputRef.nativeElement.focus();
}

// computed(): valor derivado (signals)
count = signal(0);
double = computed(() =>
  this.count() * 2
);

// ChangeDetection.OnPush
// evita re-render se inputs não mudaram
@Component({
  changeDetection:
    ChangeDetectionStrategy.OnPush,
  ...
})
React
// useRef: referência ao DOM
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
  inputRef.current?.focus();
}, []);

return <input ref={inputRef} />;

// useMemo: valor derivado memoizado
const [count, setCount] = useState(0);
const double = useMemo(
  () => count * 2, [count]
);

// React.memo: equivalente a OnPush
const ExpensiveChild = React.memo(
  ({ data }: { data: Item[] }) => {
    return <List items={data} />;
  }
);
// só re-renderiza se data (shallow) mudar
OnPush vs React.memo: ambos evitam trabalho, mas por mecanismos diferentes. OnPush ainda re-checa quando evento dispara dentro do componente, async pipe emite ou signal lido no template muda. React.memo só faz shallow compare das props. Já useCallback(fn, deps) memoiza a função em si. Útil quando ela é passada pra componente memoizado, porque cada render cria uma função nova e invalidaria o memo do filho.

Controle de fluxo no template

Angular usa diretivas/blocos (*ngIf, @if, *ngFor, @for). React usa JavaScript puro no JSX.

Angular (v17+ syntax)
<!-- Condicional -->
@if (isLogged) {
  <p>Bem-vindo, {{ user.name }}</p>
} @else {
  <p>Faça login</p>
}

<!-- Lista -->
@for (item of items; track item.id) {
  <app-row [data]="item" />
}

<!-- Pipe -->
<p>{{ birthday | date:'dd/MM/yyyy' }}</p>

<!-- Class condicional -->
<div [ngClass]="{
  'active': isActive,
  'error': hasError
}"></div>
React
{/* Condicional */}
{isLogged
  ? <p>Bem-vindo, {user.name}</p>
  : <p>Faça login</p>
}

{/* Lista. Key é obrigatório */}
{items.map(item => (
  <Row key={item.id} data={item} />
))}

{/* Função pura em vez de pipe */}
<p>{formatDate(birthday)}</p>

{/* Class condicional (lib clsx) */}
<div className={clsx({
  'active': isActive,
  'error': hasError,
})}></div>
No React não tem sintaxe de template especial. É tudo JavaScript. Ternários e .map() são os idiomas. A key no map faz o mesmo papel do track no @for.

DI & Context

Angular tem Dependency Injection sofisticada com providers hierárquicos. React usa Context API pra compartilhar dados sem prop drilling. É mais leve, com tradeoffs.

Angular — Service + DI
@Injectable({ providedIn: 'root' })
export class AuthService {
  private user$ = new BehaviorSubject<
    User | null
  >(null);

  login(u: User) {
    this.user$.next(u);
  }
  logout() {
    this.user$.next(null);
  }
  getUser() {
    return this.user$.asObservable();
  }
}

// Componente injeta:
constructor(private auth: AuthService) {}

ngOnInit() {
  this.auth.getUser().subscribe(
    u => this.user = u
  );
}
React — Context + Hook
type AuthCtx = {
  user: User | null;
  login: (u: User) => void;
  logout: () => void;
};

const AuthContext =
  createContext<AuthCtx | null>(null);

export function AuthProvider({
  children
}: { children: React.ReactNode }) {
  const [user, setUser] =
    useState<User | null>(null);

  const value = useMemo(() => ({
    user,
    login: (u: User) => setUser(u),
    logout: () => setUser(null),
  }), [user]);

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hook pra consumir:
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('fora do Provider');
  return ctx;
}

// Uso no componente:
const { user, logout } = useAuth();
Cuidado: Context re-renderiza todos os consumidores quando o value muda. Pra estado global com updates frequentes, prefira Zustand ou Redux Toolkit.

Custom Hooks

Qualquer função que começa com use e chama outros hooks é um custom hook. Funciona como Angular services, com estado reativo embutido. No Angular, um service compartilha lógica via DI; no React, um custom hook encapsula a mesma lógica.

Angular — Service injetável
@Injectable({ providedIn: 'root' })
export class WindowSizeService {
  width$ = new BehaviorSubject(
    window.innerWidth
  );

  constructor() {
    fromEvent(window, 'resize').pipe(
      debounceTime(200),
      map(() => window.innerWidth)
    ).subscribe(this.width$);
  }
}

// uso:
constructor(ws: WindowSizeService) {
  ws.width$.subscribe(w => ...);
}
React — Custom Hook
function useWindowWidth(delay = 200) {
  // initializer LAZY (não roda em SSR)
  const [width, setWidth] = useState(() =>
    typeof window !== 'undefined'
      ? window.innerWidth : 0
  );

  useEffect(() => {
    let timer: ReturnType<typeof setTimeout>;

    const handle = () => {
      clearTimeout(timer);
      timer = setTimeout(
        () => setWidth(window.innerWidth),
        delay
      );
    };

    window.addEventListener('resize', handle);
    return () => {
      clearTimeout(timer);
      window.removeEventListener('resize', handle);
    };
  }, [delay]);

  return width;
}

// uso direto no componente:
const width = useWindowWidth();
Cada componente que chama useWindowWidth() tem sua própria instância de estado. Pra compartilhar uma instância global (singleton), use Context ou Zustand. No Angular, services providedIn: 'root' já são singletons por natureza.

Forms

Angular Reactive Forms: FormGroup, FormControl, validators. React: React Hook Form + Zod é a combinação dominante hoje.

Angular — Reactive Forms
// no componente:
form = new FormGroup({
  email: new FormControl('', [
    Validators.required,
    Validators.email,
  ]),
  password: new FormControl('', [
    Validators.required,
    Validators.minLength(8),
  ]),
});

onSubmit() {
  if (this.form.valid) {
    this.auth.login(this.form.value);
  }
}

<!-- template -->
<form [formGroup]="form"
  (ngSubmit)="onSubmit()">
  <input formControlName="email" />
  <span *ngIf="form.get('email')?.errors
    ?.['email']">
    Email inválido
  </span>
  <input type="password"
    formControlName="password" />
  <button [disabled]="form.invalid">
    Entrar
  </button>
</form>
React — Hook Form + Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from
  '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Email inválido'),
  password: z.string()
    .min(8, 'Mínimo 8 chars'),
});

type Form = z.infer<typeof schema>;

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<Form>({
    resolver: zodResolver(schema)
  });

  return (
    <form onSubmit={handleSubmit(
      data => auth.login(data)
    )}>
      <input {...register('email')} />
      {errors.email &&
        <span>{errors.email.message}</span>}
      <input type="password"
        {...register('password')} />
      <button disabled={isSubmitting}>
        Entrar
      </button>
    </form>
  );
}
RHF usa uncontrolled inputs por baixo (sem re-render a cada tecla). Zod gera a validação e o tipo TypeScript de uma vez: z.infer<typeof schema> elimina duplicação tipo ↔ validator.

Routing

Angular Router
// app.routes.ts
export const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'users/:id',
    component: UserDetailComponent,
    resolve: {
      user: userResolver
    },
  },
  {
    path: 'admin',
    canActivate: [authGuard],
    loadChildren: () =>
      import('./admin/admin.routes')
  },
];

<!-- template -->
<a routerLink="/users/42">User</a>
<router-outlet />

// no componente:
const id = this.route
  .snapshot.params['id'];
React Router v7
// router.tsx
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <Home /> },
      {
        path: 'users/:id',
        element: <UserDetail />,
        loader: async ({ params }) =>
          fetch(`/api/users/${params.id}`),
      },
      {
        path: 'admin',
        lazy: () =>
          import('./admin/Admin')
            .then(m => ({ Component: m.default })),
      },
    ],
  },
]);

// componentes usam hooks:
const { id } = useParams();
<Link to="/users/42">User</Link>
<Outlet /> // = router-outlet
loader = Resolver · action = processador de Form submit · lazy = lazy load · Guards no React são feitos com componentes wrapper ou redirect no loader.

Data Fetching — TanStack Query

No Angular: HttpClient + RxJS + AsyncPipe. No React de hoje: TanStack Query (cache automático, refetch, retry, devtools).

Angular — HttpClient + RxJS
// service
@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  getUser(id: string) {
    return this.http
      .get<User>(`/api/users/${id}`);
  }
}

// componente
user$ = this.userService.getUser(id);

<!-- template -->
<div *ngIf="user$ | async as user">
  {{ user.name }}
</div>

// Nada out-of-the-box: cache via interceptor,
// retry com retry() do RxJS, refetch manual.
// Ou: NgRx Data, TanStack Query Angular.
React — TanStack Query
// custom hook
function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: () =>
      fetch(`/api/users/${id}`)
        .then(r => r.json()),
    staleTime: 60_000, // 1min
  });
}

// componente
const { data, isLoading, error } =
  useUser(id);

if (isLoading) return <Spinner />;
if (error) return <Error />;
return <div>{data.name}</div>;

// Cache, refetch on focus, retry,
// devtools. Tudo incluído.
TanStack Query é como se HttpClient + AsyncPipe + NgRx Effects viessem numa lib só, com cache inteligente. É a lib mais usada no ecossistema React pra server state.

State Management Global

Você já conhece NgRx. No React, as equivalências diretas são Redux Toolkit (mesmo paradigma) e Zustand (alternativa minimalista que dominou o mercado).

NgRx (Angular)
// actions
export const inc = createAction('[Counter] Inc');
export const set = createAction(
  '[Counter] Set',
  props<{ value: number }>()
);

// reducer
export const counterReducer = createReducer(
  { value: 0 },
  on(inc, s => ({ value: s.value + 1 })),
  on(set, (_, { value }) => ({ value })),
);

// componente
count$ = this.store.select('counter');
inc() { this.store.dispatch(inc()); }
Redux Toolkit (React)
// createSlice = actions + reducer juntos
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    inc: (state) => {
      state.value += 1; // Immer: "mutação"
    },
    set: (state, action) => {
      state.value = action.payload;
    },
  },
});

export const { inc, set } =
  counterSlice.actions;

// componente
const count = useSelector(
  (s: RootState) => s.counter.value
);
const dispatch = useDispatch();
dispatch(inc());
RTK usa Immer por baixo. Você escreve state.value += 1 e internamente vira atualização imutável. Mesmo conceito do NgRx, com ~50% menos boilerplate.

Zustand — a alternativa minimalista

Zustand — store em ~10 linhas
import { create } from 'zustand';

type AuthStore = {
  user: User | null;
  login: (u: User) => void;
  logout: () => void;
};

export const useAuth = create<AuthStore>((set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// uso (subscrição granular por seletor):
const user = useAuth(s => s.user);
const logout = useAuth(s => s.logout);
Tendência 2025/26: separar server state (TanStack Query) de client state (Zustand/RTK). Misturar tudo num Redux monolítico é considerado padrão antigo. Saber explicar isso em entrevista é diferencial.

Next.js & Server Components

Next.js é o framework full-stack dominante no ecossistema React. O App Router introduziu Server Components (RSC), conceito que não existe no Angular e cai com frequência em entrevistas.

Angular — SSR com @angular/ssr
// server-side rendering via Angular Universal
// O componente é o mesmo: roda no
// server E no client (hidratação)

@Component({
  template: `
    <h1>{{ user.name }}</h1>
    <app-edit-button
      [userId]="user.id" />
  `,
})
export class UserPage implements OnInit {
  user!: User;

  constructor(
    private http: HttpClient,
    private route: ActivatedRoute
  ) {}

  ngOnInit() {
    const id = this.route.snapshot.params['id'];
    this.http.get<User>(`/api/${id}`)
      .subscribe(u => this.user = u);
  }
}

// Todo JS do componente vai pro bundle
// do client.
Next.js — Server Component
// app/users/[id]/page.tsx
// Server Component (DEFAULT no App Router)
// Roda SÓ no servidor. Zero JS DO COMPONENTE
// no bundle (vai um payload serializado).

type Props = {
  params: { id: string }
};

export default async function UserPage({
  params
}: Props) {
  // await direto no corpo. Sem hook.
  const user = await fetch(
    `${process.env.API}/users/${params.id}`,
    { next: { revalidate: 60 } }
  ).then(r => r.json());

  return (
    <>
      <h1>{user.name}</h1>
      <EditButton userId={user.id} />
    </>
  );
}

// EditButton.tsx (precisa de interatividade)
'use client';
export function EditButton({ userId }: ...) {
  const [open, setOpen] = useState(false);
  // ...
}
Server Components são o grande diferencial: zero JS no client, dados buscados perto do servidor, bundle menor. Use 'use client' só quando precisar de interatividade. Server Actions ('use server') permitem chamadas RPC diretas do form pro servidor, sem API REST.

Armadilhas clássicas

As pegadinhas que mais pegam quem vem de Angular. Entender cada uma é diferencial em entrevista.

Mutação de estado

user.age = 31; setUser(user). React não vê mudança (mesma referência). Sempre crie um novo objeto: setUser(prev => ({...prev, age: 31}))

Stale closure

Função capturada em useEffect com [] vazio referencia valor congelado do mount. Use forma funcional do setter ou inclua dependências.

useEffect infinito

useEffect(() => setData({...data}), [data]). Muda data, que dispara o efeito, que muda data... loop infinito.

key em listas

Esquecer key no .map() ou usar índice como key em listas que reordenam. Equivalente ao trackBy do Angular. Use ID estável.

Context re-renderiza tudo

Se value={{x, y}} é objeto literal, todo consumidor re-renderiza a cada render do Provider. Memoize o value ou use Zustand.

Two-way binding não existe

React é unidirecional: value={x} onChange={e => setX(e.target.value)}. Não tem ngModel. Explicar isso em entrevista é clássico.

useEffect pra derivar estado

useEffect(() => setUpper(name.toUpperCase()), [name]). Errado. Derive direto: const upper = name.toUpperCase(). Re-render é barato.

Inline function quebrando memo

<Child onClick={() => doThing()} />. Função nova a cada render invalida React.memo do Child. Use useCallback quando relevante.

Perguntas de entrevista

Fundamentos

O que é JSX?
Açúcar sintático que descreve árvore de elementos. Desde React 17, o new JSX transform compila JSX para chamadas a _jsx() de react/jsx-runtime (antes era React.createElement; por isso hoje você não precisa mais import React from 'react'). Não é HTML: usa camelCase (className, onClick, htmlFor), interpola com {}, é expressão JavaScript.
O que é Virtual DOM e reconciliação?
Virtual DOM é representação em memória da UI. A cada render, React constrói nova árvore, compara com anterior (diff), e aplica mudanças mínimas no DOM real. key ajuda a identificar elementos em listas.
Componente controlado vs não-controlado?
Controlado: valor do input vive em state React (value={x}). Não-controlado: DOM mantém o valor, acessa por ref. React Hook Form usa não-controlado por performance.

Hooks

Quais são as regras dos hooks?
(1) Chamar só no nível superior. Nunca dentro de if, loop ou função aninhada. (2) Chamar só de componentes React ou custom hooks. Motivo: React identifica hooks pela ordem de chamada.
useMemo vs useCallback vs React.memo — qual a diferença?
useMemo(fn, deps) memoiza valor. useCallback(fn, deps) memoiza funçãouseMemo(() => fn, deps)). React.memo(Component) memoiza o componente: só re-renderiza se props (shallow compare) mudaram.
O que é stale closure e como evitar?
Função capturada num render antigo referenciando variáveis daquele render. Comum em useEffect com deps vazias. Soluções: incluir deps corretas, usar forma funcional do setter (setCount(c => c+1)), ou guardar valor em useRef.
Quando usar useReducer em vez de useState?
Quando o próximo estado depende do anterior de forma complexa; múltiplas ações mudam partes diferentes; lógica de transição precisa ser testável isolada; lógica vira máquina de estados.

Estado & dados

Context API substitui Redux?
Não. Context resolve prop drilling mas re-renderiza todos consumidores quando value muda. Pra estado global com updates frequentes ou lógica complexa: Zustand/RTK. Pra server state: TanStack Query.
Diferença entre client state e server state?
Client state: efêmero, da sessão (UI, modais, filtros). Server state: réplica de dados do backend, com cache, invalidação, refetch e retry. Ferramentas diferentes servem melhor a cada um. Separar virou consenso de 2024 pra cá.

Server Components & Next.js

O que é um React Server Component?
Componente que roda só no servidor, renderiza pra descrição serializada e não vai pro bundle do cliente. Pode fazer await no corpo. Não pode usar hooks de state/efeito. Alivia bundle e aproxima UI do dado.
Quando usar Client Component?
Quando precisa de interatividade: state, efeitos, event handlers, APIs do browser. Indicado com 'use client' no topo. Server Component é default no App Router.
SSR, SSG, ISR, CSR — diferenças?
CSR: SPA pura, browser renderiza. SSR: HTML montado a cada request no servidor. SSG: HTML montado no build. ISR: SSG com revalidação periódica. No Next App Router, configurado por opção de fetch/rota.

Performance

Quando memoizar (e quando não)?
Memoize quando: cálculo é caro, identidade referencial importa pra consumidor memoizado, ou você mediu um problema. Não memoize por default; tem custo. React 19 Compiler automatiza muito disso.
O que são Concurrent Features?
React 18+ pode interromper/pausar/reordenar renders. Habilita useTransition (updates não urgentes), useDeferredValue, Suspense pra data fetching, automatic batching.

Roteiro de estudo

Plano pra ~2 semanas leves, focado em te deixar confiante pra entrevista.

Dia 1–2. Tutorial oficial em react.dev: Quick Start até Escape Hatches. Construa um Todo list do zero.

Dia 3. Hooks essenciais na prática: implemente useDebounce, useLocalStorage, usePrevious.

Dia 4. TanStack Query + React Router: mini-app consumindo JSONPlaceholder com rota dinâmica.

Dia 5. Reescreva um form real seu do Angular em React Hook Form + Zod.

Dia 6. Pegue um trecho de NgRx e faça equivalente em Zustand e Redux Toolkit. Compare.

Dia 7. Next.js App Router: tutorial oficial, uma página Server Component buscando dados.

Dia 8–9. React DevTools Profiler. Entenda re-renders. Suspense e useTransition.

Dia 10. Releia as perguntas de entrevista. Escreva respostas em voz alta. Mock interview se possível.

Kickoff rápido

Terminal
# SPA com Vite
npm create vite@latest meu-app -- --template react-ts

# Full-stack com Next.js
npx create-next-app@latest meu-app --typescript --app

# Libs recomendadas
npm i @tanstack/react-query react-router-dom react-hook-form zod @hookform/resolvers zustand
npm i -D @testing-library/react vitest