React 搜索栏让我们在 React 中构建一个搜索栏!
本文的原始版本可以在这里找到。
我知道,我知道...另一个任务应用程序...
听我说完!我们将构建一个任务应用,它还能根据搜索查询实时过滤列表。听起来很复杂?其实并没有你想象的那么复杂,让我们开始吧!
开始之前先简单说明一下:
我将使用 Parcel 作为打包工具。它非常棒,而且
设置起来超级简单。我还有另一篇关于使用 Parcel 设置项目的文章
,里面会提供更多设置信息,所以如果我
在这里讲得太快,建议你先去这里看看。
设置我们的文件
首先,我们将创建目录并使用命令行进入。为此,请打开终端并导航到要放置项目的目录。到达那里后,使用以下代码行创建项目目录并进入。
mkdir search-tasks && cd $_
现在我们进入了项目文件夹,需要使用 yarn 或 npm 初始化项目。在本项目中,我将使用 yarn,但 npm 的命令基本相同。
yarn init -y
我们将直接使用该-y
标志,以便它自动为我们配置。我们package.json
很快就会进入并修改该文件。
现在我们有了一个package.json
文件,我们应该创建index.html
和app.js
文件。你可以在终端中使用下面的代码行同时创建这两个文件。
touch index.html app.js
接下来我们需要打开index.html
文件进行编辑并将下面的代码放入其中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Search To-Do App</title>
</head>
<body>
<div id="app"></div>
<script src="./app.js"></script>
</body>
</html>
将包添加到我们的项目
接下来,我们需要在项目中安装必要的软件包。在本例中,这些软件包包括 React、React DOM、Parcel、Babel-Preset-env、Babel-Preset-React 和 Bulma。要将这些软件包添加到项目中,您可以使用 NPM 或 Yarn。我会提供这两种软件包的代码,您可以选择更合适的方式。
npm install react react-dom parcel babel-preset-env babel-preset-react bulma --save-dev
or
yarn add react react-dom parcel babel-preset-env babel-preset-react bulma
这些是做什么的?
NPM 和 Yarn 是软件包管理器,允许您将预先编写的代码添加到项目中。这可以显著加快开发速度。下面简要介绍一下这两个软件包的功能。
- React:一个加速开发的库(对于 React 教程来说这似乎很明显,对吧?)链接
- React-DOM:一个允许 React 与浏览器中的 DOM 进行交互的库。链接
- Parcel:无需配置的打包库。链接
- Babel-preset-env:一个库,它告诉 Parcel 如何转换 ES6 代码,使其兼容各种浏览器。链接
- Babel-preset-react:一个告诉 Parcel 如何处理 JSX 的库。链接
- Bulma:一个使用 flexbox 布局且易于使用的 CSS 框架。链接
设置 package.json 和 .babelrc
在实际开始构建 React 项目之前,我们需要添加一个.babelrc
文件来包含我们安装的 babel-presets。首先,使用以下代码创建文件:
touch .babelrc && open $_
进入文件后,我们将添加以下代码以包含已安装的预设。
{
"presets": ["env", "react"]
}
设置好 .babelrc 文件后,我们需要将启动脚本添加到 package.json 文件中。打开该文件,在文件中添加以下代码:
"scripts": {
"start": "parcel index.html"
},
设置 app.js 文件
还在吗?太棒了!下一步是在app.js
文件中设置一个组件。我们将使用状态来管理列表,因此需要使用类组件。首先,让我们导入构建应用所需的库。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import 'bulma/bulma';
然后我们可以创建一个 App 组件:
class App extends Component {
render() {
return(
...
)
}
}
然后我们需要确保组件渲染到 DOM。我们将使用 React DOM 来实现这一点。
ReactDOM.render(<App />, document.getElementById('app'));
现在我们可以添加构造函数和状态了。我们将在状态中创建一个“list”数组。首先,我们将在其中填充一些项目,以便我们可以看到列表:
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: [
"Go to the store",
"Wash the dishes",
"Learn some code"
]
}
}
...
}
太棒了!现在我们在 App 组件的状态中已经有了列表,让我们来显示它。我使用的样式是 Bulma,但你可能用的是其他的。这完全没问题,你只需要相应地调整你的类即可。
class App extends Component {
...
render() {
return (
<div className="content">
<div className="container">
<section className="section">
<ul>
{this.state.list.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</section>
</div>
</div>
)
}
}
上面的代码在做什么?
我们需要渲染列表。为此,我们使用了一些 Bulma 类来提供一些布局空间。最重要的部分是<ul>
。首先,我们创建<ul>
要在其中显示列表的 。然后,我们将使用花括号转义 JSX 代码,并使用名为 的 JavaScript 函数.map()
。我们获取在状态中创建的列表this.state.list
,并将其添加.map()
到其末尾。然后,我们传递一个回调函数(在本例中使用箭头函数)来返回我们要显示的 JSX 代码。
函数.map()
的工作原理与 a 类似,foreach
因为它会循环遍历数组中的每个项目。我们传递给回调函数的参数(在本例中为item
)将代表循环每次迭代中的项目。在 return 内部,我们将创建一个<li>
,它将显示的文本将是item
,或者说是列表数组当前索引中的文本。
我们得到了什么?
如果我们回到终端并输入yarn start
或npm run start
,我们就可以localhost:1234
在浏览器中看到我们创建的待办事项列表,它显示为无序列表。现在,让我们允许用户将待办事项添加到列表中。
将项目添加到列表
这很简单。首先,我们需要添加代码来渲染一个输入框和一个提交按钮。目前,渲染组件的完整代码应该如下所示:
<div className="content">
<div className="container">
<section className="section">
<ul>
{this.state.list.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</section>
<hr />
<section className="section">
<form className="form" id="addItemForm">
<input
type="text"
className="input"
id="addInput"
placeholder="Something that needs ot be done..."
/>
<button className="button is-info" onClick={this.addItem}>
Add Item
</button>
</form>
</section>
</div>
</div>
添加添加项目的功能
现在我们已经渲染了输入框和按钮,我们需要让它们执行一些操作。否则,用户将无法更改列表。为此,我们需要在addItem()
构造函数下方、render 方法之前添加一个函数,该函数调用组件。我们需要在点击按钮时运行该函数。点击按钮后,它会获取输入框中的文本,并判断其是否为空。如果有文本,我们会将其添加到状态数组中,然后更新渲染的页面。以下函数将为输入框添加必要的功能:
addItem(e) {
// Prevent button click from submitting form
e.preventDefault();
// Create variables for our list, the item to add, and our form
let list = this.state.list;
const newItem = document.getElementById("addInput");
const form = document.getElementById("addItemForm");
// If our input has a value
if (newItem.value != "") {
// Add the new item to the end of our list array
list.push(newItem.value);
// Then we use that to set the state for list
this.setState({
list: list
});
// Finally, we need to reset the form
newItem.classList.remove("is-danger");
form.reset();
} else {
// If the input doesn't have a value, make the border red since it's required
newItem.classList.add("is-danger");
}
}
现在我们已经构建了函数,但它不知道何时运行或如何解释this
关键字。我们可以在构造函数中使用以下代码告诉 React 如何处理这个问题:
this.addItem = this.addItem.bind(this);
我们可以为按钮添加一个 onClick 触发器,因此我们的按钮应该如下所示:
<button className="button is-info" onClick={this.addItem}>
Add Item
</button>
我们可以在浏览器中使用yarn start
或npm run start
并转到 来测试我们的应用程序localhost:1234
。现在我们的应用可以向列表中添加项目了!太酷了!

添加删除按钮
好的,现在用户可以添加物品了,但如果添加完后无法删除,那还有什么用呢?他们会不断地添加物品,直到熵值达到顶峰,焦虑程度达到顶峰,最终送他们入土。不如我们加个删除按钮,救救几个人,好吗?
和之前一样,我们将添加一个函数来处理这个问题。下面的代码将允许用户在完成操作后删除列表项:
removeItem(item) {
// Put our list into an array
const list = this.state.list.slice();
// Check to see if item passed in matches item in array
list.some((el, i) => {
if (el === item) {
// If item matches, remove it from array
list.splice(i, 1);
return true;
}
});
// Set state to list
this.setState({
list: list
});
}
添加到构造函数
我们还需要将此函数添加到构造函数中。就像之前一样,我们可以这样做:
this.removeItem = this.removeItem.bind(this);
添加按钮以删除项目
为了方便用户删除项目,我们应该在 中添加一个删除按钮<li>
。下面的代码将实现这一点。
...
<ul>
{this.state.list.map(item => (
<li key={item}>
{item}
<span
className="delete"
onClick={() => this.removeItem(item)}
/>
</li>
))}
</ul>
...
现在我们可以在终端中运行yarn start
或npm run start
来查看更改。现在我们可以点击 x 从列表中删除该项目。成功了吗?
将列表变成组件
呼!目前为止一切都很好。
接下来,我们将把列表转换成一个拥有独立状态和方法的组件。为了简单起见,我将在 app.js 文件中创建该组件,但您也可以在单独的文件中创建并导入它。在 App 组件下方,创建一个名为 List 的类组件,代码如下:
class List extends React.Component {
render() {
return (
<div>
...
</div>
)
}
}
我们要渲染的代码只是我们的列表,因此请返回到我们的 App 组件并获取以下代码粘贴到我们的 List 组件的渲染函数中:
<ul>
{this.state.list.map(item => (
<li key={item}>
{item}
<span
className="delete"
onClick={() => this.removeItem(item)}
/>
</li>
))}
</ul>
将 App 组件中的代码替换为对我们的 List 组件的调用,如下所示:
<List items={this.state.list} delete={this.removeItem} />
上面的代码是做什么的?
这里,我们调用 List 组件并传入一些 props。propsitems
传入的是我们存储在状态中的列表。propsdelete
传入的是removeItem
我们创建的用于删除项目的方法。
为了使其正常工作,我们需要稍微修改一下 List 组件。首先,我们需要添加构造函数,以便接收 props。
class List extends React.Component {
constructor(props) {
super(props);
}
...
}
npm run start
如果我们使用或运行应用程序yarn start
,应用程序应该看起来和以前一样。我们仍然可以毫无问题地向列表中添加项目。如果我们点击删除按钮……哦哦……它不起作用。这是为什么呢?
我们在这个组件中没有调用任何方法removeItem
,所以点击按钮不会调用任何东西。幸运的是,我们有先见之明,将该方法作为 prop 传递给了组件。要恢复删除功能,我们只需将该按钮的代码改为以下内容:
<span className="delete" onClick={() => this.props.delete(item)} />
经过一些调整,我们现在在一个单独的组件中拥有了一个功能齐全的列表。现在,继续添加搜索功能。
在列表中创建过滤项
添加搜索栏的第一步是创建一个包含已过滤列表的数组。如果输入栏为空,则显示列表中的所有项目。如果搜索栏中有文本,则仅显示包含该文本的项目。
首先,我们将状态添加到 List 组件,并为其添加一个名为 filtered 的数组。以下代码演示了这一点。
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
filtered: []
}
}
}
一旦我们有地方放置过滤列表,我们就需要确保数据被放在那里。
温馨提示:这部分内容可能有点抽象,所以请尽量跟上我的思路。如果我解释得不够清楚,或者你遇到困难,请留言,我会尽力帮助你理解。
我们最初的任务列表位于 App 组件中,在本例中是父组件。此状态会被传递到 List 组件(在本例中是子组件),每次任务列表更新时,子组件都会重新渲染。你可能会问,告诉你这些有什么意义?我们需要在filtered
List 组件每次重新渲染时将数据传递到状态中。为此,我们将使用一些生命周期方法。
生命周期方法允许我们在组件渲染过程的各个阶段“挂钩”它。在本例中,我们将使用componentDidMount
和componentDidReceiveProps
。componentDidMount
允许我们在组件首次渲染时将数据放入filtered
数组中。另一方面,componentDidReceiveProps
将在传入组件的 props 发生变化时触发。
要将这些生命周期方法添加到我们的 List 组件,请在构造函数下方但在渲染函数之前添加以下代码:
componentDidMount() {
this.setState({
filtered: this.props.items
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.items
});
}
现在,如果我们.map()
将列表使用的函数更改为映射到filtered
列表上,而不是items
通过 props 传入列表,我们应该在前端看到相同的内容。
这有什么大不了的?重要的是,现在我们有了一个可以在不改变原始列表的情况下操作的列表。我们所要做的就是修改filter
状态,显示的项目也会反映这一点,但这样做并没有丢失原始列表。
创建搜索栏本身
我觉得,创建搜索栏的一个好起点就是……嗯……搜索栏本身。我们开始创建它吧。在 List 组件的 div 包装器内,添加一个输入框。
<div>
<input type="text" className="input" placeholder="Search..." />
<ul>
...
</ul>
</div>
太棒了!现在我们有搜索栏了。要是它真的能用就好了……
使用搜索栏进行搜索
我们有一个好看的搜索栏,但它除了看起来漂亮之外,其实没什么用。也许这已经足够好了,但我认为生活的意义远不止于拥有超级超级好看。我们来加点“大脑”吧。
首先,我们将handleChange
在生命周期方法之后添加一个名为 的方法。我们将传入e
一个代表事件的参数。在该方法内部,我们将创建两个变量,分别保存作为 props 传入的原始任务列表以及传入状态之前的筛选列表。
我们还需要添加一个 if 语句,以便该.filter()
函数仅在输入不为空时运行。否则,空的搜索栏将不会显示任何任务。因此,如果搜索栏不为空,我们需要运行该.filter()
函数并检查当前项是否包含搜索词。如果包含,则将该项返回到 newList 数组。
handleChange(e) {
// Variable to hold the original version of the list
let currentList = [];
// Variable to hold the filtered list before putting into state
let newList = [];
// If the search bar isn't empty
if (e.target.value !== "") {
// Assign the original list to currentList
currentList = this.props.items;
// Use .filter() to determine which items should be displayed
// based on the search terms
newList = currentList.filter(item => {
// change current item to lowercase
const lc = item.toLowerCase();
// change search term to lowercase
const filter = e.target.value.toLowerCase();
// check to see if the current list item includes the search term
// If it does, it will be added to newList. Using lowercase eliminates
// issues with capitalization in search terms and search content
return lc.includes(filter);
});
} else {
// If the search bar is empty, set newList to original task list
newList = this.props.items;
}
// Set the filtered state based on what our rules added to newList
this.setState({
filtered: newList
});
}
使用 时
.contains()
,它区分大小写。这可能会使用户在任务中搜索时更加困难,因为他们必须确切知道项目使用的大小写。为了解决这个问题,我们可以将函数中的所有内容改为小写,这样我们就知道搜索时所有内容将使用什么大小写。上面的注释也解释了这一点。
将方法添加到输入
快完成了!在使用该handleChange()
方法之前,我们需要将关键字绑定this
到它。在构造函数内部,状态之后,添加以下代码,将this
关键字绑定到该方法。
this.handleChange = this.handleChange.bind(this);
最后,我们可以为输入项添加一个事件处理程序,以便在内容发生更改时调用该方法。这最后一部分才是真正实现搜索功能的关键。将其添加onChange={this.handleChange}
到输入元素,使其如下所示:
<input type="text" className="input" onChange={this.handleChange} placeholder="Search..." />
结论
运行该应用程序现在应该允许您创建、删除和搜索任务。这里有很多文本,但实际上并不是那么复杂。
这对你有帮助吗?如果你遇到任何问题,请告诉我,我会更新本教程。我还在下面添加了包含完整代码的 Codepen,方便你试用或比较代码。
本文的原始版本可以在这里找到。
文章来源:https://dev.to/iam_timsmith/lets-build-a-search-bar-in-react-120j