React: Hooks Rules and Common Pitfalls

Last updated: 2026-08-26

1. What You'll Learn



2. A Story of a Debugging Nightmare

(1) Pain Point: It took 3 hours to debug a single useEffect

Alice wrote a countdown component:

▶ Example: Hooks Rule Demo

Output:

TEXT 📖 Display only
State: timeLeft. side effects via useEffect. interval updates
JSX
function Countdown({ targetDate }) {
  const [timeLeft, setTimeLeft] = useState(computeTimeLeft())

  function computeTimeLeft() {
    return Math.max(0, Math.floor((new Date(targetDate) - Date.now()) / 1000))
  }

  useEffect(() => {
    const timer = setInterval(() => {
      // ❌ Closure Pitfalls:timeLeft Always the initial value
      setTimeLeft(timeLeft - 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [])  // I want it to start only when mounted

  return <p>Remaining:{timeLeft}s</p>
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
useEffect manages side effects

Bug: The countdown always stops at the initial value of -1. This is because the timeLeft captured in the setInterval callback is the value from the first render (closure trap).

(2) Fix

JSX
// ✅ Fix 1:Functional Updates
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))  // prev Always the latest value
  }, 1000)
  return () => clearInterval(timer)
}, [])

// ✅ Fix 2:Or put targetDate Add to the dependencies array
useEffect(() => {
  const timer = setInterval(() => {
    setTimeLeft(prev => Math.max(0, prev - 1))
  }, 1000)
  return () => clearInterval(timer)
}, [targetDate])  // targetDate Restart the timer when a change occurs
▶ Try it Yourself

Lesson: Never read the state directly in useEffect to update yourself. Use functional updates setState(prev => prev + 1).



3. The Two Ironclad Rules of Hooks

(1) Rule 1: Call hooks only at the top level

Do not call hooks inside loops, conditional statements, or nested functions:

JSX
function BadComponent({ showExtra }) {
  const [name, setName] = useState('')  // ✅ Top Floor

  // ❌ Rule Violation:Called within a condition Hook
  if (showExtra) {
    const [extra, setExtra] = useState('')  // Error!
    useEffect(() => { /* ... */ }, [])
  }

  // ❌ Rule Violation:Called within a loop Hook
  for (let i = 0; i < items.length; i++) {
    useEffect(() => { /* ... */ }, [])  // Error!
  }

  // ✅ Correct:Conditional logic in Hook Internal Processing
  const [extra, setExtra] = useState('')
  useEffect(() => {
    if (showExtra) { /* Conditional logic is placed in useEffect Inside */ }
  }, [showExtra])
}
▶ Try it Yourself
Call Location Valid Reason
Top-level function components The order of the Hooks chain remains consistent with every render
Top-level custom hooks Custom hooks are essentially sub-call chains of function components
Inside if / for / while Changes in conditions cause inconsistent hook call counts, resulting in linked list misalignment
General utility functions Not in the React component context; no Hooks chain
Class Component Methods Class components do not have a Hooks chain mechanism
Inside a useEffect retorno de chamada The retorno de chamada is not at the top level of the component, so the call order is unpredictable

(2) Rule 2: Call Hooks Only in Function Components or Custom Hooks

JSX
// ❌ Called from a regular function Hook
function getData() {
  const [data, setData] = useState(null)  // Error!
}

// ❌ Calling within a class component Hook
class MyClass extends React.Component {
  render() {
    const [count, setCount] = useState(0)  // Error!
  }
}

// ✅ Custom Hook Called in
function useCustomHook() {
  const [data, setData] = useState(null)  // ✅ Correct
}
▶ Try it Yourself

(3) Why are these two rules so important?

100%
graph TB
    A[Component Rendering] --> B[Hook Linked List]
    B --> C[Hook 1: useState<br/>Linked List Nodes 1]
    B --> D[Hook 2: useEffect<br/>Linked List Nodes 2]
    B --> E[Hook 3: useRef<br/>Linked List Nodes 3]
    D -->|Conditional rendering causes skipping| F[Linked List Misalignment!]
    F --> G[Hook 2 What I read was Hook 3 data]
    G --> H[Bug! Data Chaos]
    
    style F fill:#ff4d4f,color:#fff
    style H fill:#ff4d4f,color:#fff

React relies on the order in which Hooks are called to synchronize state. If the order of calls differs with each render, the state becomes out of sync.



4. Pitfall 1: The Stale Closure Pitfall

The most common Hooks bug.

JSX
function Counter() {
  const [count, setCount] = useState(0)

  // ❌ Closure Pitfalls
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count)  // Always 0!
      setCount(count + 1)  // Set permanently to 1
    }, 1000)
    return () => clearInterval(timer)
  }, [])  // count is "locked" to first render

  // ✅ Fix 1:Functional Updates
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prev => prev + 1)  // prev Always the latest value
    }, 1000)
    return () => clearInterval(timer)
  }, [])

  // ✅ Fix 2:Add Dependencies
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(timer)
  }, [count])  // count Restart the timer when a change occurs
}
▶ Try it Yourself

When it occurs: A value that was not declared in the dependencies array was read in useEffect, useCallback, or useMemo.

Closure Pitfall Scenarios Symptoms Solutions
Reading state in setInterval The value in the timer never changes Functional updates setState(prev => ...)
Reading state in useCallback Using an old value in the callback Adding state to the dependency array or using a ref
Event handler execution is delayed The state value in an asynchronous callback is outdated Use useRef to store the latest value
Missing useEffect dependencies Variables used in the effect are not updated Complete the dependency array
Set race condition after an asynchronous request Old request overwrites new results during fast switching Use the ignore flag or AbortController

Troubleshooting Tip: All variables read in callbacks must be declared in the dependencies array. Alternatively, use functional updates.



5. Pitfall 2: useEffect Infinite Loop

(1) Reason 1: No array of dependencies

JSX
// ❌ Infinite Loop!
useEffect(() => {
  fetch('/api/data').then(setData)
})  // No array dependencies → Executed on every render → setData Trigger Rendering → Infinite Loop

// ✅ Empty array of dependencies
useEffect(() => {
  fetch('/api/data').then(setData)
}, [])
▶ Try it Yourself

(2) Reason 2: Reference types in the dependency array

JSX
function App() {
  const [count, setCount] = useState(0)

  // ❌ Infinite Loop!
  useEffect(() => {
    console.log('useEffect Execute')
  }, [{ count }])  // Create a new object on every render {} → Differences in Citation → Infinite Execution

  // ✅ Correct:Using Primitive Types
  useEffect(() => {
    console.log('useEffect Execute')
  }, [count])  // Basic Types,If the value remains unchanged, the action is not performed.
}
▶ Try it Yourself

(3) Reason 3: Modifying dependencies inside useEffect

JSX
const [items, setItems] = useState([1, 2, 3])

// ❌ Infinite Loop!
useEffect(() => {
  setItems([...items, 4])  // Edit items → Render Again → useEffect Execute again...
}, [items])

// ✅ Correct:Why is this change necessary? items?Generally, it is not necessary.
// If it's initialization,use  useRef Flag indicating whether initialization has been completed
const initialized = useRef(false)
useEffect(() => {
  if (!initialized.current) {
    setItems([...items, 4])
    initialized.current = true
  }
}, [items])
▶ Try it Yourself

(4) Reason 4: Functions as Dependencies

Causes of Infinite Loops Incorrect Code Correct Code
No array dependencies useEffect(fn) useEffect(fn, [])
Reference Types useEffect(fn, [{ count }]) useEffect(fn, [count])
Modify dependencies in effect useEffect(() => setItems(...), [items]) Mark initialization with useRef
Functions as Dependencies useEffect(fn, [handleClick]) useCallback Stable References
Object as a dependency useEffect(fn, [config]) Extract a primitive type value or use useMemo
JSX
// ❌ Each render creates a new function reference
function App() {
  const handleClick = () => { /* ... */ }

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick A new reference every time → Infinite Execution

  // ✅ use  useCallback Stable Citation
  const handleClick = useCallback(() => { /* ... */ }, [])

  useEffect(() => {
    fetch('/api').then(setData)
  }, [handleClick])  // handleClick Citation unchanged,Will not be executed again
}
▶ Try it Yourself

6. Pitfall 3: Asynchronous Updates with useState

JSX
function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    // You thought it would +3,The result was only +1
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)  // All three times count + 1, but count = 0
    console.log(count)   // Output 0,No 3
  }

  // ✅ Functional Updates
  function handleCorrectClick() {
    setCount(prev => prev + 1)  // prev = 0 → 1
    setCount(prev => prev + 1)  // prev = 1 → 2
    setCount(prev => prev + 1)  // prev = 2 → 3
    // count Or 0(The rendered values for this pass),But the next time it's rendered, it will be 3
  }
}
▶ Try it Yourself

Key Concept: Updates to useState are asynchronous and batch-processed. During a single event handling cycle, multiple setCount instances are combined into a single render. Functional updates are used to ensure that every update is based on the latest values.



7. Pitfall 4: Using async in useEffect

JSX
// ❌ useEffect Cannot pass an async function directly
useEffect(async () => {
  const data = await fetch('/api/data')
  setData(await data.json())
}, [])
// Error! async returns Promise, useEffect expects return value of cleanup function (or undefined)

// ✅ Correct: Define async function internally
useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/data')
    const data = await res.json()
    setData(data)
  }
  fetchData()  // Call async function internally
}, [])
▶ Try it Yourself

8. Complete Example: 10 Common Bug Diagnosis Tables

JSX
// ============================================
// Example:10 A common Hooks Bug + Remediation Plan
// ============================================

// Bug 1:Conditions Hook
function Bug1({ show }) {
  // if (show) const [x] = useState(0)  // ❌
  const [x] = useState(0)  // ✅ Move to the top
}

// Bug 2:Closure Pitfalls
function Bug2() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    const id = setInterval(() => {
      // setCount(count + 1)  // ❌ Closure
      setCount(prev => prev + 1)  // ✅
    }, 1000)
    return () => clearInterval(id)
  }, [])
}

// Bug 3:Infinite Loop(Dependency on Reference Types)
function Bug3() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api').then(res => res.json()).then(setData)
  }, [])  // ✅ Empty array
  // }, [data])  // ❌ data The change triggered another one useEffect
}

// Bug 4:Forgot to clean up
function Bug4() {
  useEffect(() => {
    const id = setInterval(() => console.log('tick'), 1000)
    return () => clearInterval(id)  // ✅ Must be cleaned up
  }, [])
}

// Bug 5:Edit directly state
function Bug5() {
  const [user, setUser] = useState({ name: 'Alice', age: 28 })
  function updateAge() {
    // user.age = 29  // ❌ Edit directly
    setUser({ ...user, age: 29 })  // ✅ Create a New Object
  }
}

// Bug 6:Not needed useMemo
function Bug6({ count }) {
  // const doubled = useMemo(() => count * 2, [count])  // ❌ Unnecessary
  const doubled = count * 2  // ✅ Simple calculations are not required useMemo
}

// Bug 7:effect Read from props But do not declare dependencies
function Bug7({ userId }) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch('/api/users/' + userId).then(res => res.json()).then(setUser)
  }, [userId])  // ✅ userId Dependencies must be added
  // }, [])  // ❌ userId Changes will not trigger a new request
}

// Bug 8:useMemo Return Function
function Bug8() {
  // const fn = useMemo(() => { return () => console.log('hi') }, [])  // ❌ Should use useCallback
  const fn = useCallback(() => console.log('hi'), [])  // ✅
}

// Bug 9:in  render Side effects caused by the drug
function Bug9() {
  const [data, setData] = useState(null)
  // fetch('/api/data').then(setData)  // ❌ A request is made for every render
  useEffect(() => { fetch('/api/data').then(res => res.json()).then(setData) }, [])  // ✅
}

// Bug 10:State dependency not fully declared
function Bug10({ items }) {
  const [filter, setFilter] = useState('')
  // const filtered = items.filter(i => i.includes(filter))  // ✅ in  render Calculate directly in the middle
  const filtered = useMemo(() => items.filter(i => i.includes(filter)), [items, filter])
  // useMemo The correct dependencies must be in place.
}


9. Debugging Tips

(1) React DevTools Profiler

JSX
// in  React DevTools 's  Profiler In the tab:
// 1. Click the Record button
// 2. Triggering Actions in the App
// 3. Stop Recording
// 4. View the flame diagram:Each column is a component,Rendering Time by Height
// I see components that shouldn't be re-rendered being rendered → Inspection useCallback / React.memo
▶ Try it Yourself

(2) why-did-you-render

BASH
npm install @welldone-software/why-did-you-render
JSX
// src/wdyr.js
import React from 'react'

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render')
  whyDidYouRender(React, {
    trackAllPureComponents: true,
  })
}
// The console will print:Why did this component re-render??Because which one Props It's changed?
▶ Try it Yourself

(3) Debugging with useWhyDidYouUpdate

JSX
function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef(props)

  useEffect(() => {
    const allKeys = Object.keys({ ...previousProps.current, ...props })
    const changes = {}

    allKeys.forEach(key => {
      if (previousProps.current[key] !== props[key]) {
        changes[key] = {
          from: previousProps.current[key],
          to: props[key]
        }
      }
    })

    if (Object.keys(changes).length > 0) {
      console.log(`[why-did-you-update] ${name}:`, changes)
    }

    previousProps.current = props
  })
}

// Usage
function MyComponent(props) {
  useWhyDidYouUpdate('MyComponent', props)
  return <div>{props.name}</div>
}
▶ Try it Yourself

▶ Example 2: Using a Custom Hook to Avoid Closure Pitfalls — useStableCallback

Output:

TEXT 📖 Display only
State: timeLeft (setter: setTimeLeft). useEffect manages side effects. Interval-based updates
JSX
// ============================================
// Example:useStableCallback——The Ultimate Solution to Closure Pitfalls
// Features:A callback function that always returns a reference to a stable value,However, the latest logic is executed when the function is called.
// ============================================

import { useRef, useCallback, useState, useEffect } from 'react'

// ---- Custom Hook:A Steady Pullback ----
function useStableCallback(callback) {
  // use  ref Store the latest callback
  const callbackRef = useRef(callback)

  // Update after each render ref Callbacks in
  useEffect(() => {
    callbackRef.current = callback
  })

  // Return a function that never changes
  return useCallback((...args) => {
    // Execute the latest version of the callback when it is called
    return callbackRef.current(...args)
  }, [])
}

// ---- Usage useStableCallback Avoiding Closure Pitfalls ----
function TimerWithStableCallback() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  // This callback function depends on count(Each render is a new function)
  function onTick() {
    console.log('Current Count:', count)
    // If you use it directly here count,This can lead to a closure trap
    // but useStableCallback ensures latest version is used.
  }

  // ✅ use  useStableCallback Package,Citations are always reliable
  const stableOnTick = useStableCallback(onTick)

  useEffect(() => {
    if (!isRunning) return

    const timer = setInterval(() => {
      // stableOnTick The reference will not change,However, the latest version is executed each time it is called. onTick
      stableOnTick()
      setCount(prev => prev + 1)
    }, 1000)

    return () => clearInterval(timer)
  }, [isRunning, stableOnTick])  // stableOnTick Forever Unchanging,It will not cause an infinite loop

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center' }}>
      <h2>⏱️ Solutions to Closure Pitfalls</h2>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0' }}>{count}</p>
      <button
        onClick={() => setIsRunning(!isRunning)}
        style={{
          padding: '10px 24px',
          backgroundColor: isRunning ? '#ff4d4f' : '#52c41a',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '16px'
        }}
      >
        {isRunning ? '⏸ Pause' : '▶ Start'}
      </button>
      <p style={{ marginTop: '12px', color: '#666', fontSize: '13px' }}>
        Open the console to view the logs——useStableCallback Ensure that the timer does not get stuck due to closures
      </p>
    </div>
  )
}

// ---- Comparison:Do not use useStableCallback version ----
function TimerBroken() {
  const [count, setCount] = useState(0)
  const [isRunning, setIsRunning] = useState(false)

  useEffect(() => {
    if (!isRunning) return
    // ❌ Use as is setInterval,count Locked in a closure
    const timer = setInterval(() => {
      setCount(count + 1)  // Always 0 + 1 = 1
    }, 1000)
    return () => clearInterval(timer)
  }, [isRunning])  // Missing count Dependency

  // Remediation Plan:Update Using a Function
  // setCount(prev => prev + 1)  // ✅ Not necessary count Dependency

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', textAlign: 'center', marginTop: '40px' }}>
      <h3>❌ The version with the issue(For comparison)</h3>
      <p style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0', color: '#ff4d4f' }}>{count}</p>
      <button onClick={() => setIsRunning(!isRunning)}
        style={{ padding: '10px 24px', backgroundColor: '#d9d9d9', color: '#333', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        Start(It gets stuck 1)
      </button>
    </div>
  )
}

Output:

TEXT 📖 Display only
❌ Hooks inside conditions cause bugs. ✅ Always call hooks at top level. Rule: Hooks must be called in same order every render.

▶ Example 3: Pitfalls and Fixes for Batch Updates with useState

Output:

TEXT 📖 Display only
... ('Current Count:', count)
JSX
function ShoppingCart() {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  function addItem(item) {
    setItems([...items, item])
    setTotal(items.reduce((sum, i) => sum + i.price, 0) + item.price)
  }

  function addItemFixed(item) {
    setItems(prev => [...prev, item])
    setTotal(prev => prev + item.price)
  }

  function addMultiple() {
    setItems(prev => [...prev, { id: 1, name: 'Book', price: 29 }])
    setItems(prev => [...prev, { id: 2, name: 'Pen', price: 5 }])
    setTotal(prev => prev + 34)
  }

  return (
    <div>
      <h3>Shopping Cart</h3>
      <ul>
        {items.map((item, idx) => (
          <li key={idx}>{item.name} - ${item.price}</li>
        ))}
      </ul>
      <p>Total: ${total}</p>
      <button onClick={() => addItemFixed({ id: 3, name: 'Notebook', price: 12 })}>
        Add Notebook
      </button>
      <button onClick={addMultiple}>Add Book + Pen</button>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Renders: Shopping Cart + Total: ${total}

▶ Example 4: Race Conditions in Asynchronous Requests with useEffect

Output:

TEXT 📖 Display only
Displays: "Shopping Cart". State: items (setter: setItems), total (setter: setTotal). Buttons: addItemFixed({ id: 3, name: 'Notebook', price: 12 })}>        Add Notebook, Add Book + Pen. List: {item.name} - ${item.price}
JSX
function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    let ignore = false
    setLoading(true)

    async function fetchUser() {
      try {
        const res = await fetch(`/api/users/${userId}`)
        const data = await res.json()
        if (!ignore) {
          setUser(data)
          setLoading(false)
        }
      } catch (err) {
        if (!ignore) setLoading(false)
      }
    }

    fetchUser()
    return () => { ignore = true }
  }, [userId])

  if (loading) return <p>Loading...</p>
  if (!user) return <p>No user found</p>
  return (
    <div>
      <h3>{user.name}</h3>
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
try { await fetchData(); } catch (err) { setError(err.message); } → "Failed to load data" shown. Error handled, app stays running.

▶ Example 5: Using useReducer Instead of Multiple useState Calls to Avoid State Inconsistencies

Output:

TEXT 📖 Display only
Displays: "Loading...". State: user (setter: setUser), loading (setter: setLoading). useEffect manages side effects. Async data fetching/loading states
JSX
const initialState = { count: 0, step: 1, history: [] }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {
        ...state,
        count: state.count + state.step,
        history: [...state.history, `+${state.step}`],
      }
    case 'decrement':
      return {
        ...state,
        count: state.count - state.step,
        history: [...state.history, `-${state.step}`],
      }
    case 'setStep':
      return { ...state, step: action.payload }
    case 'reset':
      return initialState
    default:
      return state
  }
}

function CounterWithHistory() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <h3>Count: {state.count}</h3>
      <label>
        Step:
        <input
          type="number"
          value={state.step}
          onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
        />
      </label>
      <button onClick={() => dispatch({ type: 'increment' })}>+{state.step}</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-{state.step}</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
      <p>History: {state.history.join(', ')}</p>
    </div>
  )
}

Output:

TEXT 📖 Display only
Renders: Count: {state.count} + History: {state.history.join(', ')}

❓ FAQ

Q Why doesn’t the page update sometimes after calling setState?
A Possible reasons: ① You modified the state object directly (without creating a new reference); ② The state value didn’t change (React uses Object.is to compare values; identical values do not trigger a render); ③ You forgot to declare the updated variable in the useEffect’s dependencies array; ④ You read an old closure value in an asynchronous callback.
Q What’s the difference between setting the dependencies array to empty and leaving it empty in useEffect?
A Leaving the dependencies array empty useEffect(fn) → Executes after every render (on mount + every update); An empty dependencies array useEffect(fn, []) → Executes only once during mounting; Non-empty dependencies useEffect(fn, [a, b]) → Executes during mounting + executes when a or b changes. The most common source of bugs: specifying an empty array but using state or props inside the effect (closure trap).

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty: ⭐): Find and fix the bug in the code below:

    JSX
    function Timer() {
      const [time, setTime] = useState(0)
      useEffect(() => {
        const id = setInterval(() => {
          setTime(time + 1)
        }, 1000)
        return () => clearInterval(id)
      }, [])
      return <p>{time}s</p>
    }
    
    ▶ Try it Yourself
  2. Advanced Problem (Difficulty ⭐⭐): Find and fix the bugs in the code below (at least 3):

    JSX
    function SearchPage() {
      const [query, setQuery] = useState('')
      const [results, setResults] = useState([])
      useEffect(async () => {
        const data = await fetch('/api/search?q=' + query)
        setResults(data)
      }, [])
      return <div>
        <input value={query} onChange={e => setQuery(e.target.value)} />
        {results.map(r => <p key={r.id}>{r.name}</p>)}
      </div>
    }
    
    ▶ Try it Yourself
  3. Challenge (Difficulty: ⭐⭐⭐): Implement a useStableCallback Hook that accepts a retorno de chamada function and always returns a reference-stable function, but executes the latest retorno de chamada when invoked (the ultimate solution to the fechamento trap).

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏