Files
notes/resource/前端/如何成为一个 React 工程师呢?.md
T
Docker7530 7ab924d668 1774110743
2026-03-22 00:32:26 +08:00

16 KiB
Raw Blame History

学习资料

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 采用固定节奏发布 + LTS 生命周期,每 6 个月发布一个,相当于每年会覆盖一个 LTS。

第一章 开发环境与核心概念

初始化环境

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。

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

我理解就是有了一个动态更新值的作用。

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 通信(只读的,子组件不可以进行修改,单向数据流。)

type Props = {
  name: string
}

const PrintNmae = (name: Props) => {
  return (
    <div>
      <h1>Welcome {name.name}</h1>
    </div>
  )
}

export default PrintNmae

普通传参

<PrintNmae name="John" />

children 传参,这里 type

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

类型声明:

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 类型:

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,此时实现了子组件决定函数的调用,但是父组件可以控制状态。

export const Button = () => {
  const handeClick = () => {
    alert('按钮触发了事件')
  }

  return <button onClick={handeClick}>点击我</button>
}

这里我们自己实现一个父子组件函数通信的能力。这里相当于我们自己声明了一个类似 onClick 的子组件。

type LoginButtonProps = {
  onLogin: () => void
}

export const LoginButton = ({ onLogin }: LoginButtonProps) => {
  return <button onClick={onLogin}>登录</button>
}

这里的时间对象不是原生浏览器对象,是一个合成事件。

useState

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 要用一个新的对象,否则话是不会生效的。

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

三元运算符

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>
  )
}

&& 标签显示问题

export const MaillBox = ({ messge }) => {
  return (
    <div>
      <h1>MailBox</h1>
      {messge.length > 0 && <h2>You have {messge.length} unread messages.</h2>}
    </div>
  )
}

代办

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 的关系,这里会展示两次挂载。

同时如果依赖数组如果是空的话,只会渲染一次。

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。

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. 无限循环
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>
  )
}

  1. 忘记放依赖项。

useRef

这里放的是 input

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>
  )
}

动态数据存储

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>
  )
}

第二部分

新老表单

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>
  )
}

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。

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 获取父组件的表单状态。

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

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