mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
fix: Select组件开启autoComplete后请求竞态问题 Close: #8817
This commit is contained in:
parent
279f876f84
commit
4c16138005
@ -1199,7 +1199,7 @@ leftOptions 动态加载,默认 source 接口是返回 options 部分,而 le
|
||||
"name": "select1",
|
||||
"type": "select",
|
||||
"label": "选项自动补全(单选)",
|
||||
"autoComplete": "/api/mock2/options/autoComplete?term=${term}",
|
||||
"autoComplete": "/api/mock2/options/autoComplete3?delay=true&term=${term}",
|
||||
"placeholder": "请输入",
|
||||
"clearable": true
|
||||
},
|
||||
|
60
mock/cfc/mock/options/autoComplete3.js
Normal file
60
mock/cfc/mock/options/autoComplete3.js
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file 请求随机延迟的模拟接口
|
||||
*/
|
||||
|
||||
function generateRandomNames(num) {
|
||||
const candidate = ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy', 'Mike', 'Nina', 'Oliver', 'Polly', 'Queenie', 'Randy', 'Sybil', 'Trudy', 'Victor', 'Wendy', 'Xander', 'Yvonne', 'Zoe'];
|
||||
const randomNames = [];
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * candidate.length);
|
||||
randomNames.push(candidate[randomIndex]);
|
||||
}
|
||||
|
||||
return [...new Set(randomNames)].map(i => ({ value: i, label: i }));
|
||||
}
|
||||
|
||||
function generateRandomDelay() {
|
||||
const timeout = [0, 200, 500, 700, 1000, 2000];
|
||||
|
||||
const randomDelay = [];
|
||||
for (let i = 0; i < timeout.length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * timeout.length);
|
||||
randomDelay.push(timeout[randomIndex]);
|
||||
}
|
||||
|
||||
return randomDelay[0];
|
||||
}
|
||||
|
||||
function isPositiveInteger(input) {
|
||||
var pattern = /^\d+$/;
|
||||
return pattern.test(input);
|
||||
}
|
||||
|
||||
module.exports = function (req, res) {
|
||||
const labelField = req.query.labelField || 'label';
|
||||
const valueField = req.query.valueField || 'value';
|
||||
const term = req.query.term || '';
|
||||
const useDelay = req.query.delay || false;
|
||||
const total = isPositiveInteger(Number(req.query.total)) ? Number(req.query.total) : 20;
|
||||
const list = generateRandomNames(total).map(item => ({[labelField]: item.label, [valueField]: item.value}));
|
||||
const delay = generateRandomDelay();
|
||||
|
||||
const responseWrapper = () => {
|
||||
res.json({
|
||||
status: 0,
|
||||
msg: '',
|
||||
data: term
|
||||
? list.filter(function (item) {
|
||||
return term ? ~item.label.toLowerCase().indexOf(term.toLowerCase()) : false;
|
||||
})
|
||||
: list
|
||||
});
|
||||
}
|
||||
|
||||
if (useDelay) {
|
||||
return setTimeout(responseWrapper, delay);
|
||||
}
|
||||
|
||||
return responseWrapper();
|
||||
};
|
@ -32,6 +32,7 @@ export const Store = types
|
||||
})
|
||||
.actions(self => {
|
||||
let component: any = undefined;
|
||||
let fetchCancel: Function | null = null;
|
||||
|
||||
const load = flow(function* (
|
||||
env: RendererEnv,
|
||||
@ -40,8 +41,22 @@ export const Store = types
|
||||
config: WithRemoteConfigSettings = {}
|
||||
): any {
|
||||
try {
|
||||
if (fetchCancel) {
|
||||
fetchCancel?.('remote load request cancelled.');
|
||||
fetchCancel = null;
|
||||
self.fetching = false;
|
||||
}
|
||||
|
||||
if (self.fetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.fetching = true;
|
||||
const ret: Payload = yield env.fetcher(api, ctx);
|
||||
const ret: Payload = yield env.fetcher(api, ctx, {
|
||||
cancelExecutor: (executor: Function) => (fetchCancel = executor)
|
||||
});
|
||||
fetchCancel = null;
|
||||
|
||||
if (!isAlive(self)) {
|
||||
return;
|
||||
}
|
||||
@ -202,7 +217,6 @@ export function withRemoteConfig<P = any>(
|
||||
ComposedComponent as React.ComponentType<T>;
|
||||
static contextType = EnvContext;
|
||||
toDispose: Array<() => void> = [];
|
||||
|
||||
loadOptions = debounce(this.loadAutoComplete.bind(this), 250, {
|
||||
trailing: true,
|
||||
leading: false
|
||||
|
@ -931,7 +931,6 @@ export default class NestedSelectControl extends React.Component<
|
||||
translate: __,
|
||||
inline,
|
||||
searchable,
|
||||
autoComplete,
|
||||
selectedOptions,
|
||||
clearable,
|
||||
loading,
|
||||
|
@ -1,29 +1,28 @@
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import find from 'lodash/find';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {
|
||||
OptionsControl,
|
||||
OptionsControlProps,
|
||||
Option,
|
||||
FormOptionsControl,
|
||||
resolveEventData,
|
||||
str2function
|
||||
str2function,
|
||||
Api,
|
||||
ActionObject,
|
||||
normalizeOptions,
|
||||
isEffectiveApi,
|
||||
isApiOutdated,
|
||||
createObject,
|
||||
autobind
|
||||
} from 'amis-core';
|
||||
import {normalizeOptions} from 'amis-core';
|
||||
import find from 'lodash/find';
|
||||
import debouce from 'lodash/debounce';
|
||||
import {Api, ActionObject} from 'amis-core';
|
||||
import {isEffectiveApi, isApiOutdated} from 'amis-core';
|
||||
import {isEmpty, createObject, autobind, isMobile} from 'amis-core';
|
||||
|
||||
import {TransferDropDown, Spinner, Select, SpinnerExtraProps} from 'amis-ui';
|
||||
import {FormOptionsSchema, SchemaApi} from '../../Schema';
|
||||
import {Spinner, Select, SpinnerExtraProps} from 'amis-ui';
|
||||
import {BaseTransferRenderer, TransferControlSchema} from './Transfer';
|
||||
import {TransferDropDown} from 'amis-ui';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
import type {SchemaClassName} from '../../Schema';
|
||||
import type {TooltipObject} from 'amis-ui/lib/components/TooltipWrapper';
|
||||
import type {PopOverOverlay} from 'amis-ui/lib/components/PopOverContainer';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
|
||||
/**
|
||||
* Select 下拉选择框。
|
||||
@ -191,7 +190,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
super(props);
|
||||
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
this.lazyloadRemote = debouce(this.loadRemote.bind(this), 250, {
|
||||
this.lazyloadRemote = debounce(this.loadRemote.bind(this), 250, {
|
||||
trailing: true,
|
||||
leading: false
|
||||
});
|
||||
@ -216,6 +215,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unHook && this.unHook();
|
||||
this.fetchCancel = null;
|
||||
}
|
||||
|
||||
inputRef(ref: any) {
|
||||
@ -324,6 +324,8 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
onChange?.(newValue);
|
||||
}
|
||||
|
||||
fetchCancel: Function | null = null;
|
||||
|
||||
async loadRemote(input: string) {
|
||||
const {
|
||||
autoComplete,
|
||||
@ -356,12 +358,22 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.fetchCancel) {
|
||||
this.fetchCancel?.('autoComplete request cancelled.');
|
||||
this.fetchCancel = null;
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const ret = await env.fetcher(autoComplete, ctx);
|
||||
const ret = await env.fetcher(autoComplete, ctx, {
|
||||
cancelExecutor: (executor: Function) => (this.fetchCancel = executor)
|
||||
});
|
||||
this.fetchCancel = null;
|
||||
|
||||
const options = (ret.data && (ret.data as any).options) || ret.data || [];
|
||||
const combinedOptions = this.mergeOptions(options);
|
||||
|
||||
let options = (ret.data && (ret.data as any).options) || ret.data || [];
|
||||
let combinedOptions = this.mergeOptions(options);
|
||||
setOptions(combinedOptions);
|
||||
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user