useEffect - ป้องกันการวนซ้ำไม่สิ้นสุดเมื่ออัพเดตสถานะ


9

ฉันต้องการให้ผู้ใช้สามารถเรียงลำดับรายการสิ่งที่ต้องทำ เมื่อผู้ใช้เลือกรายการจากเมนูแบบเลื่อนมันจะตั้งค่าsortKeyซึ่งจะสร้างรุ่นใหม่setSortedTodosและในทริกเกอร์เปิดและโทรuseEffectsetSortedTodos

ด้านล่างนี้เป็นตัวอย่างการทำงานว่าฉันต้องการ แต่ eslint จะแจ้งให้ฉันเพื่อเพิ่มtodosไปยังuseEffectอาร์เรย์ dependancy และถ้าผมทำมันทำให้เกิดห่วงอนันต์ (ตามที่คุณคาดว่าจะได้)

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]);

ตัวอย่างสด:

ฉันคิดว่าจะต้องมีวิธีที่ดีกว่าในการทำสิ่งนี้ซึ่งทำให้ eslint มีความสุข


1
แค่หมายเหตุด้าน: การsortโทรกลับสามารถทำได้เพียง: return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());ซึ่งมีข้อดีของการทำโลแคลเปรียบเทียบหากสภาพแวดล้อมมีข้อมูลโลแคลที่สมเหตุสมผล ถ้าคุณชอบคุณสามารถทำลายมันได้เช่นกัน: pastebin.com/7X4M1XTH
TJ Crowder

มีข้อผิดพลาดอะไรในeslintการขว้างปา?
Luze

คุณสามารถอัปเดตคำถามเพื่อให้ตัวอย่างที่สามารถทำซ้ำได้น้อยที่สุดที่เรียกใช้ของปัญหาโดยใช้กองขนาดเล็ก ( [<>]ปุ่มแถบเครื่องมือ)? Stack Snippets รองรับ React รวมถึง JSX; นี่เป็นวิธีการอย่างใดอย่างหนึ่ง ด้วยวิธีนี้ผู้คนสามารถตรวจสอบได้ว่าวิธีแก้ปัญหาที่เสนอของพวกเขาไม่มีปัญหาวนวนไม่สิ้นสุด ...
TJ Crowder

นั่นเป็นวิธีที่น่าสนใจและเป็นปัญหาที่น่าสนใจมาก อย่างที่คุณพูดคุณสามารถเข้าใจได้ว่าทำไม ESLint คิดว่าคุณต้องเพิ่มtodosในอาร์เรย์ที่พึ่งพาuseEffectและคุณสามารถดูว่าทำไมคุณไม่ควร :-)
TJ Crowder

ฉันเพิ่มตัวอย่างสดให้คุณ ต้องการเห็นคำตอบนี้จริงๆ
TJ Crowder

คำตอบ:


8

ฉันขอยืนยันว่านี่หมายความว่าการดำเนินการในลักษณะนี้จะไม่เหมาะสม todosฟังก์ชั่นเป็นที่แน่นอนขึ้นอยู่กับ หากsetTodosเรียกที่อื่นฟังก์ชันการเรียกกลับต้องได้รับการคำนวณใหม่มิฉะนั้นจะทำงานกับข้อมูลเก่า

เหตุใดคุณจึงจัดเก็บอาร์เรย์ที่เรียงลำดับอยู่ในสถานะอย่างไรก็ตาม คุณสามารถใช้useMemoเพื่อเรียงลำดับค่าเมื่อคีย์หรืออาร์เรย์เปลี่ยนแปลง:

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]);

จากนั้นอ้างอิงได้sortedTodosทุกที่

ตัวอย่างสด:

ไม่จำเป็นต้องเก็บค่าที่เรียงไว้ในสถานะเนื่องจากคุณสามารถรับ / คำนวณอาร์เรย์ที่เรียงลำดับจากอาร์เรย์ "ฐาน" และคีย์เรียงลำดับได้เสมอ ฉันเถียงว่ามันจะทำให้โค้ดของคุณเข้าใจง่ายขึ้นเพราะมันซับซ้อนน้อยกว่า


useMemoโอ้ใช้งานที่ดีของ เพียงคำถามด้านข้างทำไมไม่ใช้.localCompareในการจัดเรียง?
Tikkes

2
นั่นจะดีกว่าแน่นอน ฉันเพิ่งคัดลอกรหัสของ OP และไม่ได้ใส่ใจกับสิ่งนั้น (ไม่เกี่ยวข้องกับปัญหา)
เฟลิกซ์คลิง

แก้ปัญหาได้ง่ายและเข้าใจง่าย
TJ Crowder

อ่าใช่ใช้ Memo! ลืมเรื่องนั้น :)
DanV

3

สาเหตุของการวนซ้ำไม่สิ้นสุดคือเนื่องจาก to-do ไม่ตรงกับการอ้างอิงก่อนหน้านี้และเอฟเฟกต์จะรันใหม่

เหตุใดจึงใช้เอฟเฟกต์สำหรับการกระทำคลิก คุณสามารถเรียกใช้มันในฟังก์ชันดังนี้:

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

onChangeและเลื่อนลงของคุณทำ

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

หมายเหตุเกี่ยวกับการพึ่งพาโดยวิธี ESLint ถูกต้อง! สิ่งที่ต้องทำในกรณีที่อธิบายไว้ข้างต้นเป็นการอ้างอิงและควรอยู่ในรายการ แนวทางในการเลือกรายการนั้นผิดและเป็นปัญหาของคุณ


2
"sort จะส่งคืนอินสแตนซ์ใหม่ของอาร์เรย์"ไม่ใช่วิธีการเรียงลำดับในตัว มันเรียงลำดับอาร์เรย์ในสถานที่ data.slice(0)สร้างสำเนา
เฟลิกซ์คลิง

นั่นไม่เป็นความจริงเมื่อคุณทำเช่นsetStateนั้นเนื่องจากมันจะไม่แก้ไขวัตถุที่มีอยู่และจะทำการโคลนภายใน ถ้อยคำผิดในคำตอบของฉันจริง ฉันจะแก้ไขสิ่งนี้
Tikkes

setStateไม่โคลนข้อมูล ทำไมคุณคิดอย่างนั้น
เฟลิกซ์คลิง

1
@Tikkes - setStateไม่ไม่ลอกอะไรเลย Felix และ OP นั้นถูกต้องคุณต้องคัดลอกอาร์เรย์ก่อนที่จะเรียงลำดับ
TJ Crowder

โอเคใช่ขอโทษ ฉันต้องการอ่านเพิ่มเติมเกี่ยวกับภายในดูเหมือนว่า stateแน่นอนคุณต้องคัดลอกและไม่มีการเปลี่ยนแปลงที่มีอยู่
Tikkes

0

สิ่งที่คุณต้องทำที่นี่คือการใช้รูปแบบการทำงานของ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]);

รหัสการทำงานและกล่องจดหมาย

แม้ว่าคุณกำลังคัดลอกสถานะเพื่อไม่ให้กลายพันธุ์เดิม แต่ก็ยังไม่รับประกันว่าคุณจะได้รับค่าล่าสุดเนื่องจากการตั้งค่าสถานะเป็นแบบซิงค์ บวกกับวิธีการส่วนใหญ่จะส่งคืนสำเนาตื้น ๆ ดังนั้นคุณอาจจะกลายพันธุ์ในสถานะเดิมอย่างไรก็ตาม

การใช้ฟังก์ชั่นsetStateช่วยให้แน่ใจว่าคุณได้รับค่าล่าสุดของสถานะและไม่เปลี่ยนแปลงค่าสถานะดั้งเดิม


"และไม่เปลี่ยนแปลงค่าสถานะดั้งเดิม" ฉันไม่คิดว่า React ส่งสำเนาไปยังการโทรกลับ .sortเปลี่ยนอาร์เรย์ในสถานที่ดังนั้นคุณยังต้องคัดลอกด้วยตัวเอง
เฟลิกซ์

อืมดีมากฉันไม่พบการยืนยันใด ๆ เลยว่ามันผ่านสำเนาของรัฐแล้วฉันจำได้ว่าเคยอ่านแบบนี้มาก่อน
ความชัดเจน

พบได้ที่นี่: reactjs.org/docs/... 'เราสามารถใช้ setState รูปแบบการปรับปรุงการทำงาน มันช่วยให้เราสามารถระบุวิธีที่รัฐต้องการเปลี่ยนแปลงโดยไม่ต้องอ้างอิงสถานะปัจจุบัน '
ความชัดเจน

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