Files
notes/resource/前端/如何成为一个 React 工程师呢?.md
Docker7530 ab0cbad418 1774315861
2026-03-24 09:31:04 +08:00

741 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 学习资料
https://u19tul1sz9g.feishu.cn/docx/WEWJdSzF5oTnwvxHGGAc0Vojnxe?from=from_copylink
密码:M6923&65
# 关于 Node 的理解
| Node.js | Java |
| --------------- | ----------- |
| Node.js version | JDK version |
| V8 Engine | JVM |
| Npm | Maven |
| N-API | JNI |
```
┌───────────────┐
│ JS APP │
└───────▲───────┘
┌───────┴───────┐
│ NPM │ ← 包管理 & 生态
└───────▲───────┘
┌───────┴───────┐
│ Node.js │ ← RuntimeAPI / EventLoop / Libuv
└───────▲───────┘
┌───────┴───────┐
│ V8 │ ← JS 引擎
└───────▲───────┘
操作系统
```
## Node.js 版本
[Node.js](https://nodejs.org/zh-cn/about/previous-releases) 采用固定节奏发布 + LTS 生命周期,每 6 个月发布一个,相当于每年会覆盖一个 LTS。
# 第一章 开发环境与核心概念
初始化环境
```bash
pnpm create vite@latest basic2 --template react-ts
```
所谓的优雅公式:`UI = f(State)`,用户界面仅仅是应用程序状态的一个函数。
JSX 不是 HTML 而是 JavaScript 的一种特殊语法。我们编写的每一行 JSX 代码最后都会被转换为一个函数:`React.createElement()`
> 例如:
> `<h1 className="title">Hello React</h1>`
> 对应:
> `React.createElement('h1', { className: 'title' }, 'Hello React')`
可以使用 `{ }` 在 JSX 中嵌入任何 JavaScript 表达式(变量、数学、函数)。
关于声明:
| 关键字 | 作用域 | 重复声明 | 是否可以修改引用 | 推荐场景 |
| ----------- | ---------------- | ---- | -------- | -------------------------- |
| **`const`** | 块级作用域 (Block) | 不允许 | 不允许 | **默认使用**,除非变量需要重新赋值 |
| **`let`** | 块级作用域 (Block) | 不允许 | 允许 | 用于循环变量或需要修改引用的场景 |
| **`var`** | 函数作用域 (Function) | 允许 | 允许 | **严禁使用** (旧时代的产物,存在变量提升问题) |
一个组件的返回必须有一个**单一的根元素**,如果返回并列的多个元素,可以嵌套一个 `<div>`,但更好的做法是使用 `Fragment`,语法糖是 `<>`。因 JSX 最终是会被编译为 JavaScript,所以要注意不要产生关键字的冲突。这也是为什么 `class` 在 React 中要写成 `className`,同时时间名要遵循驼峰命名法,例如 `onClick`
**导入方式**
具名导入:
```
import { ... } from 'react'
```
默认导入:
```
import ... from 'react'
```
副作用导入:
```
import './index.css'
```
React 历史写法:
这里我还是不是很理解 `(prevState) => ({ count: prevState.count + 1 })` 这个的原理,目前的理解是,他可以拿到自己作用域的 count。
```tsx
import React from 'react'
interface CounterState {
count: number
}
class ClassCounter extends React.Component<unknown, CounterState> {
state = { count: 0 }
handleClick = () => {
// this.setState({ count: this.state.count + 1 })
this.setState((prevState) => ({ count: prevState.count + 1 }))
}
render() {
return (
<div>
<h1>Class Counter</h1>
<button onClick={this.handleClick}>数字是 {this.state.count}</button>
</div>
)
}
}
export default ClassCounter
```
useState
我理解就是有了一个动态更新值的作用。
```tsx
import { useState } from 'react'
const FunctionalCounter = () => {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount((prevCount) => prevCount + 1)
setCount((prevCount) => prevCount + 1)
}
console.log(count)
return (
<div>
<h1>Functional Counter</h1>
<button onClick={handleClick}>数字1 {count}</button>
</div>
)
}
export default FunctionalCounter
```
# 第二章 组件化开发核心
Props 通信(只读的,子组件不可以进行修改,单向数据流。)
```tsx
type Props = {
name: string
}
const PrintNmae = (name: Props) => {
return (
<div>
<h1>Welcome {name.name}</h1>
</div>
)
}
export default PrintNmae
```
普通传参
```tsx
<PrintNmae name="John" />
```
children 传参,这里 type
```tsx
import React from 'react'
type CardProps = React.PropsWithChildren<{}>
const Card = ({ children }: CardProps) => {
return (
<div
style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}
>
{children}
</div>
)
}
export default Card
```
关于 `{ children }: CardProps` 是组件接收一个 props 然后从里边结构出来 children
类型声明:
```tsx
type UserProps = {
name: string
age: number
isVerified: boolean
hobbies?: string[]
}
export const User = (user: UserProps) => {
const { name, age, isVerified, hobbies } = user
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>校验: {isVerified ? '已验证' : '未验证'}</p>
{hobbies && hobbies.length > 0 && <p>爱好: {hobbies.join(', ')}</p>}
</div>
)
}
```
关于 TypeScript 定义 Props 类型:
```tsx
type UserProps = {
name: string
age: number
isVerified: boolean
hobbies?: string[]
}
export const User = (user: UserProps) => {
const { name, age, isVerified, hobbies } = user // 这里是解构的意思。
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>校验: {isVerified ? '已验证' : '未验证'}</p>
{hobbies && hobbies.length > 0 && <p>爱好: {hobbies.join(', ')}</p>}
</div>
)
}
```
事件处理和合成事件系统
这里相当于把函数当作了 Porps,此时实现了子组件决定函数的调用,但是父组件可以控制状态。
```tsx
export const Button = () => {
const handeClick = () => {
alert('按钮触发了事件')
}
return <button onClick={handeClick}>点击我</button>
}
```
这里我们自己实现一个父子组件函数通信的能力。这里相当于我们自己声明了一个类似 onClick 的子组件。
```tsx
type LoginButtonProps = {
onLogin: () => void
}
export const LoginButton = ({ onLogin }: LoginButtonProps) => {
return <button onClick={onLogin}>登录</button>
}
```
> 这里的时间对象不是原生浏览器对象,是一个合成事件。
useState
```tsx
import { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
const handelClick = () => {
setCount(count + 1)
}
return (
<div>
<h1>Counter</h1>
<p>Count: {count}</p>
<button onClick={handelClick}>增加</button>
</div>
)
}
export default Counter
```
这里 `setUser` 要用一个新的对象,否则话是不会生效的。
```tsx
import { useState } from 'react'
const ErrorState = () => {
const [user, setUser] = useState({ name: 'tianzhuo', age: 20 })
const handelClick = () => {
setUser({ ...user, age: user.age + 1 })
}
return (
<div>
<h1>ErrorState</h1>
<p>Name {user.name}</p>
<p>Age {user.age}</p>
<button onClick={handelClick}>Increment Age</button>
</div>
)
}
export default ErrorState
```
三元运算符
```tsx
import { useState } from 'react'
export const Login = () => {
const [isLogin, setIsLogin] = useState(false)
return (
<div>
<h1>{isLogin ? 'Welcome Back!' : 'Please Login'}</h1>
<button onClick={() => setIsLogin(!isLogin)}>Toggle Login</button>
</div>
)
}
```
`&&` 标签显示问题
```tsx
export const MaillBox = ({ messge }) => {
return (
<div>
<h1>MailBox</h1>
{messge.length > 0 && <h2>You have {messge.length} unread messages.</h2>}
</div>
)
}
```
代办
```tsx
const todos = [
{ id: 1, tesx: 'Learn React' },
{ id: 2, tesx: 'Learn TypeScript' },
{ id: 3, tesx: 'Build a React App' },
]
export const TodoList = () => {
return (
<div>
<h1>Todo List</h1>
<ul className="li">
{todos.map((todo) => (
<li key={todo.id}>{todo.tesx}</li>
))}
</ul>
</div>
)
}
```
## hook 生命周期
因为 StrictMode 的关系,这里会展示两次挂载。
同时如果依赖数组如果是空的话,只会渲染一次。
```tsx
import { useState, useEffect } from 'react'
export const UserProfile = ({ userId }) => {
const [userData, setUserData] = useState(null)
useEffect(() => {
console.log('Fetching user data for userId:', userId)
const timer = setTimeout(() => {
setUserData({ name: 'John Doe', age: 30 })
}, 1000)
return () => {
clearTimeout(timer)
console.log('🧹 定时器已清理')
}
}, [userId])
if (!userData) {
return <div>Loading...</div>
}
return (
<div>
<h1>User Profile</h1>
<p>Name: {userData.name}</p>
<p>Age: {userData.age}</p>
</div>
)
}
```
hook 的更新
这里相当与用 useState 触发了依赖数组的更新。只得关注的是每变化的时候,是先执行上一次的 clean。
```tsx
import { useState, useEffect } from 'react'
export const UserProfile = () => {
const [userId, setUserId] = useState(1) // ← 当前用户ID
const [userData, setUserData] = useState(null)
// 模拟不同ID返回不同数据
const mockUsers = {
1: { name: '张三', age: 28 },
2: { name: '李四', age: 35 },
3: { name: '王五', age: 42 },
}
useEffect(() => {
console.log(`🚀 开始请求 userId: ${userId}`)
const timer = setTimeout(() => {
setUserData(mockUsers[userId] || { name: '未知用户', age: 0 })
console.log(`✅ 数据加载完成!当前用户: ${mockUsers[userId]?.name}`)
}, 800)
// 清理函数(StrictMode 会展示)
return () => {
console.log(`🧹 清理上一次的定时器 (userId: ${userId})`)
clearTimeout(timer)
}
}, [userId]) // ← 关键!依赖 userId
if (!userData) {
return <div>加载中...</div>
}
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h1>用户资料(当前 ID: {userId}</h1>
<p>姓名:{userData.name}</p>
<p>年龄:{userData.age}</p>
<hr />
<button onClick={() => setUserId(1)}>切换到张三 (ID=1)</button>
<button onClick={() => setUserId(2)} style={{ marginLeft: '10px' }}>
切换到李四 (ID=2)
</button>
<button onClick={() => setUserId(3)} style={{ marginLeft: '10px' }}>
切换到王五 (ID=3)
</button>
</div>
)
}
```
hook 常见问题
1. 无限循环
```tsx
import { useState, useEffect } from 'react'
export const BadExample = () => {
const [count, setCount] = useState(0)
useEffect(() => {
console.log(`🔄 Effect 执行了!当前 count = ${count}`)
// 这里更新了 count,而 count 又是依赖项
setCount(count + 1) // ← 罪魁祸首!
}, [count]) // ← 依赖了 count
return (
<div style={{ padding: '20px', border: '2px solid red' }}>
<h1>无限循环演示</h1>
<p>当前 count: {count}</p>
<p style={{ color: 'red' }}>看控制台!页面会疯狂卡顿,count 一直加!</p>
<button onClick={() => setCount(0)}>重置(也没用)</button>
</div>
)
}
```
2. 忘记放依赖项。
useRef
这里放的是 input
```tsx
import { useRef, useEffect } from 'react'
export const RefExample = () => {
const inputRef = useRef(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return (
<div>
<h1>useRef 示例:自动聚焦输入框</h1>
<input ref={inputRef} type="text" placeholder="focus"></input>
</div>
)
}
```
动态数据存储
```tsx
import { useRef, useState, useEffect } from 'react'
export const Search = () => {
const [query, setQuery] = useState('')
const timeOutRef = useRef(null)
useEffect(() => {
timeOutRef.current = setTimeout(() => {
console.log('搜索:', query)
}, 500)
return () => {
console.log('清除上一次的定时器')
clearTimeout(timeOutRef.current)
}
}, [query])
return (
<input value={query} onChange={(e) => setQuery(e.target.value)}></input>
)
}
```
## 第二部分
新老表单
```tsx
export const OldForm = () => {
const handleSubmit = (e) => {
e.preventDefault() // 这里是为了阻止表单提交默认的动作。
const formData = new FormData(e.target)
const name = formData.get('name')
console.log(name)
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />
</form>
)
}
```
```tsx
export const NewForm = () => {
const handleSubmit = async (formdata: FormData) => { // 异步操作
const name = formdata.get('name')
console.log(name)
await new Promise((resolve) => setTimeout(resolve, 1000))
}
return (
<form action={handleSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
)
}
```
useActionState,这个我理解其实是一个对历史功能的封装优化。简化了自己需要声明很多的 state。
```tsx
import { useActionState } from 'react'
export const UseActionState = ({ productId }) => {
const addToCart = async (previousState, formData) => {
const quantity = formData.get('quantity')
console.log('Added to cart')
const result = await addToCart1(productId, quantity)
if (result.success) {
return { success: true, message: 'Product added to cart successfully!' }
} else {
return { success: false, message: 'Failed to add product to cart.' }
}
}
const addToCart1 = (productId, quantity) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true })
}, 1000)
})
}
const [state, submitAction, isPending] = useActionState(addToCart, null)
return (
<form action={submitAction}>
<input type="number" name="quantity" defaultValue={1} min={1} />
<button type="submit" disabled={isPending}>
{isPending ? 'Adding...' : 'Add to Cart'}
</button>
{state?.message && <p>{state.message}</p>}
</form>
)
}
```
useformState
可使用 useFormState 获取父组件的表单状态。
```tsx
import { useFormStatus } from 'react-dom'
const SubButton = () => {
const { pending } = useFormStatus()
return (
<button type="subbmit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
)
}
export const NewForm = () => {
const handeSubmit = async (formData) => {
const name = formData.get('name')
console.log('Form submitted with name:', name)
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log('Form submission completed')
}
return (
<div>
<form action={handeSubmit}>
<input type="text" name="name" />
<SubButton />
</form>
</div>
)
}
```
use
这里的一个想法是实现了层级 fallback
```tsx
import React, { Suspense, use } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
function fetchUser() {
return new Promise<{ name: string }>((resolve, reject) => {
setTimeout(() => {
const success = false
if (success) {
resolve({ name: '张三' })
} else {
reject(new Error('获取用户失败'))
}
}, 2000)
})
}
function UserInfo({ userPromise }: { userPromise: Promise<{ name: string }> }) {
const user = use(userPromise)
return <h2>用户名:{user.name}</h2>
}
export default function App() {
const userPromise = fetchUser()
return (
<ErrorBoundary fallback={<p>加载失败了</p>}>
<Suspense fallback={<p>用户加载中...</p>}>
<UserInfo userPromise={userPromise} />
</Suspense>
</ErrorBoundary>
)
}
```
---
| 模式 | 英文 | 中文 | 类比 | 特点 | SEO |
| --- | ----------------------- | ----------- | ---------------- | -------------------- | -------- |
| CSR | Client-Side Rendering | 客户端渲染 | 外卖(现点+等) | 慢启动,但灵活 | ❌ |
| SSR | Server-Side Rendering | 服务端渲染 | 餐厅现做 | 平衡(快+动态) | ✅ |
| SSG | Static Site Generation | 静态站点生成 | 预制菜 | 极快,但不灵活 | ✅ |
| RSC | React Server Components | React 服务端组件 | 后厨分工(部分菜在厨房直接处理) | 组件在服务器执行,不进浏览器,减少 JS | ⚠️(间接有利) |
# CSS
顶级作用域
```
:root {
```
媒体查询
```
@media (prefers-color-scheme: light) {
}
```
ID 选择器
```
#root
```