跳到主要内容

Basic React

新建react项目

npm create vite@latest my-react-app

cd my-react-app
npm install
npm run dev

spread and destruturing array or object

  1. spread array
const arr = [1, 2, 3];
const copy = [...arr];
console.log(copy); // [1, 2, 3]
  1. spread object
const obj = { a: 1, b: 2 };
const copy = { ...obj };
console.log(copy); // { a: 1, b: 2 }
  1. array destruturing
const [a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
  1. object destruturing
const person = { name: "Alice", age: 25 };
const { name, age } = person;
console.log(name, age); // Alice 25

Updating a nested object

consider the object construct like this,

const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});

you can update immutable object like this way,

const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

or

setPerson({
...person, // Copy other fields
artwork: { // but replace the artwork
...person.artwork, // with the same one
city: 'New Delhi' // but in New Delhi!
}
});

or use useImmer instead of useState

another way you can use use-immer library, handle deeper nested object.

  const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});

function handleNameChange(e) {
updatePerson(draft => {
draft.name = e.target.value;
});
}

usage from official website

箭头函数

写法含义是否需要写 return
(param) => ( expression )隐式返回,自动返回括号里的表达式结果❌ 不需要
(param) => { statement }函数体语句块,不会自动返回,需要手动写 return✅ 需要

react数据遍历指定key

key 只能用在 父组件 map 渲染列表的元素上,而不是子组件内部。

Need key component
export default function Contact({
img,
imgAlt,
name,
phoneNumber,
email,
}: ContactInfo) {
return (
//这是 不对的,因为 key 只能用在 父组件 map
// 渲染列表的元素上,而不是子组件内部。
// <article className="contact-card" key={id}>
<article className="contact-card">
<img src={img} alt={imgAlt} />
<h3>{name}</h3>
<div className="info-group">
<img src={phoneIcon} alt="phone icon" />
<p>{phoneNumber}</p>
</div>
<div className="info-group">
<img src={mailIcon} alt="mail icon" />
<p>{email}</p>
</div>
</article>
);
}
Parent component pass key
function App() {
return (
<div className="contacts">
{contacts.map((contact) => (
<Contact key={contact.id} {...contact} />
))}
</div>
);
}

向react组件传递参数

Contact.txs
import phoneIcon from "./images/phone-icon.png";
import mailIcon from "./images/mail-icon.png";
import type ContactInfo from "./ContactInfo";

export default function Contact({
img,
imgAlt,
name,
phoneNumber,
email,
}: ContactInfo) {
return (
//这是 不对的,因为 key 只能用在 父组件 map 渲染列表的元素上,而不是子组件内部。
// <article className="contact-card" key={id}>
<article className="contact-card">
<img src={img} alt={imgAlt} />
<h3>{name}</h3>
<div className="info-group">
<img src={phoneIcon} alt="phone icon" />
<p>{phoneNumber}</p>
</div>
<div className="info-group">
<img src={mailIcon} alt="mail icon" />
<p>{email}</p>
</div>
</article>
);
}
App.tsx
import "./index.css";
import Contact from "./Contact";
import { contacts } from "./ContactInfo";
function App() {
return (
<div className="contacts">
{contacts.map((contact) => (
<Contact key={contact.id} {...contact} />
))}
</div>
);
}
export default App;

useState function

Main.tsx
import { useState } from "react";

export default function Main() {
// Array destructuring
const [ingredients, setIngredients] = useState<string[]>([
"Chicken",
"Oregano",
"Tomatoes",
]);
const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
// the key is the name of input
const newIngredient = formData.get("ingredient");
if (typeof newIngredient === "string" && newIngredient?.trim() !== "") {
setIngredients((prev) => [...prev, newIngredient]);
} else {
console.warn("No ingredient provided");
}
};

return (
<main>
<form className="add-ingredient-form" onSubmit={handleSubmit}>
<input
type="text"
name="ingredient"
aria-label="Add ingredient"
placeholder="e.g. oregano"
></input>
<button>Add ingredient</button>
</form>
<h1>Ingrredients on hand:</h1>
<ul className="ingredient-ul">
{ingredients.map((ingredient) => (
<li key={ingredient}>{ingredient}</li>
))}
</ul>
</main>
);
}

onClick传值函数名or匿名函数

Version 1 (onClick={getRecipe})

You’re giving React a function reference. React will call getRecipe() only when the button is clicked.

This is the simplest and most efficient form — no new function is created on every render.

Version 2 (onClick={() => getRecipe()})

You’re creating a new anonymous function on each render that calls getRecipe() inside it.

It still works — React will call your wrapper function on click, which in turn calls getRecipe() — but it’s one extra layer of indirection and (very slightly) less efficient.

CodeWhen to useNotes
onClick={changeToHeld}Default, most common,or closure functionPass function directly
onClick={() => changeToHeld(id)}When you need to wrap logic or pass argumentsCreates a new function every render

pass parameter function

App.tsx
import React, { useState } from "react";
import "./index.css";
import Die from "./Die";
import { nanoid } from "nanoid/non-secure";

export interface DiceProp {
value: number;
isHeld: boolean;
id: string;
}

const App: React.FC = () => {
const generateDice = () =>
Array.from({ length: 10 }, () => ({
value: Math.ceil(Math.random() * 6),
isHeld: false,
id: nanoid(),
}));
const [dice, setDice] = useState<DiceProp[]>(generateDice());
const changeToHeld = (id: string) =>
setDice((prevDice) =>
prevDice.map((die) => (die.id === id ? { ...die, isHeld: true } : die))
);
const diceElements = dice.map((diceProp) => (
<Die key={diceProp.id} changeToHeld={changeToHeld} die={diceProp} />
));

const rollDice = () => {
setDice(generateDice());
};

return (
<main>
<div className="dice-container">{diceElements}</div>
<button className="roll-button" onClick={rollDice}>
Roll
</button>
</main>
);
};

export default App;
Die.tsx
import type { DiceProp } from "./App";

/**
* Recommendation
* For scalable apps / larger state management: Option 1 is preferred
* because it separates state (data) from behavior (actions), which aligns with React best practices.
*/
const Die: React.FC<{ die: DiceProp; changeToHeld: (id: string) => void }> = ({
die,
changeToHeld,
}) => {
const { value, isHeld, id } = die;
return (
<button
className={isHeld ? "held" : "notHeld"}
onClick={() => changeToHeld(id)}
>
{value}
</button>
);
};

export default Die;

pass a closure function

App.tsx
import React, { useState } from "react";
import "./index.css";
import Die from "./Die";
import { nanoid } from "nanoid/non-secure";

export interface DiceProp {
value: number;
isHeld: boolean;
id: string;
}

const App: React.FC = () => {
const generateDice = () =>
Array.from({ length: 10 }, () => ({
value: Math.ceil(Math.random() * 6),
isHeld: false,
id: nanoid(),
}));
const [dice, setDice] = useState<DiceProp[]>(generateDice());
const changeToHeld = (id: string) =>
setDice((prevDice) =>
prevDice.map((die) => (die.id === id ? { ...die, isHeld: true } : die))
);

const diceElements = dice.map((diceProp) => (
<Die
key={diceProp.id}
// closure function
changeToHeld={() => changeToHeld(diceProp.id)}
die={diceProp}
/>
));

const rollDice = () => {
setDice(generateDice());
};

return (
<main>
<div className="dice-container">{diceElements}</div>
<button className="roll-button" onClick={rollDice}>
Roll
</button>
</main>
);
};

export default App;
Die.tsx
import type { DiceProp } from "./App";

const Die: React.FC<{ die: DiceProp; changeToHeld: () => void }> = ({
die,
changeToHeld,
}) => {
const { value, isHeld } = die;
return (
<button className={isHeld ? "held" : "notHeld"} onClick={changeToHeld}>
{value}
</button>
);
};

export default Die;

如何动态设置变量名为key值

错误示范
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.currentTarget;
setMeme((prev) => ({ ...prev, name: value }));
};
正确用法示范
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.currentTarget;
setMeme((prev) => ({ ...prev, [name]: value }));
};

tsx能渲染的类型

React 的 JSX 表达式 { ... } 里,只能直接渲染以下类型:

  • 字符串 (string)
  • 数字 (number)
  • 布尔值 (boolean)
  • React 元素 (JSX 元素)
  • 数组(包含以上类型)
  • 不能渲染对象,对象数组

useEffect function

when calling useEffect, depends on the dependents in [].

That's look this example.

infinite loop
import React from "react";

export default function App() {
const [starWarsData, setStarWarsData] = React.useState({});
const [count, setCount] = React.useState(0);

console.log("Rendered!");

React.useEffect(() => {
console.log("useEffect runs");
fetch("https://swapi.dev/api/people/1")
.then((res) => res.json())
.then((data) => setStarWarsData(data)
);
}, [starWarsData]);

return (
<div>
<h2>The count is {count}</h2>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>
Add
</button>
<pre>{JSON.stringify(starWarsData, null, 2)}</pre>
</div>
);
}

Each time setStarWarsData(data) runs, React receives a new { ... } object. Even if the JSON content is identical, it’s a different object reference, so React thinks the dependency changed and re-runs the effect — causing an infinite loop.

corret usage
React.useEffect(() => {
console.log("useEffect runs");
fetch("https://swapi.dev/api/people/1")
.then((res) => res.json())
.then((data) =>
setStarWarsData(data)
);
}, []);

What happens when visit the page first time?

Console Output
Rendered!
App.tsx:11 useEffect runs
App.tsx:8 Rendered!
StageTriggerWhat happensConsole output
1Initial mountComponent function executesRendered!
2After mountuseEffect callback runsuseEffect runs
3State updatedComponent re-renders with new dataRendered!

useRef

FocusInput
import { useRef } from "react";
export default function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null);

const handleFocus = () => {
// current 可能为 null,因此需要进行非空检查
if (inputRef.current) {
inputRef.current.focus();
}
};

return (
<div>
<input ref={inputRef} type="text" placeholder="Click the button to focus me" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}

how to clean resouce in useEffect

WindowTracker
import { useEffect, useState } from "react";

export default function WindowTracker() {
const [windowWidth, setWindownWidth] = useState<number>(window.innerWidth);
useEffect(() => {
const watchWindowWidth = () => {
console.log("resized!");
setWindownWidth(window.innerWidth);
};
window.addEventListener("resize", watchWindowWidth);
// cleaning up what creating before
return () => {
window.removeEventListener("resize", watchWindowWidth);
console.log("cleaning up");
};
}, []);
return <h1>Window width: {windowWidth}</h1>;
}

Array.from

// The _ means: “Ignore the element, just use the index.”
Array.from({ length: 3 }, (_, i) => i * 2);
// → [0, 2, 4]

当没有参数需要被使用时,使用不写参数.

Array.from({ length: 10 }, () => Math.ceil(Math.random() * 6))

Array.every

array.every(callback(element, index, array), thisArg?)

  1. with callback only
check all elements meet condition
const threshold = { limit: 10 };
const numbers = [3, 5, 9];

// Arrow functions do NOT bind `this`, so `thisArg` is ignored.
const allBelowLimit = numbers.every((num) => num <br threshold.limit);

console.log(allBelowLimit); // true
  1. with callback and thisArg
check all elements meet condition with thisArg
const threshold = 5;
const numbers = [6, 7, 8, 9, 10];
const allAboveThreshold = numbers.every(function (num) {
return num > this.threshold;
}, { threshold });
console.log(allAboveThreshold); // true

lazy Initialization for useState

1️⃣ Lazy initialization: useState(() => generateDice())

  • Here, you pass a function to useState.
  • React will call that function once — only on the initial render.
  • The return value of the function becomes the initial state.
  • After that, React never calls the function again, because it already has the state stored internally.
  • Benefit: Useful when computing the initial state is expensive.

Explain:

  1. Component mounts → React calls () => generateDice().
  2. The return value (DiceProp[]) is stored as the state.
  3. Component re-renders → React reuses the stored state, function is never called again.

2️⃣ Non-lazy way: const [dice, setDice] = useState(generateDice());

  • Here, you call generateDice() immediately and pass its return value to useState.
  • This happens every render of the component, but only the first value matters for initialization.

Explain:

  1. Component renders → generateDice() runs → returns array → React stores as state.
  2. Component re-renders later → generateDice() still runs, but React ignores it, because the state is already managed internally.
  3. Problem: If generateDice() is expensive, you waste CPU calling it unnecessarily on every render.

reusable routes

All Xml codes

Xml
GameRoutes.tsx
import { Route, Routes } from "react-router-dom";
import GamesId from "./GamesId";
import GameLayout from "./GameLayout";

// this contains links and routes
// 1. move all sub Route
// 2. wrapper all routes in to a "<Route element={<GameLayout />}></Route>"
// this part of code equivalent to "src/routes/v4/gameRoutes.tsx"
export default function GameRoutes() {
return (
<>
{/* <GameLayout /> */}
<Routes>
<Route element={<GameLayout />}>
<Route index element={<h1>Games</h1>}></Route>
<Route path=":id" element={<GamesId />}></Route>
<Route path="search" element={<h1>games search</h1>}></Route>
</Route>
</Routes>
</>
);
}
App.tsx
import { Link, Route, Routes } from "react-router-dom";
import NotFoundPage from "../other-function/NotFoundPage";
import GameRoutes from "./GameRoutes";
import GamesHome from "./GamesHome";

export default function App() {
return (
<>
<nav
style={{
display: "flex",
flexDirection: "column",
}}
>
{/* if using replace here, backward will be two pages instead of one page */}
<Link to="/" replace>
home
</Link>
<Link to="/games">games</Link>
</nav>
<Routes>
<Route path="/" element={<GamesHome />}></Route>
{/* must "/*"" after "/games" */}
<Route path="/games/*" element={<GameRoutes />}></Route>
<Route path="*" element={<NotFoundPage />}></Route>
</Routes>
</>
);
}

All Json codes

Json
gameRoute.tsx
import type { RouteObject } from "react-router-dom";
import GameLayout from "./GameLayout";
import GameItem from "./GameItem";
/**
* reusable links and routes in gameRoutes,
* - index
* - index > {id}
* - index > description
*/
const gameRoutes: RouteObject = {
element: <GameLayout />,
children: [
{ index: true, element: <h1>games</h1> },
{ path: ":id", element: <GameItem /> },
{
path: "description",
element: (
<p>
PUBG is a last-player-standing shooter where up to 100 players
parachute onto an island, scavenge for weapons and equipment, and
fight to be the sole survivor. The playable area continuously shrinks
throughout the match, forcing players into closer combat.
</p>
),
},
],
};

export default gameRoutes;
routes.tsx
import type { RouteObject } from "react-router-dom";

import gameRoutes from "./gameRoutes";

// equivalent to <Routes>...</Routes>
export const routes: RouteObject[] = [
{
path: "/",
element: <h1>home</h1>,
},
{
path: "/games",
// nested layout links here, need Layout inside of it.
/* element: <GameLayout />,
children: [
{ index: true, element: <h1>games</h1> },
{ path: ":id", element: <GameItem /> },
{ path: "search", element: <h1>games search</h1> },
], */
// reuse code from gameRoutes
...gameRoutes,
},
{
path: "/games1",
...gameRoutes,
},
];
协议
本作品代码部分采用 Apache 2.0协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:
  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留Apache 2.0协议文件。
本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:
  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0国际许可协议进行许可。