A React app is built by composing components. This chapter covers components and JSX, props and state, the hooks you will use most, rendering lists with keys, and how to compose components.
Components and JSX
A component is a JavaScript function that returns JSX. JSX looks like HTML but compiles to JavaScript, so it follows a few rules:
- Return a single root element. When you do not want an extra wrapper, use a Fragment,
<>...</>. - Attributes use camelCase:
className instead of class, onClick instead of onclick. - Every tag must be closed, including
<img /> and <input />. - Curly braces
{} embed any JavaScript expression, such as a variable or a function call.
const user = { name: "Hedy Lamarr", avatarUrl: "/avatars/hedy.png" };
export function Profile() {
return (
<>
<h2 className="title">{user.name}</h2>
<img src={user.avatarUrl} alt={`Photo of ${user.name}`} width={64} />
</>
);
}
Props are read-only inputs that a parent component passes to a child. Typing them in TypeScript catches incorrect usage at compile time. The special children prop receives whatever you nest between the opening and closing tags.
import type { ReactNode } from "react";
type CardProps = {
title: string;
variant?: "default" | "warning";
children: ReactNode;
};
export function Card({ title, variant = "default", children }: CardProps) {
return (
<section className={`card card-${variant}`}>
<h3>{title}</h3>
{children}
</section>
);
}
// Usage: <Card title="Heads up" variant="warning"><p>Unsaved changes.</p></Card>
State with useState
State is a value a component remembers between renders. useState returns the current value and a setter; calling the setter tells React to re-render the component. Never mutate state directly: always replace it with a new value, and copy objects or arrays with the spread syntax.
import { useState } from "react";
export function ItemAdder() {
const [count, setCount] = useState(0);
const [items, setItems] = useState<string[]>([]);
function addItem() {
setItems([...items, `Task ${items.length + 1}`]); // a new array
setCount((c) => c + 1); // updater function when the next value depends on the previous one
}
return <button onClick={addItem}>Add ({count})</button>;
}
Conditional rendering, lists and keys
Use if, the ternary operator or && to render different output depending on a condition. Turn arrays into JSX with map, and give each item a key that is unique among its siblings and stable over time. React uses keys to match items between renders, so using the array index as a key in a list that can be reordered may attach state to the wrong item.
type Todo = { id: string; text: string; done: boolean };
export function TodoList({ todos }: { todos: Todo[] }) {
if (todos.length === 0) return <p>Nothing to do.</p>;
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text} {todo.done && "✓"}
</li>
))}
</ul>
);
}
useEffect, useMemo and useRef
useEffect synchronizes with external systems (timers, browser APIs, subscriptions) after rendering. It re-runs whenever a value in its dependency array changes, and the cleanup function it returns runs before the next run and when the component unmounts.useMemo caches the result of an expensive calculation until its dependencies change.useRef holds a value that does not trigger a re-render, or gives you access to a DOM element.
import { useEffect, useMemo, useRef, useState } from "react";
export function SearchBox({ words }: { words: string[] }) {
const [query, setQuery] = useState("");
const [seconds, setSeconds] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // cleanup
}, []);
const matches = useMemo(
() => words.filter((w) => w.includes(query)),
[words, query],
);
return (
<div>
<input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
<p>{matches.length} matches · {seconds}s elapsed</p>
</div>
);
}
If a value can be computed from props or state, compute it during rendering rather than in an effect. Call hooks only at the top level of a component or custom hook, never inside conditions or loops. These are the Rules of Hooks, and the ESLint plugin enforces them.
Composition
React reuses components through composition rather than inheritance. Build the frame with children or props that accept JSX, and let the caller fill in the content. Repeated stateful logic moves into custom hooks whose names start with use.
import { useState, type ReactNode } from "react";
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return [on, () => setOn((v) => !v)] as const;
}
export function Panel({ header, children }: { header: ReactNode; children: ReactNode }) {
const [open, toggle] = useToggle(true);
return (
<div className="panel">
<button onClick={toggle}>{header}</button>
{open && children}
</div>
);
}
Summary
- Components are functions that return JSX, and their names start with a capital letter.
- Props are read-only inputs; state comes from
useState and is always replaced, never mutated. - Give list items stable, unique
key values. - Use
useEffect to sync with external systems, useMemo for expensive calculations and useRef for DOM access or values that should not trigger renders. - Compose with
children and custom hooks instead of inheritance.
0 条评论
登录 · 登录后即可发表评论。