【问题标题】:Entire list re-rendering when changing a single item更改单个项目时重新渲染整个列表
【发布时间】:2020-10-30 11:27:15
【问题描述】:

我有一个产品列表,我想添加更多数据,例如价格和数量。问题是当我开始输入时我失去了输入焦点,因为整个列表正在重新呈现。我有一个简单的 Fiddle 复制那段代码:



const App = () => {

  // List of products
  const [products, setProducts] = React.useState([
  {
    title: 'My Product',
  },
  {
    title: 'My Product 2',
  }
  ]);
  
  
  // Simple debug to track changes
  React.useEffect(() => {
    console.log('PRODUCTS', products);
  }, [products]);
  
  
  const ProductForm = ({ data, index }) => {
    
    const handleProductChange = (name, value) => {
      const allProducts = [...products];
      const selectedProduct = {...allProducts[index]};
      allProducts[index] = {
        ...selectedProduct,
        [name]: value
      };
      setProducts([ ...allProducts ]);
    }
    
    return (
      <li>
        <h2>{data.title}</h2>
        <label>Price:</label>
        <input type="text" value={products[index].price} onChange={(e) => handleProductChange('price', e.target.value)} />
      </li>
    );
  }
  
  return <ul>{products.map((item, index) => <ProductForm key={item.title} index={index} data={item} />)}</ul>;
}

ReactDOM.render(
  <App />,
  document.getElementById('container')
);

https://jsfiddle.net/lucasbittar/zh1d6y37/23/

我尝试了一堆我在这里找到的解决方案,但没有一个真正代表我所拥有的。

谢谢!

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    问题

    您已在内部将ProductForm 定义为App,因此在每个渲染周期都会重新创建它,即每次渲染它都是一个全新的组件。

    解决方案

    ProductForm 移动到外部定义。这现在存在handleProductChange 无法访问App 的功能范围products 状态的问题。这里的解决方案是将handleProductChange 移回App 并更新函数签名以使用index。使用data.price作为输入值,你可以提供一个回退值或者为这个属性提供初始状态。

    建议:将输入命名为name="price",然后简单地从事件对象中使用它。

    const ProductForm = ({ data, index, handleProductChange }) => {
        return (
          <li>
            <h2>{data.title}</h2>
            <label>Price:</label>
            <input
              name="price" // <-- name input field
              type="text"
              value={data.price || ''} // <-- use data.price or fallback value
              onChange={(e) => handleProductChange(e, index)} // <-- pass event and index
            />
          </li>
        );
      }
    
    const App = () => {
    
        // List of products
        const [products, setProducts] = React.useState([
      {
        title: 'My Product',
      },
      {
        title: 'My Product 2',
      }
      ]);
      
      
      // Simple debug to track changes
      React.useEffect(() => {
        console.log('PRODUCTS', products);
      }, [products]);
    
      // Update signature to also take index
      const handleProductChange = (e, index) => {
          const { name, value } = e.target; // <-- destructure name and value
          const allProducts = [...products];
          const selectedProduct = {...allProducts[index]};
          allProducts[index] = {
            ...selectedProduct,
            [name]: value
          };
          setProducts([ ...allProducts ]);
        }
      
      return (
        <ul>
          {products.map((item, index) => (
            <ProductForm
              key={item.title}
              index={index}
              data={item}
              handleProductChange={handleProductChange} // <-- pass callback handler
            />)
          )}
        </ul>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('container')
    );
    

    Working jsfiddle demo

    【讨论】:

    • 谢谢德鲁!这完全有道理!也有很棒的提示!
    • @Drew Reese 感谢您的解释。当您告诉:“在外部移动 ProductForm”时,我的第一反应是也移动 handleProductChange 并将 setProductsproducts 传递给 &lt;ProductionForm /&gt; 调用。这样handleProductChange 就可以停留在ProductForm 函数的上方。我认为您将 handleProductChange 留在 App 中的方法更好,但您能给我理由或解释原因吗? :)
    • @PredragDavidovic 您当然可以将状态和状态更新器函数传递给使用组件,但随后您依赖它们在更新时保持状态不变。换句话说,每个调用setProducts 的组件都必须确保它正确地更新状态而不是改变它。通过将handleProductChange 与具有状态的组件保持一致,您可以更好地控制如何更新状态,使用组件只需传递需要添加的数据陈述。
    • 抱歉打扰了,但你能解释一下这是什么意思吗:“但是你在更新时依赖它们来保持状态不变。”?在此先感谢:)
    • @PredragDavidovic 您可以在handleProductChange 中定义一次并传递它,您可以认为它每次都会以相同的方式工作, 你可以传递setProducts 并且每个组件都需要实现相同的(或非常相似的更新函数),并且出错的可能性更大。当您执行后者时,您将使其成为消费组件的责任(委托)来更新状态,并且希望他们正确地做到了。希望通过直接状态更新函数传递回调的好处更清楚。
    【解决方案2】:

    解决问题的一种方法是为&lt;ProductForm /&gt; 设置一个单独的组件,并在那里传递所需的props

    const ProductForm = ({ data, index, products, setProducts }) => {
        const handleProductChange = (name, value) => {
          const allProducts = [...products];
          const selectedProduct = {...allProducts[index]};
          allProducts[index] = {
            ...selectedProduct,
            [name]: value
          };
          setProducts([ ...allProducts ]);
        }
        
        return (
          <li>
            <h2>{data.title}</h2>
            <label>Price:</label>
            <input
              type="text"
              value={products[index].price || ''}
              onChange={(e) => handleProductChange('price', e.target.value)}
            />
          </li>
        );
    }
    
    const App = () => {
      const [products, setProducts] = React.useState([{title: 'My Product'},{title: 'My Product 2'}]);
      return (
        <ul>
          {products.map((item, index) => 
             <ProductForm
               key={item.title}
               index={index}
               data={item}
               products={products}
               setProducts={setProducts}
             />
          )}
         </ul>
      )
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('container')
    );
    

    这是有效的 JSFiddle 代码link

    【讨论】:

    • 感谢喜玛雅!这也奏效了!非常感谢您的帮助。
    猜你喜欢
    • 2018-04-10
    • 1970-01-01
    • 2022-11-21
    • 2019-08-27
    • 2021-03-20
    • 1970-01-01
    • 2020-11-15
    • 2023-04-09
    • 1970-01-01
    相关资源
    最近更新 更多