React.js搜索筛选对象数组

13

我在使用前端搜索时遇到了问题,而不是远程搜索。由于问题需要,我正在使用react.js并创建了一个名为App的组件。我的任务是根据类型值显示和突出匹配部分。

如果您能为我提供好的解决方案,我将不胜感激。

让我告诉您整个情景。我将这个问题分成了三个部分。

第一部分:数据的形状是什么?

数据的形状如下:

src/data.js:

export default [
    {
        id: 1,
        name: 'Wordpress',
        list: [
            {
                id: 1,
                name: 'Best Mobile App Builder',
                slug: '/'
            },
            {
                id: 2,
                name: 'Best Wordpress Themes',
                slug: '/'
            },
            {
                id: 3,
                name: 'Best Website Creator',
                slug: '/'
            },
            {
                id: 4,
                name: 'Best Wordpress Builder',
                slug: '/'
            }
        ]
    },
    {
        id: 2,
        name: 'SaaS',
        list: [
            {
                id: 1,
                name: 'Appointment Scheduling Software',
                slug: '/'
            },
            {
                id: 2,
                name: 'Design Services',
                slug: '/'
            },
            {
                id: 3,
                name: 'Online Cloud Storage',
                slug: '/'
            },
            {
                id: 4,
                name: 'Remote PC Access',
                slug: '/'
            }
        ]
    },
];

注意:

搜索2个属性

基本上这是我的筛选函数。

src/filter.js:

import _ from 'lodash';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';

/**
 * Returns the new filtered array with highlighted parts.
 * @param data {Array<Object>} - The collection to iterate over.
 * @param inputValue {string} - The input value.
 * @return {Array} - Returns the new filtered array.
 */
export const filterByNames = (data, inputValue) => {
    // Create a dynamic regex expression object with ignore case sensitivity
    const re = new RegExp(_.escapeRegExp(inputValue), 'i');
    const results = data.filter((object) => {
        if (re.test(object.name)) {
            return true;
        } else {
            return object.list.some((item) => {
                if (re.test(item.name)) {
                    // Calculates the characters to highlight in text based on query
                    const matches = match(item.name, inputValue);
                    // Breaks the given text to parts based on matches.
                    // After that create a new property named `parts` and assign an array to it.
                    item['parts'] = parse(item.name, matches);
                    return true;
                } else {
                    return false;
                }
            });
        }
    });
    return results;
};

搜索正常工作,但面临2个主要问题:

  1. 当出现上述name属性的匹配时,它会停止并且不会深入检查。嵌套列表name属性也是同样的情况。

  2. 当过滤发生在后台时,我们通过添加一个名为parts的新属性来改变原始数据,并且它是对象数组,包含突出显示的部分。但我不想改变原始数据,而是想返回包含parts属性的新过滤数组。

请参见此处。

Search Overall

工作演示

Edit search-frontend

第二部分: 我使用了哪些第三方库来实现过滤和突出显示?

  • Lodash 字符串函数 escapeRegExp 用于转义 RegExp 特殊字符。

  • autosuggest-highlightmatch 函数用于根据查询计算文本中要突出显示的字符。

    之后,从同一库的parse函数帮助我们根据匹配项将给定的文本分解为parts。最终,它将返回一个对象数组,其中包含匹配字符串和highlight布尔标志。因此,很容易在UI上加粗突出显示的parts

第三部分:应用程序组件

import React, { useState } from 'react';
import { filterByNames } from './filter';
import data from './data';


/**
 * Return the JSX for the List
 * @param data {Array<Object>} - The collection to iterate over.
 * @return {null|*} - Returns the JSX or null.
 */
const renderList = (data) => {
  if (Array.isArray(data) && data.length > 0) {
    return data.map((object) => {
      return (
          <div key={object.id}>
            <h1>{object.name}</h1>
            <ul className="list">
              {object.list.map((item) => {
                return (
                    <li key={item.id}>
                      {item.parts ? (
                          <a href={item.slug}>
                            {item.parts.map((part, index) => (
                                <span
                                    key={index}
                                    style={{ fontWeight: part.highlight ? 700 : 400 }}
                                >
                          {part.text}
                        </span>
                            ))}
                          </a>
                      ) : (
                          <a href={item.slug}>{item.name}</a>
                      )}
                    </li>
                )
              })}
            </ul>
          </div>
      )
    })
  } else {
    return null
  }
};

// Main App Component
const App = () => {

  const [value, setValue] = useState('');

  const onChangeHandler = (event) => {
    const { target } = event;
    const val = target.value;
    setValue(val);
  };

  const results = !value ? data : filterByNames(data, value);
  
    return (
        <div className="demo">
          <input type="text" value={value} onChange={onChangeHandler}/>
          <div className="demo-result">
            { renderList(results) }
          </div>
        </div>
    );
    
};

export default App;

可以轻松地使用原生JavaScript和可能的jQuery AJAX处理,因为React并不是必需的。这是有意义的注释。 - GetSet
@GetSet 不需要使用ajax,整个应用的前端都是用react.js实现的。但filter函数是主要的功能。 - Ven Nilson
但是像大多数框架一样,React允许您在需要时使用自己的解决方案。看起来您的React技能还比较中等,因此您可以提供一个不使用React的解决方案。Ijs - GetSet
我有一个关于你的目标的问题。当用户输入“存储”时,列出所有SaaS项目(4个项目)并突出显示存储是否正确?我不太理解你的主要问题1。 - Hayden S.
1
@FujiRoyale 我们可以用任何lodash函数,例如map来返回新的修改后的数组吗? - Ven Nilson
显示剩余5条评论
2个回答

2

这是修订后的代码。

export const filterByNames = (data, inputValue) => {
  // Create a dynamic regex expression object with ignore case sensitivity
  const re = new RegExp(_.escapeRegExp(inputValue), "i");
  const clonedData = _.cloneDeep(data);
  const results = clonedData.filter((object) => {
    return object.list.filter((item) => {
      if (re.test(item.name)) {
        // Calculates the characters to highlight in text based on query
        const matches = match(item.name, inputValue);
        // Breaks the given text to parts based on matches.
        // After that create a new property named `parts` and assign an array to it.
        item["parts"] = parse(item.name, matches);
        return true;
      } else {
        return false;
      }
    }).length > 0 || re.test(object.name);
  });
  return results;
};

分支链接在这里。 https://codesandbox.io/s/search-frontend-forked-e3z55


哇,答案太棒了。您能否在代码中添加注释?如果您知道其他解决方案,请告诉我。我将非常感激。 - Ven Nilson

0

这里是已经解决了的代码

export const filterByNames = (data, inputValue) => {
  // Create a dynamic regex expression object with ignore case sensitivity
  const re = new RegExp(_.escapeRegExp(inputValue), "i");
  // since we cannot directly mutate the data object, why not copy it here ? (or if the data is bigger and copying is also not an option then consider using two arrays of data, one for the mutation and one default maybe)
  let data_ = JSON.parse(JSON.stringify(data));
  // filter and return the newer copy of the object.
  const results = data_.filter((object) => {
    // since we need the highlighting in both cases, on top level, or even in nested level so create separate function for that.
    let highLightEm = (list) => {
      return object.list.some((item) => {
        if (re.test(item.name)) {
          // Calculates the characters to highlight in text based on query
          const matches = match(item.name, inputValue);
          // Breaks the given text to parts based on matches.
          // After that create a new property named `parts` and assign an array to it.
          item["parts"] = parse(item.name, matches);
          return true;
        } else {
          return false;
        }
      });
    };

    if (re.test(object.name)) {
      // check for the highlighting in the inner name
      highLightEm(object);
      return true;
    } else {
      return highLightEm(object);
    }
  });
  return results;
};

https://codesandbox.io/s/search-frontend-forked-kxui9?file=/src/filter.js


当用户输入“wordpress”时,它将突出显示数据中的所有“wordpress”单词。另一个例子是“best”单词,我已经在gif中展示过了,但它没有突出显示数据中的所有“best”单词。 - Ven Nilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接