# 学习资料
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()`
> 例如:
> `
`,但更好的做法是使用 `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
{
state = { count: 0 }
handleClick = () => {
// this.setState({ count: this.state.count + 1 })
this.setState((prevState) => ({ count: prevState.count + 1 }))
}
render() {
return (
Class Counter
)
}
}
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 (
Functional Counter
)
}
export default FunctionalCounter
```
# 第二章 组件化开发核心
Props 通信(只读的,子组件不可以进行修改,单向数据流。)
```tsx
type Props = {
name: string
}
const PrintNmae = (name: Props) => {
return (
Welcome {name.name}
)
}
export default PrintNmae
```
普通传参
```tsx
```
children 传参,这里 type
```tsx
import React from 'react'
type CardProps = React.PropsWithChildren<{}>
const Card = ({ children }: CardProps) => {
return (
{children}
)
}
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 (
{name}
Age: {age}
校验: {isVerified ? '已验证' : '未验证'}
{hobbies && hobbies.length > 0 &&
爱好: {hobbies.join(', ')}
}
)
}
```
关于 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 (
{name}
Age: {age}
校验: {isVerified ? '已验证' : '未验证'}
{hobbies && hobbies.length > 0 &&
爱好: {hobbies.join(', ')}
}
)
}
```
事件处理和合成事件系统
这里相当于把函数当作了 Porps,此时实现了子组件决定函数的调用,但是父组件可以控制状态。
```tsx
export const Button = () => {
const handeClick = () => {
alert('按钮触发了事件')
}
return
}
```
这里我们自己实现一个父子组件函数通信的能力。这里相当于我们自己声明了一个类似 onClick 的子组件。
```tsx
type LoginButtonProps = {
onLogin: () => void
}
export const LoginButton = ({ onLogin }: LoginButtonProps) => {
return
}
```
> 这里的时间对象不是原生浏览器对象,是一个合成事件。
useState
```tsx
import { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0)
const handelClick = () => {
setCount(count + 1)
}
return (
Counter
Count: {count}
)
}
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 (
ErrorState
Name {user.name}
Age {user.age}
)
}
export default ErrorState
```
三元运算符
```tsx
import { useState } from 'react'
export const Login = () => {
const [isLogin, setIsLogin] = useState(false)
return (
{isLogin ? 'Welcome Back!' : 'Please Login'}
)
}
```
`&&` 标签显示问题
```tsx
export const MaillBox = ({ messge }) => {
return (
MailBox
{messge.length > 0 && You have {messge.length} unread messages.
}
)
}
```
代办
```tsx
const todos = [
{ id: 1, tesx: 'Learn React' },
{ id: 2, tesx: 'Learn TypeScript' },
{ id: 3, tesx: 'Build a React App' },
]
export const TodoList = () => {
return (
Todo List
{todos.map((todo) => (
- {todo.tesx}
))}
)
}
```
## 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 Loading...
}
return (
User Profile
Name: {userData.name}
Age: {userData.age}
)
}
```
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 加载中...
}
return (
用户资料(当前 ID: {userId})
姓名:{userData.name}
年龄:{userData.age}
)
}
```
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 (
无限循环演示
当前 count: {count}
看控制台!页面会疯狂卡顿,count 一直加!
)
}
```
2. 忘记放依赖项。
useRef
这里放的是 input
```tsx
import { useRef, useEffect } from 'react'
export const RefExample = () => {
const inputRef = useRef(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return (
useRef 示例:自动聚焦输入框
)
}
```
动态数据存储
```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 (
setQuery(e.target.value)}>
)
}
```
## 第二部分
新老表单
```tsx
export const OldForm = () => {
const handleSubmit = (e) => {
e.preventDefault() // 这里是为了阻止表单提交默认的动作。
const formData = new FormData(e.target)
const name = formData.get('name')
console.log(name)
}
return (
)
}
```
```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 (
)
}
```
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 (
)
}
```
useformState
可使用 useFormState 获取父组件的表单状态。
```tsx
import { useFormStatus } from 'react-dom'
const SubButton = () => {
const { pending } = useFormStatus()
return (
)
}
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 (
)
}
```
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 用户名:{user.name}
}
export default function App() {
const userPromise = fetchUser()
return (
加载失败了}>
用户加载中...}>
)
}
```
# CSS
顶级作用域
```
:root {
```
媒体查询
```
@media (prefers-color-scheme: light) {
}
```
ID 选择器
```
#root
```