【问题标题】:Building chainable filter components in React?在 React 中构建可链接的过滤器组件?
【发布时间】:2021-06-28 20:04:00
【问题描述】:

构建可重用、可链接的过滤器组件最“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 模块化/组件化为单独的过滤器组件,并能够任意将它们链接在一起?像这样的伪代码:

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

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

<ServicesList>
<NameFilter/>
</ServicesList>

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

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

【问题讨论】:

  • filter 返回一个数组,因此您可以链接任何其他数组函数,例如另一个 filter,然后最终是 map,以将生成的过滤数据数组映射到 JSX。我可以想象在数据上使用reduce 函数,使用一组过滤器函数可以使代码更加干燥和可重用。
  • 这从数组操作的角度来看是可行的,但它并没有真正允许将过滤器逻辑和 UI 模块化到单独的组件中。例如要连续添加多个过滤器,您必须在渲染循环中单独编写过滤器逻辑,然后为 UI 添加 JSX 并手动将其绑定到逻辑中。有没有办法用UI+逻辑做一个JSX的“管道”?
  • 您可以在父组件中保存一个过滤器(具有所有条件的对象),然后将设置器提供给“过滤子组件”(例如&lt;NameFilter onChange={(newFilterItem) =&gt; setFilter((oldFilter) =&gt; ({...oldFilter, yourCriteria: newFilterItem.yourCriteria || null })) } /&gt;)。让我知道我是否应该为此描述一个答案。

标签: arrays reactjs filter react-hooks react-component


【解决方案1】:

回答这个问题

构建可重用、可链接过滤器的最“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 } 来对其进行更多模块化。您可以从 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>

*注意:以上代码只是伪代码,只是说明想法。

【讨论】:

  • 谢谢!我必须对此进行试验才能完全理解它在做什么(比如它如何将一个 set 函数传递给另一个钩子),但我认为这已经完成了 95% 的工作!如果我想制作 ProductList 的不同变体(如 ProductGrid、ProductCards、ProductMosaic 等),我只需在其他组件中导入 useProducts 并重用逻辑,对吗?
  • 我应该注意到,使用上面的代码,每个 useProducts 存储单独的一组“产品”(因为每个 useProducts 都有自己的“产品”状态。这对于某些用例应该没问题,但如果它开始成为数据重复的问题,您需要将 useContext 添加到组合中并将“产品”存储在一个地方。
【解决方案2】:

条件过滤有什么问题吗?

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']}),或者如果你把它加入到一个组件然后&lt;Search filter={['size', 'availability']} products={products} /&gt;

甚至

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

   return //whatever product u render
}

【讨论】:

  • 我认为困难的部分是在 UI 组件 (&lt;NameFilter/&gt;) 和过滤器函数 (fullTextFilter()) 之间来回传递状态。
猜你喜欢
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多