vbe*_*nal 2 javascript reactjs redux react-redux
我有一个呈现一些产品的列表,这些产品分为一些类别。有些产品可能有多个类别。
我正在尝试通过复选框应用包含这些类别的过滤器。当用户选中该复选框时,必须使用所选类别更新列表。
我仍然是初学者Redux,我不知道如何在组件之间进行通信以更新列表。我说组件之间的通信是因为我的类别列表在Drawer组件中,我的产品列表在Card组件中。
我把我的代码放入codesandbox,因为有很多文件
我在这里呈现我的产品列表:
import React, { useState, useMemo, useEffect } from 'react';
import { useSelector } from 'react-redux';
import CardItem from '../CardItem';
import Pagination from '../Pagination';
import Search from '../Search';
import { useStyles } from './styles';
const Card = (props) => {
const { activeFilter } = props;
const classes = useStyles();
const data = useSelector((state) => state.perfume.collections);
const [searchPerfume, setSearchPerfume] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [perfumesPerPage, setPerfumesPerPage] = useState(3);
console.log('activeFilter: ', activeFilter);
const filteredPerfumes = useMemo(() => {
return data.filter((perfume) =>
perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
);
}, [data, searchPerfume]);
const currentPerfumes = filteredPerfumes.slice(
(currentPage - 1) * perfumesPerPage,
currentPage * perfumesPerPage
);
const pages = Math.ceil(filteredPerfumes.length / perfumesPerPage);
useEffect(() => {
if (currentPage > pages) {
setCurrentPage(1);
}
}, [currentPage, pages]);
const pageNumbers = Array(pages)
.fill(null)
.map((val, index) => index + 1);
const handleClick = (page) => {
setCurrentPage(page);
};
return (
<div>
<Search
data-testid="input-filter-id"
setSearchPerfume={setSearchPerfume}
/>
{currentPerfumes
.filter((perfume) => {
return (
perfume.name.toLowerCase().indexOf(searchPerfume.toLowerCase()) >= 0
);
})
.map((item) => (
<CardItem key={item.id} item={item} />
))}
<Pagination
pageNumbers={pageNumbers}
handleClick={handleClick}
currentPage={currentPage}
/>
</div>
);
};
export default Card;Run Code Online (Sandbox Code Playgroud)
我在这里呈现我的类别列表:
import Divider from '@material-ui/core/Divider';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Checkbox from '@material-ui/core/Checkbox';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useStyles } from './styles';
const DrawerComponent = (props) => {
const { activeFilter, setActiveFilter } = props;
const classes = useStyles();
const data = useSelector((state) => state.perfume.collections);
const handleChange = (text) => (event) => {
setActiveFilter((prev) => ({
...prev,
value: event.target.checked,
text,
}));
};
const allCategories = data
.reduce((p, c) => [...p, ...c.categories], [])
.filter((elem, index, self) => index === self.indexOf(elem));
return (
<div className={classes.root}>
<div className={classes.toolbar} />
<Divider />
<List className={classes.list}>
{allCategories.sort().map((text, index) => (
<ListItem className={classes.itemList} button key={text}>
<Checkbox onChange={handleChange(text)} />
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
};
export default DrawerComponent;Run Code Online (Sandbox Code Playgroud)
您知道如何将此过滤器应用到我的列表中吗?
预先非常感谢您。
通常,当您需要在组件之间进行通信时,解决方案是Lifting State Up。这意味着国家应该由共同的祖先控制。
而不是在你的DrawerComponent:
const [activeFilter, setActiveFilter] = React.useState([]);
Run Code Online (Sandbox Code Playgroud)
将钩子移至您的并将和作为道具PerfumeStore传递给。activeFiltersetActiveFilterDrawerComponent
您需要向组件添加一个onChange函数,通过调用 来添加或删除类别。CheckboxDrawerComponentsetActiveFilter
所以现在您需要将 应用于activeFilter您的香水列表,该列表是在 中确定的Card。您可以将所有过滤移至PerfumeStore,但为了简单起见,让我们activeFilter作为 prop 向下传递Card(它只需要读取但不写入过滤器,因此我们不会向下传递setActiveFilter)。现在,该Card组件具有根据所选类别过滤项目所需的信息。
到目前为止,一切都与 React 有关,而您使用 redux 的事实根本没有发挥作用。如果您愿意,合并 redux 原则的方法是将组件外部的一些过滤和映射逻辑定义为选择器函数state和其他参数。然后,您可以使用仅获取所需数据的选择器进行调用useSelector,而不是调用以获取在组件内处理的大量状态。useSelector
执行此操作的一个明显位置是在 中DrawerComponent,您可以在其中获取类别列表。创建一个选择器函数getPerfumeCategories,它接受state并返回类别。然后在你的组件中,你调用const allCategories = useSelector(getPerfumeCategories);
现在该组件负责渲染类别。它不再负责存储选择(我们已经将其移出useState)或从状态中查找类别。这很好!如果您想更深入地了解为什么这是好的,您可以阅读单一职责原则、关注点分离、逻辑与表示组件等原则。
您Card可以使用一个选择器来获取已过滤的香水列表。但在这种情况下,一个getCurrentPagePerfumes函数会接受很多不同的参数,所以有点混乱。
您已就如何应用 的值activeFilter来过滤列表中显示的香水寻求帮助。
可以一次选择多个类别,因此activeFilter需要识别所有主动选择的类别。我首先建议使用一个名称数组,但是从数组中删除项目(不进行突变)比为对象赋值更复杂。
所以后来我想到有一个对象,其中键是类别名称,值是boolean是否检查类别的真/假。这变得handleChange非常简单,因为我们可以将该键的值更新为 的值event.target.checked。
const handleChange = (text) => (event) => {
setActiveFilter((prev) => ({
...prev,
[text]: event.target.checked,
}));
};
Run Code Online (Sandbox Code Playgroud)
...prev说“除了我要更改的密钥之外,保持一切不变”。[text]说“我正在更新的键是变量text,而不是文字键‘text’”event.target.checked是是否检查该类别的布尔值。我们可以设置一个初始状态,其中activeFilter包括每个类别的键和所有值false(即未选择任何内容)。或者我们可以允许对象不完整,假设如果不包含它的键,则不会检查它。让我们这样做吧。
所以现在我们的activeFilter看起来像这样:{Floral: true, Floriental: false, Fresh: true}有些在true,有些在false,很多都丢失了,因此假设是false。
我们需要弄清楚如何根据 的值过滤显示的香水activeFilter。让我们首先编写一个函数来确定一种香水是否有资格显示,然后我们可以将其用作香水数组上的 array.filter() 的回调。如果选中了任何类别的香水,我们希望将其包含在内(除非您希望它与所有选中的类别相匹配?)。看起来像:
perfume.categories.some(
category => activeFilter[category] === true
);
Run Code Online (Sandbox Code Playgroud)
其中.some()循环遍历类别,true如果我们的回调适用true于任何类别,则返回。
我将其添加到您的filteredPerfumes备忘录中并添加activeFilter为依赖项,以便在复选框更改时它会重新过滤。
const filteredPerfumes = useMemo(() => {
return data.filter((perfume) =>
perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
&& perfume.categories.some(
category => activeFilter[category] === true
)
);
}, [data, searchPerfume, activeFilter]);
Run Code Online (Sandbox Code Playgroud)
这是可行的,只是当没有检查类别时什么也没有显示 - 哎呀!因此,我们想添加一个特殊情况,即“如果没有检查类别,则所有香水都会通过类别过滤器”。为此,我们需要知道是否有已检查的类别。有很多方法可以做到这一点,但这里有一个:
const hasCategoryFilter = Object.values(activeFilter).includes(true);
Run Code Online (Sandbox Code Playgroud)
我们查看对象中的所有值activeFilter,看看它是否包含任何true.
现在我们需要使用这个值来仅在它为 时根据类别进行过滤true。我将把之前的逻辑拉入一个函数中并添加一条if语句(注意:布尔运算符||使用起来更短,但我认为if更具可读性)。
const matchesCategories = (perfume) => {
if ( hasCategoryFilter ) {
return perfume.categories.some(
category => activeFilter[category] === true
);
} else return true;
}
Run Code Online (Sandbox Code Playgroud)
旁注:我们有两个独立的过滤器,一个用于搜索,一个用于类别,因此我们可以调用data.filter一次并一次或两次检查两个条件,然后分别检查每个条件。你做什么并不重要。
最终的过滤器是:
const filteredPerfumes = useMemo(() => {
const hasCategoryFilter = Object.values(activeFilter).includes(true);
const matchesCategories = (perfume) => {
if ( hasCategoryFilter ) {
return perfume.categories.some(
category => activeFilter[category] === true
);
} else return true;
}
return data.filter((perfume) =>
perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
).filter( matchesCategories );
}, [data, searchPerfume, activeFilter]);
Run Code Online (Sandbox Code Playgroud)