我正在做这个前端导师挑战,大部分功能都已实现。我可以根据数据的isActive
属性进行过滤,根据它们是否处于活动状态来显示正确的项目。但我搞不清楚如何更新单个项目的isActive
状态,以及如何在切换时重新渲染应用,以便将项目移动到正确的过滤器中。
以下是代码App.jsx
:
function App() {
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState(data);
const [activeFilter, setActiveFilter] = useState("All");
// Fetch and set the data.
useEffect(() => {
fetch("../data.json")
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => console.error("error", error));
}, []);
// Filter the data
useEffect(() => {
if (activeFilter === "All") {
setFilteredData(data);
} else if (activeFilter === "Active") {
setFilteredData(data.filter((item) => item.isActive === true));
} else {
setFilteredData(data.filter((item) => item.isActive === false));
}
}, [activeFilter, data]);
return (
<div className="w-full h-full bg-linear-to-b from-[#040918] to-[#091540] py-6 px-3 flex flex-col text-white">
{/* Header */}
<Header />
{/* Options */}
<Options activeFilter={activeFilter} setActiveFilter={setActiveFilter} />
{/* Cards */}
{filteredData &&
filteredData.map((item) => (
<Card
logo={item.logo}
name={item.name}
description={item.description}
isActive={item.isActive}
key={item.name}
setFilteredData={setFilteredData}
filteredData={filteredData}
/>
))}
</div>
);
}
这是Card.jsx
带有拨动开关的:
const Card = ({
logo,
name,
description,
isActive,
filteredData,
setFilteredData,
}) => {
const [activeState, setActiveState] = useState(isActive);
function handleClick() {
setActiveState(!activeState);
}
return (
<div className="bg-neutral-700 p-4 rounded-xl border border-neutral-600 mt-4">
<div className="flex items-start gap-4">
{/* Logo */}
<img src={logo} alt="Extension Image" />
{/* Name and Description */}
<div className="flex flex-col gap-2 mb-8">
<h2 className="font-semibold text-xl">{name}</h2>
<p className="text-sm font-light">{description}</p>
</div>
</div>
{/* Remove Button */}
<div className="flex justify-between items-center">
<div className="border border-neutral-600 rounded-full px-3 py-1 flex items-center justify-center">
<button>Remove</button>
</div>
{/* Is Active Toggle */}
<div className="flex items-center justify-center">
<label
htmlFor={`${name}Toggle`}
className="flex items-center cursor-pointer "
>
<div className="relative">
<input
id={`${name}Toggle`}
type="checkbox"
className="sr-only"
onClick={handleClick}
/>
<div
className={`flex items-center ${
activeState ? "bg-red-400 " : "bg-gray-600 "
} w-11 h-6 rounded-full transition-all px-[2px]`}
>
<div
className={`bg-white w-5 h-5 rounded-full ${
activeState ? "translate-x-5" : "translate-x-0"
} transition-all`}
></div>
</div>
</div>
</label>
</div>
</div>
</div>
);
};
我试图在卡片handleclick
函数里找到同样的方法,将特定卡片的名称与物品的名称匹配,filteredData
然后……翻转它的isActive
状态?我不知道该怎么做,我甚至不确定这是不是最好的方法。
看起来状态被复制了。父组件维护着列表项和过滤器的状态,并将
isActive
状态值传递给每个子组件:然而,子组件随后将该值复制到其内部管理的状态中:
更新该状态对父组件中的列表没有影响。
不要在子组件中跟踪重复的状态,而是将回调函数传递给子组件,该函数会更新父组件中的状态。例如,假设父组件有以下内容:
(注意:这当然是自由的,没有用您的数据进行测试。但上述的总体目标是将状态更新为一个新的项目数组,其中为
isActive
与指定项目匹配的项目切换标志。)然后将其传递给子组件以更新该状态:
然后子组件可以
useState
完全删除它并调用传递给它的回调函数:这样,父状态就会更新,父组件和相关子组件会使用新状态重新渲染。
问题
您已经实现了至少几个 React 反模式:
filteredData
是派生的“状态”,例如,从data
和activeFilter
真值源状态派生而来,因此filteredData
不应同时是 React 状态。您应该在渲染时计算过滤后的值,或将其值记忆起来。请记住,实现useState
-useEffect
耦合也是一种反模式,在几乎 100% 的用例中,这种耦合应该用useMemo
钩子代替。Card
组件无法正确更新data
父组件中的可靠来源。它传递了一个isActive
prop,但还应该传递一个回调函数来处理父组件中状态的更新。解决方案建议
data
以计算得出的过滤数据值。data
状态并将其传递Card
给filteredData
状态和状态更新器(将被删除)。例子: