面试题答案
一键面试设置依赖追踪
- 确定依赖项:
- 在电商购物车场景中,商品列表中每个商品的价格、数量、折扣等属性都是计算总价的依赖项。例如,如果商品对象数组为
cartItems
,每个cartItem
对象包含price
(价格)、quantity
(数量)、discount
(折扣)等属性。 - 总价的计算逻辑通常依赖于遍历
cartItems
数组,并根据每个商品的属性计算其小计(price * quantity * (1 - discount)
),然后将所有商品的小计相加得到总价。
- 在电商购物车场景中,商品列表中每个商品的价格、数量、折扣等属性都是计算总价的依赖项。例如,如果商品对象数组为
- 在createMemo中设置依赖:
- 以React为例(假设使用React hooks中的
createMemo
,即useMemo
,这里createMemo
类似概念),代码如下:
import React, { useMemo } from'react'; const Cart = ({ cartItems }) => { const totalPrice = useMemo(() => { return cartItems.reduce((acc, item) => { const subTotal = item.price * item.quantity * (1 - item.discount); return acc + subTotal; }, 0); }, [cartItems]); return ( <div> {/* 购物车其他内容 */} <p>Total Price: {totalPrice}</p> </div> ); };
- 在上述代码中,
useMemo
的第二个参数[cartItems]
设置了依赖。这意味着当cartItems
数组引用发生变化时,useMemo
中的回调函数会重新执行,从而重新计算总价。
- 以React为例(假设使用React hooks中的
可能遇到的坑及解决方案
- 浅比较问题:
- 坑:
- 当使用
createMemo
并将数组(如cartItems
)作为依赖时,JavaScript默认进行浅比较。如果只是修改了数组中某个对象的属性,而数组引用没有改变,createMemo
不会重新计算。例如:
import React, { useMemo, useState } from'react'; const Cart = () => { const [cartItems, setCartItems] = useState([{ id: 1, price: 10, quantity: 2, discount: 0.1 }]); const totalPrice = useMemo(() => { return cartItems.reduce((acc, item) => { const subTotal = item.price * item.quantity * (1 - item.discount); return acc + subTotal; }, 0); }, [cartItems]); const handlePriceChange = () => { const newCartItems = [...cartItems]; newCartItems[0].price = 15; setCartItems(newCartItems); }; return ( <div> <button onClick={handlePriceChange}>Change Price</button> <p>Total Price: {totalPrice}</p> </div> ); };
- 在上述代码中,
handlePriceChange
函数试图修改商品价格,但由于cartItems
数组引用在修改后没有改变(只是修改了数组内对象的属性),totalPrice
不会重新计算。
- 当使用
- 解决方案:
- 可以使用immer库来帮助创建新的对象或数组,同时保持数据结构的一致性,确保数组引用发生变化。例如:
import React, { useMemo, useState } from'react'; import produce from 'immer'; const Cart = () => { const [cartItems, setCartItems] = useState([{ id: 1, price: 10, quantity: 2, discount: 0.1 }]); const totalPrice = useMemo(() => { return cartItems.reduce((acc, item) => { const subTotal = item.price * item.quantity * (1 - item.discount); return acc + subTotal; }, 0); }, [cartItems]); const handlePriceChange = () => { setCartItems(produce(cartItems, draft => { draft[0].price = 15; })); }; return ( <div> <button onClick={handlePriceChange}>Change Price</button> <p>Total Price: {totalPrice}</p> </div> ); };
produce
函数会返回一个新的数组引用,从而触发useMemo
重新计算。
- 坑:
- 依赖过多或过少问题:
- 坑:
- 依赖过多:如果在
createMemo
的依赖数组中添加了不必要的依赖,会导致不必要的重新计算。例如,如果有一个与购物车总价无关的组件状态userName
,并错误地将其添加到useMemo
的依赖数组中:
import React, { useMemo, useState } from'react'; const Cart = () => { const [cartItems, setCartItems] = useState([{ id: 1, price: 10, quantity: 2, discount: 0.1 }]); const [userName, setUserName] = useState('John'); const totalPrice = useMemo(() => { return cartItems.reduce((acc, item) => { const subTotal = item.price * item.quantity * (1 - item.discount); return acc + subTotal; }, 0); }, [cartItems, userName]); const handleNameChange = () => { setUserName('Jane'); }; return ( <div> <button onClick={handleNameChange}>Change Name</button> <p>Total Price: {totalPrice}</p> </div> ); };
- 此时,每次
userName
变化都会导致totalPrice
重新计算,这是不必要的。 - 依赖过少:如果遗漏了必要的依赖,会导致
createMemo
不会在需要时重新计算。例如,如果计算总价还依赖于一个全局的税率taxRate
,但没有将其添加到依赖数组中:
import React, { useMemo, useState } from'react'; const taxRate = 0.05; const Cart = () => { const [cartItems, setCartItems] = useState([{ id: 1, price: 10, quantity: 2, discount: 0.1 }]); const totalPrice = useMemo(() => { return cartItems.reduce((acc, item) => { const subTotal = item.price * item.quantity * (1 - item.discount); return acc + subTotal * (1 + taxRate); }, 0); }, [cartItems]); const handleTaxRateChange = () => { // 假设这里可以改变taxRate,实际场景可能更复杂 taxRate = 0.08; }; return ( <div> <button onClick={handleTaxRateChange}>Change Tax Rate</button> <p>Total Price: {totalPrice}</p> </div> ); };
- 当
taxRate
变化时,totalPrice
不会重新计算,因为taxRate
没有在依赖数组中。
- 依赖过多:如果在
- 解决方案:
- 仔细分析计算逻辑,确保依赖数组只包含与计算结果直接相关的状态或变量。对于全局变量,如果其值可能变化影响计算结果,考虑将其包装在状态中(如使用
useState
或其他状态管理工具),并将该状态添加到依赖数组中。对于不必要的依赖,从依赖数组中移除。同时,可以使用ESLint插件(如eslint-plugin-react-hooks
)来帮助检测依赖相关的错误,它会提示依赖过多或过少的问题。
- 仔细分析计算逻辑,确保依赖数组只包含与计算结果直接相关的状态或变量。对于全局变量,如果其值可能变化影响计算结果,考虑将其包装在状态中(如使用
- 坑:
- 性能问题:
- 坑:
- 如果购物车商品数量非常大,每次重新计算总价可能会带来性能问题。例如,计算逻辑复杂且商品数量达到成百上千时,
createMemo
的重新计算可能会导致卡顿。
- 如果购物车商品数量非常大,每次重新计算总价可能会带来性能问题。例如,计算逻辑复杂且商品数量达到成百上千时,
- 解决方案:
- 可以采用增量计算的方式。例如,记录上一次计算的总价和每个商品的小计,当某个商品属性变化时,只重新计算该商品的小计变化,并相应地更新总价,而不是重新遍历所有商品。另外,可以考虑使用虚拟列表技术来处理大量商品的展示,减少渲染和计算的压力。
- 坑: