732 lines
16 KiB
Markdown
732 lines
16 KiB
Markdown
# 学习资料
|
||
|
||
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 │ ← Runtime(API / 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>
|
||
)
|
||
}
|
||
|
||
```
|
||
|
||
# CSS
|
||
|
||
顶级作用域
|
||
|
||
```
|
||
:root {
|
||
```
|
||
|
||
媒体查询
|
||
|
||
```
|
||
@media (prefers-color-scheme: light) {
|
||
|
||
}
|
||
```
|
||
|
||
ID 选择器
|
||
|
||
```
|
||
#root
|
||
```
|