深入理解React useRef与数组操作:避免常见陷阱

深入理解React useRef与数组操作:避免常见陷阱

在使用react的`useref`管理非渲染数据时,对存储在其中的数组进行过滤操作是一个常见场景。本文将详细阐述为何`Array.prototype.Filter()`方法无法直接修改`useref`中存储的数组,并提供正确的更新策略,同时纠正了访问`useref`长度的常见错误,确保数据管理和逻辑判断的准确性。

理解 useRef 与 javaScript 数组的不可变性

react应用中,useRef是一个非常有用的Hook,它允许我们在组件的整个生命周期中持久化一个可变的值,而不会触发组件的重新渲染。这使得它非常适合存储不直接影响ui的数据,例如dom元素的引用、计时器ID或者本例中讨论的非渲染数据数组。

然而,当我们在useRef中存储一个数组并尝试对其进行修改时,一个常见的误解是认为像filter()这样的数组方法会原地修改原始数组。实际上,javascript中的许多数组方法,包括filter()、map()、slice()等,都是不可变的。这意味着它们不会修改调用它们的原始数组,而是返回一个包含新结果的新数组

考虑以下代码片段,它试图从useRef中存储的items.current数组中移除一个元素:

let items = useRef([]); // 假设 items.current 已经包含一个数组 // ... // 尝试过滤掉名为 'toy' 的项目 items.current.filter((item) => item.name !== toy); // 此时,items.current 并没有被修改!

这段代码的问题在于,filter()方法执行后,会返回一个不包含指定toy的新数组,但这个新数组并没有被赋值回items.current。因此,items.current仍然保持着过滤前的原始数组。

正确更新 useRef 中存储的数组

要正确地更新useRef中存储的数组,我们需要将filter()方法返回的新数组显式地赋值回items.current。

import { useNavigate } from 'react-router-dom'; import { useState, useEffect, useRef } from "react"; import supabase from "../config/supabaseClient"; import Image from "./image"; import Timer from "./timer";  const Game = () => {   let items = useRef([]); // 使用 useRef 存储非渲染数据   const [fetchError, setFetchError] = useState(null);   const [found, setFound] = useState("");   const [time, setTime] = useState(0);   const navigate = useNavigate();    useEffect(() => {     const fetchOptions = async () => {       const { data, error } = await supabase         .from('items')         .select();        if (error) {         setFetchError('Could not fetch items');         items.current = [];       }        if (data) {         items.current = data;         setFetchError(null);       }     }     fetchOptions();   }, []);    function handleAction(click, toy) {     const item = items.current.find(item => item.name === toy);      if (!item) {       setFound(`Not quite, try again!`);       return;     }      if (click.x > item.left && click.x < item.right) {       if (click.y < item.bottom && click.y > item.top) {         setFound(`Well done! You've found Sarah's ${toy}`);         // 正确的做法:将过滤后的新数组重新赋值给 items.current         items.current = items.current.filter((i) => i.name !== toy);         console.log("Updated items.current:", items.current); // 检查更新后的数组          // 检查数组长度时,也需要访问 items.current         if (items.current.Length === 0) {           console.log('winner');           navigate("/leaderboard", { state: time });         }       }     } else {       setFound(`Not quite, try again!`);       return;     }   }    return (     <>       {fetchError && (<p>{fetchError}</p>)}       <Timer time={time} setTime={setTime} />       <Image handleAction={handleAction} />       <p>{found}</p>     </>   ); }  export default Game;

在上述代码中,关键的修改是这一行:

items.current = items.current.filter((i) => i.name !== toy);

通过将filter()方法返回的新数组赋值回items.current,我们确保了useRef中存储的数据得到了正确的更新。

深入理解React useRef与数组操作:避免常见陷阱

钉钉 AI 助理

钉钉AI助理汇集了钉钉AI产品能力,帮助企业迈入智能新时代。

深入理解React useRef与数组操作:避免常见陷阱21

查看详情 深入理解React useRef与数组操作:避免常见陷阱

另一个常见陷阱:访问 useRef 的长度

除了过滤操作,另一个与useRef相关的常见错误是直接访问ref对象的length属性,而不是其current属性的length。

错误的示例:

if (items.length === 0) { // 错误!items 是一个 ref 对象,不是数组   console.log('Winner');   navigate("/leaderboard", { state: time }); }

items本身是一个RefObject,它不具备length属性(除非你把它赋值为一个带有length属性的对象,但这与我们的意图不符)。我们真正想要检查的是useRef所引用的数组的长度,因此必须通过items.current来访问。

正确的做法:

if (items.current.length === 0) { // 正确!访问 ref 引用值的长度   console.log('Winner');   navigate("/leaderboard", { state: time }); }

总结与注意事项

  1. filter() 不会原地修改数组: Array.prototype.filter()方法总是返回一个新数组,而不会修改原始数组。要更新数据,必须将新数组重新赋值。
  2. useRef 的值通过 .current 访问和修改: 无论是读取还是写入useRef所引用的值,都必须通过其.current属性进行。直接操作ref对象本身(例如items.length)通常是错误的。
  3. 何时使用 useRef vs useState: 当数据不需要触发组件重新渲染时,useRef是管理可变数据的理想选择。如果数据的变化需要反映在UI上,则应使用useState。在本例中,游戏中的隐藏物品列表不直接渲染,因此useRef是合适的。

通过理解这些核心概念和避免常见陷阱,您可以更有效地在React应用中使用useRef来管理复杂的数据结构

上一篇
下一篇
text=ZqhQzanResources