在 React 中构建可链接的过滤器组件?

IT技术 arrays reactjs filter react-hooks react-component
2021-05-24 15:01:15

构建可重用、可链接的过滤器组件的最“react”方式是什么?

假设我有一个输入数组:

[
{name: 'Generic t-shirt', size: 'xxl', available: 35},
{name: 'Generic t-shirt', size: 'md', available: 2},
{name: 'Generic t-shirt', size: 'sm', available: 5},
{name: 'Really awesome shirt', size: 'md', available: 0}
]

以及名称的关键字搜索、大小的下拉菜单以及可用性的“售罄”布尔复选框。

现在,我在渲染循环中有过滤代码:

const [productsFilteredByFullText, setProductsFilteredByFullText] = useState;
const [productsFilteredBySize, setProductsFilteredBySize] = useState;
const [productsFilteredByAvailability, setProductsFilteredByAvailability] = useState;

const searchResults = useMemo(() => {
    let filteredProducts = eventsFromAPI; // array of products as input

    filteredProducts = filteredEvents.filter(product => fullTextFilter(product));
    filteredProducts = filteredEvents.filter(product => sizeFilter(product));
    filteredProducts = filteredEvents.filter(product => availabilityFilter(product));

    return filteredProducts;
}, [productsFilteredByFullText, productsFilteredBySize, productsFilteredByAvailability]);

JSX 中的 UI:

<div>

    // Fulltext search
    <input
        type="text"
        placeholder="Keyword search"
        value={searchTerm}
        onChange={fullTextHandler}
    />


    // Size dropdown
    <Dropdown
        options={allAvailableSizes}
        onChange={sizeHandler}
    />

    // Sold out checkbox
    <input
        name="soldout"
        type="checkbox"
        checked={productsFilteredByAvailability}
        onChange={availabilityHandler}
    />

    <h1>Results</h1>
    {filteredProducts.map(item => (
        <Product item={item} />
    ))}
</div>

这有效,但根本不是很可重用。假设我有另一个产品类别,围巾,它们都是一个尺寸,但我仍然希望能够重新使用过滤器组件和逻辑来确定名称和可用性。

有没有办法将过滤器逻辑和表示 JSX module化/组件化为单独的过滤器组件,并能够任意将它们链接在一起?像这样的伪代码:

<TShirtList>
<NameFilter/>
<SizeFilter/>
<AvailabilityFilter/>
</TShirtList>

<ScarfList>
<NameFilter/>
<AvailabilityFilter/>
</ScarfList>

<ServicesList>
<NameFilter/>
</ServicesList>

所以每个过滤器都是它自己的组件,能够插入到任何地方的任何产品阵列中?就像 React 组件如何还提供其他组件可以使用的自己的逻辑/功能(产品数组输入,过滤后的产品数组 + JSX UI 输出,但可链接)。

这是思考这个问题的正确方式吗……?我对如何在架构上最好地构建这个感到困惑。

2个回答

回答这个问题

构建可重用、可链接的过滤器组件的最“react”方式是什么?

我建议使用钩子来分离显示和数据逻辑。将所有与数据相关的逻辑移动到一个钩子中,并将该钩子导入到react组件中。

// useProducts.js
const useProducts = () => {
  const [products, setProducts = useState([]);
  const [filters, setFilters] = useState({}); // eg. filters = { size: 'large', availability: 'instock' }

  // Get all products on initial load
  useEffect(() => {
    fetch(....).then(response => setProducts(response.data));
  }, []);

  // function to set filter.
  const setFilter = (name, value) => {
    setFilters({ ...filters, [name]: value });
  }

  // Loop filters and filter product 
  const filteredProducts = [...products];
  for (const [key, value] of Object.entries(filters)) {
    // Get list of products from somewhere.
    filteredProducts = filterProducts.filter(p => p[key] === value);
  }

  return [filteredProducts, { setFilter }];
}

// Product List
import React from 'react';
import useProducts from './useProducts';

const ProductList = (props) => {
  const [products, { setFilter }] = useProducts();

  return (
    <div>
      <div className="toolbar">
        <NameFilter onChange={value => setFilter('name', value)} />
        <SizeFilter onChange={value => setFilter('size', value)} />
        <AvailabilityFilter onChange={value => setFilter('availability', value)} />
      </div>
      <div>
        {products.map(p => <ProductItem {...p} />}
      </div>
    </div>
  );
}

此外,您甚至可以通过在过滤器组件本身中导入 { setFilter } 来对其进行更多module化。您可以从 ProductList 中删除“onChange”。

const NameFilter = () => {
  const [, { setFilter }] = useProducts();
  return (
    <input
      type="text"
      onChange={e => setFilter('name', e.target.value)}
    />
  );
}


// And now, we can remove onChange from each filter in Product List component
<div>
  <div className="toolbar">
    <NameFilter />
    <SizeFilter />
    <AvailabilityFilter />
  </div>
  <div>
    {products.map(p => <ProductItem {...p} />}
  </div>
</div>

*注:上面的代码只是伪代码,它只是展示了这个想法。

条件过滤有什么问题吗?

const searchResults =(text, size) => {
    let filteredProducts = eventsFromAPI; // array of products as input

    filteredProducts = text ? filteredEvents.filter(product => fullTextFilter(text)) : filteredProducts;
    filteredProducts = size ? filteredEvents.filter(product => sizeFilter(product)) : filteredProducts
    filteredProducts = filteredEvents.filter(product => availabilityFilter(product));

    return filteredProducts;
};

大小可以是布尔值。

也可以...(伪代码)

const Search =({filter = []}) => { 
    let filteredProducts = eventsFromAPI; // array of products as input

    filteredProducts = filter.indexOf('something') > -1  ? filteredEvents.filter(product => fullTextFilter(text)) : filteredProducts;
    filteredProducts = filter.indexOf('size') > -1 ? filteredEvents.filter(product => sizeFilter(product)) : filteredProducts
    filteredProducts = filteredEvents.filter(product => availabilityFilter(product));

    return filteredProducts; 
};

然后你可以调用search({ filter: ['size', 'availability']})或者如果你把它放到一个组件中<Search filter={['size', 'availability']} products={products} />

甚至

const Search = ({ size = null, text = '', availability = true}) => {
   ... conditionally filtering

   return //whatever product u render
}