useState hook setter เขียนทับสถานะอย่างไม่ถูกต้อง


31

นี่คือปัญหา: ฉันพยายามโทร 2 ฟังก์ชั่นเมื่อคลิกปุ่ม ฟังก์ชั่นทั้งสองอัพเดทสถานะ (ฉันใช้ useState hook) ฟังก์ชันแรกอัปเดตค่า 1 อย่างถูกต้องเป็น 'ใหม่ 1' แต่หลังจาก 1 วินาที (setTimeout) ฟังก์ชั่นที่สองเริ่มทำงานและจะเปลี่ยนค่า 2 เป็น 'ใหม่ 2' แต่! ได้ตั้งค่า value1 กลับไปที่ '1' ทำไมสิ่งนี้จึงเกิดขึ้น ขอบคุณล่วงหน้า!

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState({ ...state, value1: "new 1" });
  };
  const changeValue2 = () => {
    setState({ ...state, value2: "new 2" });
  };

  return (
    <>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </>
  );
};

export default Test;

คุณสามารถเข้าสู่ระบบสถานะที่จุดเริ่มต้นของchangeValue2?
DanStarns

1
ผมขอแนะนำให้คุณแยกวัตถุออกเป็นสองแยกโทรไปหรือใช้แทนuseState useReducer
Jared Smith

ใช่ - ข้อที่สอง เพียงใช้สองสายในการใช้สถานะ ()
Esben Skov Pedersen

const [state, ...]และจากนั้นอ้างอิงถึงในตัวตั้งค่า ... มันจะใช้สถานะเดียวกันตลอดเวลา
Johannes Kuhn

แนวทางปฏิบัติที่ดีที่สุด: ใช้การuseStateโทร2 สายแยกกันหนึ่งสายสำหรับ "ตัวแปร"
Dima Tisnek

คำตอบ:


30

ยินดีต้อนรับสู่นรกปิด ปัญหานี้เกิดขึ้นเพราะเมื่อใดก็ตามที่setStateเรียกว่าstateได้รับการอ้างอิงหน่วยความจำใหม่ แต่ฟังก์ชั่นchangeValue1และchangeValue2เนื่องจากการปิดให้เก็บการstateอ้างอิงเริ่มต้นเก่า

วิธีการแก้ปัญหาเพื่อให้แน่ใจว่าsetStateจากchangeValue1และchangeValue2ได้รับสถานะล่าสุดคือการใช้โทรกลับ (มีสถานะก่อนหน้าเป็นพารามิเตอร์):

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
  };

  // ...
};

คุณสามารถค้นหาการสนทนาในวงกว้างเกี่ยวกับปัญหาการปิดนี้ที่นี่และที่นี่


การโทรกลับด้วย hook useState ดูเหมือนจะเป็นคุณสมบัติที่ไม่มีเอกสารคุณแน่ใจหรือไม่ว่าใช้งานได้
HMR

@HMR ใช่มันใช้งานได้และมีการบันทึกไว้ในหน้าอื่น ลองดูที่ส่วน "ฟังก์ชั่นอัพเดท" ที่นี่: reactjs.org/docs/hooks-reference.html ("หากสถานะใหม่ถูกคำนวณโดยใช้สถานะก่อนหน้านี้คุณสามารถส่งผ่านฟังก์ชั่นเพื่อ setState")
Alberto Trindade Tavares

1
@AlbertoTrindadeTavares ใช่ฉันกำลังดูเอกสารเช่นกันไม่พบอะไรเลย ขอบคุณมากสำหรับคำตอบ!
Bartek

1
วิธีแก้ปัญหาแรกของคุณไม่ใช่แค่ "วิธีแก้ปัญหาง่าย ๆ " แต่เป็นวิธีที่ถูกต้อง อันที่สองจะใช้งานได้ก็ต่อเมื่อส่วนประกอบได้รับการออกแบบเป็นซิงเกิลตันและแม้กระทั่งฉันไม่แน่ใจเกี่ยวกับสิ่งนั้นเพราะสถานะจะกลายเป็นวัตถุใหม่ทุกครั้ง
Scimonster

1
ขอบคุณ @AlbertoTrindadeTavares! Nice one
José Salgado

19

หน้าที่ของคุณควรเป็นเช่นนี้:

const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
};

ดังนั้นคุณต้องแน่ใจว่าคุณจะไม่พลาดคุณสมบัติที่มีอยู่ใด ๆ ในสถานะปัจจุบันโดยใช้สถานะก่อนหน้าเมื่อมีการดำเนินการ นอกจากนี้คุณหลีกเลี่ยงที่จะต้องจัดการการปิด


6

เมื่อchangeValue2มีการเรียกใช้สถานะเริ่มต้นจะถูกจัดขึ้นเพื่อให้รัฐหันกลับไปสู่สถานะ inital แล้วvalue2เขียนทรัพย์สิน

ครั้งต่อไปchangeValue2จะถูกเรียกใช้หลังจากนั้นจะมีสถานะ{value1: "1", value2: "new 2"}ดังนั้นvalue1คุณสมบัติจะถูกเขียนทับ

คุณต้องการฟังก์ชันลูกศรสำหรับsetStateพารามิเตอร์

const Test = () => {
  const [state, setState] = React.useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState(prev => ({ ...prev, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState(prev => ({ ...prev, value2: "new 2" }));
  };

  return (
    <React.Fragment>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </React.Fragment>
  );
};

ReactDOM.render(<Test />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>


3

สิ่งที่เกิดขึ้นคือทั้งสองchangeValue1และchangeValue2ดูสถานะจากการสร้างภาพที่สร้างขึ้นดังนั้นเมื่อองค์ประกอบของคุณแสดงผลเป็นครั้งแรกที่ฟังก์ชันทั้งสองนี้เห็น:

state= {
  value1: "1",
  value2: "2"
}

เมื่อคุณคลิกที่ปุ่มchangeValue1จะถูกเรียกก่อนและเปลี่ยนสถานะ{value1: "new1", value2: "2"}เป็นที่คาดไว้

ตอนนี้หลังจาก 1 วินาทีchangeValue2ถูกเรียกใช้ แต่ฟังก์ชั่นนี้ยังคงเห็นสถานะเริ่มต้น ( {value1; "1", value2: "2"}) ดังนั้นเมื่อฟังก์ชั่นนี้ปรับปรุงสถานะด้วยวิธีนี้:

setState({ ...state, value2: "new 2" });

{value1; "1", value2: "new2"}คุณท้ายเห็น:

แหล่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.