setState hook setter ghi đè không chính xác trạng thái


31

Đây là vấn đề: Tôi đang cố gắng gọi 2 chức năng trên một nút bấm. Cả hai chức năng đều cập nhật trạng thái (Tôi đang sử dụng hook useState). Hàm đầu tiên cập nhật giá trị1 chính xác thành 'new 1', nhưng sau 1 giây (setTimeout), hàm thứ hai sẽ kích hoạt và nó thay đổi giá trị 2 thành 'new 2' NHƯNG! Nó đặt giá trị1 trở lại '1'. Tại sao chuyện này đang xảy ra? Cảm ơn trước!

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;

bạn có thể đăng nhập trạng thái khi bắt đầu changeValue2?
DanStarns

1
Tôi đặc biệt khuyên bạn nên chia đối tượng thành hai cuộc gọi riêng biệt useStatehoặc thay vào đó sử dụng useReducer.
Jared Smith

Vâng - thứ hai này. Chỉ cần sử dụng hai cuộc gọi để sử dụngState ()
Esben Skov Pedersen

const [state, ...], và sau đó đề cập đến nó trong setter ... Nó sẽ sử dụng cùng một trạng thái mọi lúc.
Julian Kuhn

Hành động tốt nhất: sử dụng 2 useStatecuộc gọi riêng biệt , một cuộc gọi cho mỗi "biến".
Dima Tisnek

Câu trả lời:


30

Chào mừng đến với địa ngục đóng cửa . Vấn đề này xảy ra bởi vì bất cứ khi nào setStateđược gọi, statesẽ có một tham chiếu bộ nhớ mới, nhưng các hàm changeValue1changeValue2, vì đóng, giữ nguyên statetham chiếu ban đầu cũ .

Một giải pháp để đảm bảo trạng thái setStatetừ changeValue1changeValue2nhận trạng thái mới nhất là sử dụng hàm gọi lại (có trạng thái trước đó làm tham số):

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

  // ...
};

Bạn có thể tìm thấy một cuộc thảo luận rộng hơn về vấn đề đóng cửa này ở đâyđây .


Một cuộc gọi lại với hook useState dường như là một tính năng không có giấy tờ , bạn có chắc là nó hoạt động không?
HMR

@HMR Có, nó hoạt động và nó được ghi lại trên một trang khác. Hãy xem phần "Cập nhật chức năng" tại đây: Reacjs.org/docs/hooks-reference.html ("Nếu trạng thái mới được tính bằng trạng thái trước đó, bạn có thể chuyển một hàm sang setState")
Alberto Trindade Tavares

1
@AlbertoTrindadeTavares Vâng, tôi đã xem các tài liệu cũng như không thể tìm thấy bất cứ điều gì. Cảm ơn rất nhiều cho câu trả lời!
Bartek

1
Giải pháp đầu tiên của bạn không chỉ là "giải pháp dễ dàng", đó là phương pháp chính xác. Cái thứ hai sẽ chỉ hoạt động nếu thành phần được thiết kế dưới dạng đơn, và thậm chí sau đó tôi không chắc về điều đó bởi vì trạng thái trở thành một đối tượng mới mỗi lần.
Scimonster

1
Cảm ơn bạn @AlbertoTrindadeTavares! Nice one
Jose Salgado

19

Các chức năng của bạn nên như thế này:

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

Do đó, bạn đảm bảo rằng bạn không thiếu bất kỳ tài sản hiện có nào ở trạng thái hiện tại bằng cách sử dụng trạng thái trước đó khi hành động được thực hiện. Ngoài ra, do đó bạn tránh phải quản lý đóng cửa.


6

Khi changeValue2được gọi, trạng thái ban đầu được giữ để trạng thái quay trở lại trạng thái ban đầu và sau đó value2tài sản được viết.

Lần sau changeValue2được gọi sau đó, nó giữ trạng thái {value1: "1", value2: "new 2"}, do đó value1tài sản bị ghi đè.

Bạn cần một hàm mũi tên cho setStatetham số.

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

Điều đang xảy ra là cả hai changeValue1changeValue2nhìn thấy trạng thái từ kết xuất mà chúng được tạo , vì vậy khi thành phần của bạn kết xuất lần đầu tiên, hai hàm này sẽ thấy:

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

Khi bạn nhấp vào nút, changeValue1được gọi đầu tiên và thay đổi trạng thái {value1: "new1", value2: "2"}như mong đợi.

Bây giờ, sau 1 giây, changeValue2được gọi, nhưng hàm này vẫn thấy trạng thái ban đầu ( {value1; "1", value2: "2"}), vì vậy khi hàm này cập nhật trạng thái theo cách này:

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

bạn cuối cùng nhìn thấy : {value1; "1", value2: "new2"}.

nguồn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.