如何使用 JavaScript 创建具有多个可选参数的搜索?
Posted
技术标签:
【中文标题】如何使用 JavaScript 创建具有多个可选参数的搜索?【英文标题】:How can I create a search with multiple, optional, parameters using JavaScript? 【发布时间】:2019-11-18 11:58:08 【问题描述】:我目前有什么“作品”,但是每个参数都取决于最后一个。我的目标是允许用户使用任意数量的搜索字段来过滤帖子,但我似乎无法理解如何实际执行它。
搜索字段的代码:
import React from "react";
import Input, DropDown from "../Form";
import "./index.css";
function Sidebar(props)
return (
<div className="sidebar-container">
<p>Search Posts: props.carMake</p>
<div className="field-wrap">
<Input
value=props.carMake
onChange=props.handleInputChange
name="carMake"
type="text"
placeholder="Manufacturer"
/>
</div>
<div className="field-wrap">
<Input
value=props.carModel
onChange=props.handleInputChange
disabled=!props.carMake
name="carModel"
type="text"
placeholder="Model"
/>
</div>
<div className="field-wrap">
<Input
disabled=!props.carModel || !props.carMake
value=props.carYear
onChange=props.handleInputChange
name="carYear"
type="text"
placeholder="Year"
/>
</div>
<div className="field-wrap">
<DropDown
//disabled=!props.carModel || !props.carMake || !props.carYear
value=props.category
onChange=props.handleInputChange
name="category"
type="text"
id="category"
>
<option>Select a category...</option>
<option>Brakes</option>
<option>Drivetrain</option>
<option>Engine</option>
<option>Exhaust</option>
<option>Exterior</option>
<option>Intake</option>
<option>Interior</option>
<option>Lights</option>
<option>Suspension</option>
<option>Wheels & Tires</option>
</DropDown>
</div>
</div>
);
export default Sidebar;
这是父组件的代码(实际过滤数据的地方):
import React, Component from 'react';
import Sidebar from '../../components/Sidebar';
import API from '../../utils/API';
import PostContainer from '../../components/PostContainer';
import withRouter from 'react-router';
import axios from 'axios';
import './index.css';
class Posts extends Component
constructor(props)
super(props);
this.state =
posts: [],
carMake: '',
carModel: '',
carYear: '',
category: 'Select A Category...'
;
this.signal = axios.CancelToken.source();
componentDidMount()
API.getAllPosts(
cancelToken: this.signal.token
)
.then(resp =>
this.setState(
posts: resp.data
);
)
.catch(function(error)
if (axios.isCancel(error))
console.log('Error: ', error.message);
else
console.log(error);
);
componentWillUnmount()
this.signal.cancel('Api is being canceled');
handleInputChange = event =>
const name, value = event.target;
this.setState(
[name]: value
);
;
handleFormSubmit = event =>
event.preventDefault();
console.log('Form Submitted');
;
render()
const carMake, carModel, carYear, category, posts = this.state;
const filterMake = posts.filter(
post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1
);
const filterModel = posts.filter(
post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1
);
const filterYear = posts.filter(
post => post.carYear.toString().indexOf(carYear.toString()) !== -1
);
const filterCategory = posts.filter(
post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1
);
return (
<div className='container-fluid'>
<div className='row'>
<div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
<Sidebar
carMake=carMake
carModel=carModel
carYear=carYear
category=category
handleInputChange=this.handleInputChange
handleFormSubmit=event =>
event.preventDefault();
this.handleFormSubmit(event);
/>
</div>
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
carMake && carModel && carYear && category
? filterCategory.map(post => (
<PostContainer post=post key=post.id />
))
: carMake && carModel && carYear
? filterYear.map(post => (
<PostContainer post=post key=post.id />
))
: carMake && carModel
? filterModel.map(post => (
<PostContainer post=post key=post.id />
))
: carMake
? filterMake.map(post => (
<PostContainer post=post key=post.id />
))
: posts.map(post => <PostContainer post=post key=post.id />)
</div>
</div>
</div>
);
export default withRouter(Posts);
API返回的数据是对象数组的形式,如下:
[
"id":4,
"title":"1995 Toyota Supra",
"desc":"asdf",
"itemImg":"https://i.imgur.com/zsd7N8M.jpg",
"price":32546,
"carYear":1995,
"carMake":"Toyota",
"carModel":"Supra",
"location":"Phoenix, AZ",
"category":"Exhaust",
"createdAt":"2019-07-09T00:00:46.000Z",
"updatedAt":"2019-07-09T00:00:46.000Z",
"UserId":1
,
"id":3,
"title":"Trash",
"desc":"sdfasdf",
"itemImg":"https://i.imgur.com/rcyWOQG.jpg",
"price":2345,
"carYear":2009,
"carMake":"Yes",
"carModel":"Ayylmao",
"location":"asdf",
"category":"Drivetrain",
"createdAt":"2019-07-08T23:33:04.000Z",
"updatedAt":"2019-07-08T23:33:04.000Z",
"UserId":1
]
从上面可以看出,我试图只注释掉下拉列表的“禁用”属性,但这会导致它完全停止作为过滤器工作,并返回所有结果,无论选择如何。这是由于我检查每个过滤器的三元运算符混乱造成的。有没有更好的方法可以做到这一点?
【问题讨论】:
你能发布一个codeandbox/stackblitz链接来重现这个问题吗? 【参考方案1】:即使@Nina Lisitsinskaya 的回答是正确的,我也不会有大量的if
s 列表,而我只是完成了过滤连接。
这样添加另一种过滤方式更容易且可读性强。解决方案虽然类似。
render()
const carMake = '', carModel = '', carYear = '', category = '', posts = this.state;
let filtered = [...posts];
filtered = filtered
.filter(post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1)
.filter(post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1)
.filter(post => post.carYear.toString().indexOf(carYear.toString()) !== -1)
.filter(post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1)
...
当然以后你要在 JSX 表达式中使用类似 this 的filtered
,否则什么都没有。
...
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
filtered.map(post => <PostContainer post=post key=post.id />)
</div>
【讨论】:
【参考方案2】:这可以通过一个过滤器功能来实现。
render()
const carMake, carModel, carYear, category, posts = this.state;
const filteredPost = posts.filter(post =>
post.category.toLowerCase().includes(category.toLowerCase()) &&
post.carYear === carYear &&
post.carModel.toLowerCase().includes(carModel.toLowerCase()) &&
post.carMake.toLowerCase().includes(carMake.toLowerCase())
);
return
...
filteredPost.map(post => <PostContainer post=post key=post.id />);
列表中只有一个循环。没有很多 if 和 else 或三元运算符的麻烦。只是根据需要排序过滤方式。
【讨论】:
【参考方案3】:我认为你可以使用 Lodash_.filter
收集方法来帮助你:
Lodash 文档:https://lodash.com/docs/4.17.15#filter
多输入搜索
/*
* `searchOption` is something like: carMake: 'Yes', carYear: 2009
*/
function filterData(data = [], searchOption = )
let filteredData = Array.from(data); // clone data
// Loop through every search key-value and filter them
Object.entries(searchOption).forEach(([key, value]) =>
// Ignore `undefined` value
if (value)
filteredData = _.filter(filteredData, [key, value]);
);
// Return filtered data
return filteredData;
渲染方法
return (
<div className='container-fluid'>
<div className='row'>
<div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
<Sidebar
carMake=carMake
carModel=carModel
carYear=carYear
category=category
handleInputChange=this.handleInputChange
handleFormSubmit=event =>
event.preventDefault();
this.handleFormSubmit(event);
/>
</div>
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
filterData(post, carMake, carModel, carYear, category ).map(post => (
<PostContainer post=post key=post.id />
))
</div>
</div>
</div>
);
单输入搜索
或者你可以有一个单一的搜索输入字段,这将过滤整个数据
function filterData(data = [], searchString = '')
return _.filter(data, obj =>
// Go through each set and see if any of the value contains the search string
return Object.values(obj).some(value =>
// Stringify the value (so that we can search numbers, boolean, etc.)
return `$value`.toLowerCase().includes(searchString.toLowerCase()));
);
);
渲染方法
return (
<div className='container-fluid'>
<div className='row'>
<div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
<input
onChange=this.handleInputChange
value=this.state.searchString
/>
</div>
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
filterData(posts, searchString).map(post => <PostContainer post=post key=post.id />)
</div>
</div>
</div>
);
【讨论】:
【参考方案4】:尝试以下操作(代码 cmets 中的说明):
// 1. don't default category to a placeholder.
// If the value is empty it will default to your empty option,
// which shows the placeholder text in the dropdown.
this.state =
posts: [],
carMake: '',
carModel: '',
carYear: '',
category: ''
// 2. write a method to filter your posts and do the filtering in a single pass.
getFilteredPosts = () =>
const posts, ...filters = this.state
// get a set of filters that actually have values
const activeFilters = Object.entries(filters).filter(([key, value]) => !!value)
// return all posts if no filters
if (!activeFilters.length) return posts
return posts.filter(post =>
// check all the active filters
// we're using a traditional for loop so we can exit as soon as the first check fails
for (let i; i > activeFilters.length; i++)
const [key, value] = activeFilters[i]
// bail on the first failure
if (post[key].toLowerCase().indexOf(value.toLowerCase()) < 0)
return false
// all filters passed
return true
)
// 3. Simplify render
render()
// destructure filters so you can just spread them into SideBar
const posts, ...filters = this.state
const filteredPosts = this.getFilteredPosts()
return (
<div className='container-fluid'>
<div className='row'>
<div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
<Sidebar
...filters
handleInputChange=this.handleInputChange
handleFormSubmit=this.handleFormSubmit
/>
</div>
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
filteredPosts.map(post => <PostContainer post=post key=post.id />)
</div>
</div>
</div>
)
要考虑的另一件事是 PostContainer
正在传递一个作为对象的单个 prop post
。如果您传播该帖子对象使其成为道具,您可能可以在该组件中简化道具访问相当多:
filteredPosts.map(post => <PostContainer key=post.id ...post />)
然后在PostContainer
中,props.post.id
将变为props.id
。 props api 变得更简单,组件将更容易优化(如果有必要的话)。
【讨论】:
【参考方案5】:您永远不应该在您的 render
方法中进行此类计算 - 它应该与干净计算的 state/props
一起使用。基本上过滤应该发生在你的backend
上,但是如果你想过滤frontend
你应该把过滤逻辑移动到服务方法,像这样:
function getPosts( cancelToken, filter )
// first fetch your posts
// const posts = ...
const carMake, carModel, carYear, category = filter;
let filtered = [];
for (let i = 0; i < posts.length; i++)
const post = posts[i];
let add = true;
if (carMake && add)
add = post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1;
if (carModel && add)
add = post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1;
if (carYear && add)
add = post.carYear.toLowerCase().indexOf(carYear.toLowerCase()) !== -1;
if (category && add)
add = post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1;
if (add)
filtered.push(post);
return filtered;
For loop
被使用是因为使用这种方法你只迭代一次posts
。如果你不打算改变你的服务方法,至少在你解析的promise
componentDidMount
中添加这个帖子过滤,但我强烈建议不要在render
方法中做这样的事情。
【讨论】:
【参考方案6】:根本没有必要在 JSX 中使用可怕的巨大的三元运算符。首先,您可以使用每个过滤器按顺序过滤集合:
render()
const carMake, carModel, carYear, category, posts = this.state;
let filtered = [...posts];
if (carMake)
filtered = filtered.filter(post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1);
if (carModel)
filtered = filtered.filter(post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1);
if (carYear)
filtered = filtered.filter(post => post.carYear.toString().indexOf(carYear.toString()) !== -1);
if (category)
filtered = filtered.filter(post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1);
...
那么你可以在 JSX 表达式中使用filtered
:
...
<div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
filtered.map(post => <PostContainer post=post key=post.id />)
</div>
【讨论】:
以上是关于如何使用 JavaScript 创建具有多个可选参数的搜索?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 2 个位置参数在 argparse 上打印帮助界面?