Ruby on Rails+Stimulus中Slim Select防抖搜索实现遇问题求助
Slim Select防抖搜索问题(Rails + Stimulus JS)
背景
我在Ruby on Rails项目中搭配Stimulus JS使用Slim Select已有6个月。本季度团队做性能优化,已将搜索功能改为Slim Select的Ajax请求且可正常运行,现在需要实现防抖搜索以避免每次按键都请求服务器,但尝试后遇到问题。
原控制器代码
import { Controller } from "@hotwired/stimulus" import SlimSelect from 'slim-select' import debounce from "debounce"; import { authToken } from '../global' export default class extends Controller { connect() { this.initializeInstance(); } disconnect() { this.slimSelect.destroy(); } initializeInstance() { this.searchIt = this.searchIt.bind(this); const options = { select: this.element, settings: { hideSelectedOption: true, placeholderText: this.placeholder, allowDeselect: true, maxValuesShown: 100, } } if (this.optionsUrl) { options.settings.searchingText = 'Searching...' options.events = { search: this.searchIt, afterOpen: () => { this.slimSelect.search(''); } } } this.slimSelect = new SlimSelect(options) // Auto-populate the select with data from the prepopulateUrl // This comes in handy for applying filters to the select if (this.prepopulateUrl) { fetch(this.prepopulateUrl, { method: 'GET', headers: { 'Accept': 'application/json', 'X-CSRF-Token': authToken(), }, }) .then(response => response.json()) .then(data => { const fieldMappings = this.fieldMappings; const options = data.map(item => ({ text: item[fieldMappings.text], value: item[fieldMappings.value] })); const selectedValues = data.map(item => item[fieldMappings.value]); this.slimSelect.setData(options); this.slimSelect.setSelected(selectedValues); }) .catch(error => { console.error('Error:', error); }); } } reinitializeInstance() { this.disconnect(); this.initializeInstance(); } get optionsUrl () { return this.element.dataset.optionsUrl } get placeholder () { return this.element.getAttribute('placeholder') } get fieldMappings() { return JSON.parse(this.element.dataset.fieldsMapping) } get prepopulateUrl() { return this.element.dataset.prepopulateUrl } searchIt (search, currentData) { return new Promise((resolve, reject) => { const url = this.optionsUrl.replace('${search}', search); fetch(url, { method: 'GET', headers: { 'Accept': 'application/json', 'X-CSRF-Token': authToken(), }, }) .then((response) => response.json()) .then(data => { const fieldMappings = this.fieldMappings; // Take the data and create an array of options // excluding any that are already selected in currentData const options = data.filter((obj) => { return !currentData.some((optionData) => { return optionData.value === obj[fieldMappings.value] }) }) .map((item) => { return { text: item[fieldMappings.text], value: item[fieldMappings.value], } }) resolve([...options, ...currentData]); }) }); } }
尝试的防抖实现
我将search事件替换为防抖函数,输入搜索内容时功能正常,但首次打开搜索框会报错:
if (this.optionsUrl) { options.settings.searchingText = 'Searching...' options.events = { search: debounce(this.searchIt, 300), afterOpen: () => { this.slimSelect.search(''); } } }
错误现象
首次打开搜索框时出现如下错误:
我也尝试在afterOpen中使用debounce(this.slimSelect.search(''), 300),但问题仍未解决。
解决方案
问题根源在于:
- Slim Select的
search事件要求返回Promise,但默认的防抖函数不会传递原函数的Promise返回值,首次打开触发search('')时,Slim Select无法获取到预期的Promise,导致报错。 afterOpen中调用this.slimSelect.search('')会触发防抖后的搜索函数,延迟执行的结果无法被Slim Select及时获取。
可以通过以下方式修复:
方案1:使用支持leading参数的防抖库
如果你的debounce库(如lodash.debounce或新版debounce包)支持leading配置,让首次调用立刻执行,同时确保防抖函数返回原函数的Promise:
initializeInstance() { // 绑定防抖函数,设置leading: true让首次调用立即执行 this.debouncedSearch = debounce(this.searchIt.bind(this), 300, { leading: true }); const options = { select: this.element, settings: { hideSelectedOption: true, placeholderText: this.placeholder, allowDeselect: true, maxValuesShown: 100, } } if (this.optionsUrl) { options.settings.searchingText = 'Searching...' options.events = { search: (search, currentData) => { // 确保返回防抖函数的Promise结果 return this.debouncedSearch(search, currentData); }, afterOpen: () => { // 首次打开直接调用原searchIt,避免防抖延迟 this.searchIt('', this.slimSelect.data).then(data => { this.slimSelect.setData(data); }); } } } this.slimSelect = new SlimSelect(options); // 原预填充代码保持不变 }
方案2:自定义支持Promise的防抖函数
如果你的debounce库不支持leading参数,自己实现一个能传递Promise的防抖函数:
// 自定义防抖函数,支持Promise返回值和首次立即执行 function debouncePromise(func, wait) { let timeoutId; return function(...args) { return new Promise((resolve, reject) => { if (timeoutId) clearTimeout(timeoutId); // 首次调用立即执行 if (!timeoutId) { func.apply(this, args).then(resolve).catch(reject); } timeoutId = setTimeout(() => { func.apply(this, args).then(resolve).catch(reject); timeoutId = null; }, wait); }); }; }
然后在控制器中导入并使用:
// 替换原debounce导入 // import debounce from "debounce"; import debouncePromise from './debounce-promise'; // ... initializeInstance() { this.debouncedSearch = debouncePromise(this.searchIt.bind(this), 300); // 后续事件配置同方案1 }
这样修改后,首次打开搜索框会立刻加载初始数据,输入搜索内容时触发防抖逻辑避免频繁请求,同时保证Slim Select能正确获取到Promise返回值,不会出现报错。
内容的提问来源于stack exchange,提问作者mohsin_jamshaid
相关产品推荐
相关产品推荐

