React 项目中实现客户端搜索

假设您想要在没有合适后端的情况下,在应用程序中实现一个粗略的搜索功能。你可以通过 lunr 完成它并生成一个静态搜索索引服务。

关键的问题是索引会根据内容的数量而变化。好消息是应用在最开始的时候不需要搜索索引,您可以在用户输入搜索字段时开始加载索引。

我们把索引的加载移动到性能更可接受的地方。初始搜索将比后续搜索慢,您应该显示一个加载指示符。但从用户的角度来看,这是可以接受的。Webpack 的代码拆分可以做到这一点。

使用代码拆分实现搜索

要实现代码拆分,您需要决定将拆分点放在何处,然后在那里通过 promise 加载代码:

import("./asset").then(asset => ...).catch(err => ...)

美妙的是,这可以在出现问题(网络故障等)时进行错误处理,并提供恢复的机会。您还可以使用 Promise 基础实用程序 Promise.all 来编写更复杂的查询。

现在,您需要检测用户何时点击搜索元素,然后加载数据(除非已经加载了数据),然后对其执行搜索逻辑。考虑下面的 React 实现:

App.js

import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      index: null,
      value: "",
      lines: [],
      results: [],
    };
  }
  render() {
    const { results, value } = this.state;

    return (
      <div className="app-container">
        <div className="search-container">
          <label>Search against README:</label>
          <input
            type="text"
            value={value}
            onChange={e => this.onChange(e)}
          />
        </div>
        <div className="results-container">
          <Results results={results} />
        </div>
      </div>
    );
  }
  onChange({ target: { value } }) {
    const { index, lines } = this.state;

    // 更新搜索框的值
    this.setState(() => ({ value }));

    // 搜索行或索引,如果行和索引存在的话
    if (lines && index) {
      return this.setState(() => ({
        results: this.search(lines, index, value),
      }));
    }

    // 如果索引不存在,我们就加载它
    // 你应该在这里添加一个加载提示图标
    // 因为索引的加载可能会花费一些时间
    loadIndex()
      .then(({ index, lines }) => {
        // 现在搜索索引
        this.setState(() => ({
          index,
          lines,
          results: this.search(lines, index, value),
        }));
      })
      .catch(err => console.error(err));
  }
  search(lines, index, query) {
    // 搜索索引并匹配 README 行
    return index
      .search(query.trim())
      .map(match => lines[match.ref]);
  }
}

const Results = ({ results }) => {
  if (results.length) {
    return (
      <ul>
        {results.map((result, i) => <li key={i}>{result}</li>)}
      </ul>
    );
  }

  return <span>No results</span>;
};

function loadIndex() {
  // 关键在于这个地方,通过 `import` 告诉 Webpack 在这里拆分代码
  // 并且动态加载我们的搜索索引
  // 注意,对于一些老的浏览器以及 IE,你需要提供 promise.all 兼容代码
  return Promise.all([
    import("lunr"),
    import("../search_index.json"),
  ]).then(([{ Index }, { index, lines }]) => {
    return {
      index: Index.load(index),
      lines,
    };
  });
}

在该示例中,webpack 对 import 进行静态检测。它可以基于此分割点生成单独的包。鉴于它依赖于静态分析,您无法在这种情况下进行生成 loadIndex,也不能将搜索索引路径作为查询参数。

总结

除了搜索之外,该方法也可以与路由一起使用。当用户进入路由时,您可以加载生成视图所需的依赖项。或者,您可以在用户滚动页面到特定的位置时开始加载依赖项。import 提供了强大的功能,让您的应用程序保持精简。

你可以找到一个 lunr、React 和 Webpack 的三者结合使用的完整的例子。基本的想法是一样的,但有功能更加丰富。

回顾一下:

  • 如果您的数据集很小且是静态的,那么客户端搜索是一个不错的选择。
  • 您可以使用 lunr 等解决方案索引内容,然后对其执行搜索。
  • Webpack 的代码拆分功能非常适合按需加载搜索索引。
  • 代码拆分可以与 React 等 UI 解决方案结合使用,以实现整个用户界面。
用户头像
登录后发表评论