useEffect - Ngăn chặn vòng lặp vô hạn khi cập nhật trạng thái


9

Tôi muốn người dùng có thể sắp xếp danh sách các mục việc cần làm. Khi người dùng chọn một mục từ danh sách thả xuống, nó sẽ đặt mục sortKeyđó sẽ tạo một phiên bản mới setSortedTodosvà lần lượt kích hoạt useEffectvà gọi setSortedTodos.

Ví dụ dưới đây hoạt động chính xác như tôi muốn, tuy nhiên eslint đang nhắc tôi thêm todosvào useEffectmảng phụ thuộc và nếu tôi làm điều đó sẽ gây ra một vòng lặp vô hạn (như bạn mong đợi).

const [todos, setTodos] = useState([]);
const [sortKey, setSortKey] = useState('title');

const setSortedTodos = useCallback((data) => {
  const cloned = data.slice(0);

  const sorted = cloned.sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });

  setTodos(sorted);
}, [sortKey]);

useEffect(() => {
    setSortedTodos(todos);
}, [setSortedTodos]);

Ví dụ trực tiếp:

Tôi nghĩ rằng phải có một cách tốt hơn để làm điều này để giữ cho eslint hạnh phúc.


1
Chỉ cần một lưu ý phụ: Cuộc sortgọi lại có thể chỉ là: return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());điều này cũng có lợi thế khi thực hiện so sánh miền địa phương nếu môi trường có thông tin miền địa phương hợp lý. Nếu bạn thích, bạn cũng có thể ném phá hủy nó: pastebin.com/7X4M1XTH
TJ Crowder

Lỗi gì đang eslintném?
Luze

Bạn có thể cập nhật câu hỏi để cung cấp một ví dụ có thể lặp lại tối thiểu có thể chạy được của vấn đề bằng cách sử dụng Stack Snippets ( [<>]nút thanh công cụ) không? Stack Snippets hỗ trợ React, bao gồm cả JSX; Dưới đây là cách thực hiện . Bằng cách đó, mọi người có thể kiểm tra xem các giải pháp được đề xuất của họ không có vấn đề vòng lặp vô hạn ...
TJ Crowder

Đó là một cách tiếp cận thực sự thú vị và là một vấn đề thực sự thú vị. Như bạn nói, bạn có thể hiểu tại sao ESLint nghĩ rằng bạn cần thêm todosvào mảng phụ thuộc useEffectvà bạn có thể thấy lý do tại sao bạn không nên. :-)
TJ Crowder

Tôi đã thêm ví dụ trực tiếp cho bạn. Thực sự muốn xem câu trả lời này.
TJ Crowder

Câu trả lời:


8

Tôi cho rằng điều này có nghĩa là đi theo cách này không lý tưởng. Các chức năng thực sự phụ thuộc vào todos. Nếu setTodosđược gọi ở một nơi khác, hàm gọi lại phải được tính toán lại, nếu không thì nó hoạt động trên dữ liệu cũ.

Tại sao bạn lưu trữ các mảng được sắp xếp trong trạng thái? Bạn có thể sử dụng useMemođể sắp xếp các giá trị khi khóa hoặc mảng thay đổi:

const sortedTodos = useMemo(() => {
  return Array.from(todos).sort((a, b) => {
    const v1 = a[sortKey].toLowerCase();
    const v2 = b[sortKey].toLowerCase();

    if (v1 < v2) {
      return -1;
    }

    if (v1 > v2) {
      return 1;
    }

    return 0;
  });
}, [sortKey, todos]);

Sau đó tham khảo sortedTodoskhắp nơi.

Ví dụ trực tiếp:

Không cần lưu trữ các giá trị được sắp xếp ở trạng thái, vì bạn luôn có thể lấy / tính toán mảng đã sắp xếp từ mảng "cơ sở" và khóa sắp xếp. Tôi cho rằng nó cũng làm cho mã của bạn dễ hiểu hơn vì nó ít phức tạp hơn.


Oh sử dụng tốt đẹp của useMemo. Chỉ là một câu hỏi phụ, tại sao không sử dụng .localComparetrong sắp xếp?
Tikkes

2
Điều đó thực sự sẽ tốt hơn. Tôi chỉ sao chép mã của OP và không chú ý đến điều đó (không thực sự liên quan đến vấn đề).
Felix Kling

Giải pháp thực sự đơn giản, dễ hiểu.
TJ Crowder

À đúng rồi, sử dụngMemo! Quên về điều đó :)
DanV

3

Lý do cho vòng lặp vô hạn là vì todos không khớp với tham chiếu trước đó và hiệu ứng sẽ chạy lại.

Tại sao lại sử dụng hiệu ứng cho một hành động nhấp chuột? Bạn có thể chạy nó trong một chức năng như vậy:

const [todos, setTodos] = useState([]);

function sortTodos(e) {
    const sortKey = e.target.value;
    const clonedTodos = [...todos];
    const sorted = clonedTodos.sort((a, b) => {
        return a[sortKey.toLowerCase()].localeCompare(b[sortKey.toLowerCase()]);
    });

    setTodos(sorted);
}

và trên thả xuống của bạn làm một onChange.

    <select onChange="sortTodos"> ......

Lưu ý về sự phụ thuộc bằng cách này, ESLint là đúng! Todos của bạn, trong trường hợp được mô tả ở trên, là một phụ thuộc và nên có trong danh sách. Cách tiếp cận về việc lựa chọn một mặt hàng là sai, và do đó vấn đề của bạn.


2
"sort sẽ trả về một thể hiện mới của một mảng" Không phải là phương thức sắp xếp tích hợp. Nó sắp xếp các mảng tại chỗ. data.slice(0)tạo bản sao.
Felix Kling

Điều đó không đúng khi bạn thực hiện setStatevì nó sẽ không chỉnh sửa đối tượng hiện có và do đó sẽ sao chép nó vào bên trong. Từ ngữ sai trong câu trả lời của tôi, đúng. Tôi sẽ chỉnh sửa cái này.
Tikkes

setStatekhông sao chép dữ liệu. Tại sao bạn nghĩ rằng?
Felix Kling

1
@Tikkes - Không, setStatekhông sao chép bất cứ điều gì. Felix và OP là chính xác, bạn cần sao chép mảng trước khi sắp xếp nó.
TJ Crowder

Được rồi, xin lỗi. Có vẻ như tôi cần đọc thêm về nội bộ. Bạn thực sự phải sao chép và không thay đổi hiện có state.
Tikkes

0

Những gì bạn cần làm ở đây là sử dụng hình thức chức năng của setState:

  const [todos, setTodos] = useState(exampleToDos);
    const [sortKey, setSortKey] = useState('title');

    const setSortedTodos = useCallback((data) => {

      setTodos(currTodos => {
        return currTodos.sort((a, b) => {
          const v1 = a[sortKey].toLowerCase();
          const v2 = b[sortKey].toLowerCase();

          if (v1 < v2) {
            return -1;
          }

          if (v1 > v2) {
            return 1;
          }

          return 0;
        });
      })

    }, [sortKey]);

    useEffect(() => {
        setSortedTodos(todos);
    }, [setSortedTodos, todos]);

Hộp mã làm việc

Ngay cả khi bạn đang sao chép trạng thái để không làm thay đổi trạng thái ban đầu, vẫn không đảm bảo rằng bạn sẽ nhận được giá trị mới nhất, do cài đặt trạng thái không đồng bộ. Ngoài ra, hầu hết các phương thức sẽ trả về một bản sao nông, vì vậy cuối cùng bạn có thể thay đổi trạng thái ban đầu.

Sử dụng chức năng setStateđảm bảo rằng bạn nhận được giá trị mới nhất của trạng thái và không làm thay đổi giá trị trạng thái ban đầu.


"và không làm thay đổi giá trị trạng thái ban đầu."Tôi không nghĩ React chuyển một bản sao cho cuộc gọi lại. .sortthay đổi mảng tại chỗ, do đó bạn vẫn phải tự sao chép nó.
Felix Kling

Hmm, điểm tốt, tôi thực sự không thể tìm thấy bất kỳ xác nhận nào rằng nó vượt qua bản sao của tiểu bang, mặc dù tôi nhớ đã đọc smth như thế trước đó.
Rõ ràng

Tìm thấy nó ở đây: Reacjs.org/docs/ . 'chúng ta có thể sử dụng mẫu cập nhật chức năng của setState. Nó cho phép chúng tôi chỉ định cách nhà nước cần thay đổi mà không cần tham khảo trạng thái hiện tại '
Clarity

Tôi không đọc nó như là một bản sao. Điều đó chỉ có nghĩa là bạn không phải cung cấp trạng thái hiện tại là phụ thuộc "bên ngoài".
Felix Kling
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.