Apropos Verschlussfalle und Lösung der React-Hooks-Funktionskomponenten

Ich glaube, dass Freunde, die React-Hooks zum Entwickeln verwenden, mehr oder weniger auf einige "seltsame" "Closure Trap"-Szenarien gestoßen sein müssen (eigentlich ist es logisch). folgendermaßen:

const testDemo = () => {
    const [count, setCount] = useState(1)
    useEffect(() => {
        setInterval(() => {
           console.log(count)
        }, 1000)
        //闭包陷阱
    }, [])
    const handleClick = () => {
        setCount(count+1)
    }

    return (
        <div onClick={handleClick}>
            click to add, count: {count}
        </div>
    )
    
}

Wir erwarten, dass nach dem Klicken auch die gedruckte Zählung aktualisiert werden kann, aber Tatsache ist, dass sich die gedruckte Zählung nicht jedes Mal ändert.

Was ist eine Verschlussfalle:

Um es einfach auszudrücken, Reaktionshaken führen beim Rendern eine verknüpfte Liste, um die Position und den Wert von useState und useEffect aufzuzeichnen (dies ist auch der Grund, warum state if else nicht verwenden kann, da dies dazu führen kann, dass die Reihenfolge von useEffect in der verknüpften Liste geändert wird ungeordnet sein, kann also nicht den korrekten Wert erhalten)

Jedes Mal, wenn der Status aktualisiert wird, wird die verknüpfte Liste von Anfang an neu gerendert, aber da useEffect im obigen Beispiel von keinem Status abhängt, wird es nur beim ersten Rendern ausgelöst und die Callback-Funktion in useEffect nicht ausgelöst werden, wenn das Rendering von setCount aktualisiert wird Daher ist die Zählung in setInterval darin immer noch der Wert zum Zeitpunkt der Initialisierung,
und der letzte wurde nicht erhalten.Dies ist die Abschlussfalle
 

Grund:

Wenn wir in Funktionskomponenten den von useState erstellten Wert in der Callback-Funktion verwenden, wird der Abschluss generiert. Der Abschluss wird generiert, wenn die Funktionskomponente erstellt wird, und speichert den Statuswert zum Zeitpunkt der Erstellung.

Wenn die Funktion im Hook wie useEffect(()=>(),[]) geschrieben ist, also nur während der Komponentenmontagephase ausgeführt wird , dann ist der Wert, den die darin enthaltene Funktion erhält, immer nur der Wert at zum Zeitpunkt der Initialisierung, auch wenn Sie den Wert an anderer Stelle ändern, können Sie nicht den neuesten Wert abrufen.

Lösung:

1) Am einfachsten: useRef

Da useRef jedes Mal das Objekt selbst erhält, also die Daten desselben Speicherplatzes, kann der neueste Wert abgerufen werden.

Auf die gleiche Weise können wir, wenn wir eine flache Kopie wie diese erstellen, auch den neuesten Wert erhalten (z. B. die Object.assign-Methode).

Prinzip: Verwenden Sie die Referenz des Objekts, um direkt die Daten des Objekts selbst zu erhalten

2) Clear-Rebuild: Verwenden Sie useEffect, um den generierten Abschluss zu löschen, wenn die Seite aktualisiert wird

Da die Funktion useEffect immer dann verwendet wird, wenn andere Komponenten aktualisiert werden, kann dies im Aktualisierungsmodus durch Löschen und Neuaufbau erfolgen

const [count, setCount] = useState(0)
    let myInterval = null

    const interval = () => {
        myInterval = setInterval(() => {
            console.log(count)
            if (count > 5) {
                clearInterval(myInterval)
            }
            setCount(count + 1)
        }, 1000)
    }
    useEffect(() => {
        //由于更新时清除了, 所以要重新模拟一下点击时的操作, 确保继续运行
        if (count > 0) {
            interval()
        }
        //更新时清除掉interval
        return () => clearInterval(myInterval)
    })
    return <div onClick={interval}>click count add : {count}</div>

Sie können den Parameter nicht festlegen, dann wird useEffect jedes Mal aufgerufen, wenn er aktualisiert wird, oder Sie können die zweite Parameterabhängigkeit von useEffect festlegen, die ein Parameter vom Array-Typ ist, und die Abhängigkeit wird übergeben. Nach dem Wert der Abhängigkeit ändert, wird es erneut ausgeführt useEffect, holt den neuesten Wert.
Wenn ein Timer darin geschrieben ist, ist es am besten, zu einer Funktion zurückzukehren, um den Timer zu löschen. Wenn addEventListener auf das Ereignis lauscht, geben Sie eine Funktion zurück, um das Listener-Ereignis zu löschen.

3) Wenn es sich nur um die Berechnungsoperation von Variablen handelt, verwenden Sie beim Aktualisieren die Schreibmethode der useState-Funktion

// 异常的写法
import { useState } from 'react';
 
export default function Counter() {
  const [count, setCount] = useState(0)
 
  function handle() {
    setCount(count + 1)
    // 当 setTimeout 执行时,
    // 注意:回调函数的 count 值不是 1,而是 0
    setTimeout(() => {
      setCount(count + 2)
    }, 0)
  }
 
  return (
    <div>
      <div>{count}</div>
      <button onClick={handle}>递增</button>
    </div>
  )
}


// 正常的写法
const [count, setCount] = useState(0);

const handle = () => {
    setCount(count + 1);
    setTimeout(() => {
       -  setCount(count + 2)
       +  setCount(count => count + 2) // 异步写法
   }, 0)
}

4) Definieren Sie eine Variable direkt zur Verwendung, z. B. Definieren Sie die Timer-Identifikationsvariable außerhalb der Funktionskomponente (in der globalen Umgebung)

Ein weiteres Beispiel: Wenn Sie useState nicht verwenden, um den Wert festzulegen (definieren Sie einfach eine Variable, lassen Sie count = 1; ), verwenden Sie direkt count = count + 1;, um den Wert zu ändern. Wenn Sie setCount nicht verwenden, um den Wert zu ändern, erhalten Sie jedes Mal den neuesten Zählwert, da die Komponente nicht aktualisiert wird, wenn Sie die setCount-Funktionskomponente nicht verwenden.Es ist immer die gleiche Closure, also der gleiche Wert genommen wird.

5) Wenn Sie Hooks wie useMemo oder useCallback verwenden, um einen Abschluss zu bilden, können Sie auch alle Abhängigkeiten zum Array hinzufügen, um den Update-Effekt zu erzielen

function App() {
  return <Demo1 />
}
 
function Demo1(){
  const [num1, setNum1] = useState(1)
  const [num2, setNum2] = useState(10)
 
  const text = useMemo(()=>{
    return `num1: ${num1} | num2:${num2}`
  }, [num2])  // 此处只添加num2即可,因为num2和num1是存在于同一个闭包里,所以更新时是同步的
 
  function handClick(){
    setNum1(2)
    setNum2(20)
  }
 
  return (
    <div>
      {text}
      <div><button onClick={handClick}>click!</button></div>
    </div>
  )
}

Hinweis: Und wenn mehrere Abhängigkeiten die Eigenschaften haben, sich gleichzeitig zu ändern, fügen Sie nur eine davon in das Array ein
 

Referenzartikel:

Referenz 1 : Vereinfachte Version

Quelle 2 : Ausführliche Version

Beispiel 3 : Demo Demoversion

Guess you like

Origin blog.csdn.net/BUG_CONQUEROR_LI/article/details/128232106