簡體   English   中英

為什么調用一個 `setState` function 會更新一個完全獨立的 state 值?

[英]Why does calling one `setState` function update an entirely separate state value?

我遇到一個問題,其中一個setState function 正在更新兩個單獨的 state 值。

我正在嘗試制作一個小型 React 可排序數組。 該程序的行為應如下所示:

  • 在掛載上獲取數據
  • 將該數據存儲在unsortedDatadisplayedData state 鈎子中
  • 當用戶單擊“切換排序”按鈕時, displayedData中的數組被排序(使用.sort()
  • 第二次點擊“切換排序”時,它應該將displayedData設置為與unsortedData相同的值 - 恢復原始順序

但是,在第一次單擊切換排序時,它會同時對unsortedDatadisplayedData進行排序,這意味着我丟失了原始數據順序。 我知道我可以存儲他們的訂單,但我想知道為什么這些 state 值看起來是耦合的。

Stackblitz 工作代碼在這里
GIF 在這里顯示問題

我找不到發生這種情況的地方。 我看不到任何對象/數組引用正在進行(我正在傳播新對象/數組)。

代碼在這里:

const Test = () => {
  const [unsortedData, setUnsortedData] = useState([])
  const [displayedData, setDisplayedData] = useState([])
  const [isSorted, setisSorted] = useState(false)

  const handleSorting = () => setisSorted(!isSorted)

  useEffect(() => {
    if (isSorted === true) setDisplayedData([...unsortedData.sort()]) // sort data
    if (isSorted === false) setDisplayedData([...unsortedData]) // restore original data order
  }, [isSorted, unsortedData])

  useEffect(() => {
    const mockData = [3, 9, 6]
    setUnsortedData([...mockData]) // store original data order in "unsortedData"
    setDisplayedData([...mockData])
  }, [])

  return (
    <div>
      {displayedData.map(item => item)}
      <br />
      <button onClick={() => handleSorting()}>Toggle sorting</button>
    </div>
  )
}

.sort function 改變數組,所以當你這樣做時:

setDisplayedData([...unsortedData.sort()])

你正在改變 unsortedData,然后復制它。 由於您改變了原始數組,因此該更改可以在組件重新呈現時顯示在屏幕上。

因此,最低限度的解決方法是先復制,然后再排序:

setDisplayedData([...unsortedData].sort())

另外,為什么我需要 useEffect? 為什么我不能將 useEffect 的內容(其中包含 if 語句)移動到 handleSorting function 中?

將它移動到 handleSorting 應該是可能的,但實際上我想提出另一種選擇:只有兩種狀態,未排序的數據和是否排序的 boolean。 然后顯示的數據是基於這兩者的計算值。

const [unsortedData, setUnsortedData] = useState([3, 9, 6]) // initialize with mock data
const [isSorted, setisSorted] = useState(false)

const displayedData = useMemo(() => {
  if (isSorted) {
    return [...unsortedData].sort();
  } else {
    return unsortedData
  }
}, [unsortedData, isSorted]);

const handleSorting = () => setisSorted(!isSorted)

// No use useEffect to sort the data
// Also no useEffect for the mock data, since i did that when initializing the state

這種方法的好處是

  1. 您不需要進行雙重渲染。 原版設置isSorted,渲染,然后設置顯示的數據,再次渲染
  2. 不可能有不匹配的狀態。 例如,在第一次和第二次渲染之間,isSorted 為真,但顯示的數據實際上尚未排序。 但即使沒有雙重渲染情況,擁有多個狀態也需要您保持警惕,每次更新 unsortedData 或 isSorted 時,您還記得更新 displayedData。 如果它是計算值而不是獨立的 state,那只會自動發生。

如果您只想在用戶按下按鈕時向用戶顯示已排序的數組,您可以使用此代碼語句(最初是從vue3 todo list 復制的)

const filters = {
  none: (data) => [...data],
  sorted: (data) => [...data].sort((a, b) => a - b),
}

const Test = () => {
  const [unsortedData, setUnsortedData] = useState([])
  const [currentFilter, setCurrentFilter] = useState('none')

  useEffect(() => {
    const mockData = [3, 9, 6]
    setUnsortedData([...mockData])
  }, [])

  return (
    <div>
      <ul>
         {/* All magic is going here! */}
         {filters[currentFilter](unsortedData).map(item => <li key={item}>{item}</li>)}
      </ul>
      <br />
      <button
        onClick={() =>
            setCurrentFilter(currentFilter === 'none' ? 'sorted' : 'none')}>
          Toggle sorting
       </button>
    </div>
  )
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM