Реагировать Родной Предотвратить Двойное нажатие
У меня есть TouchableHighlight
обертывание Text
блокировать, что при нажатии открывает новую сцену (которую я использую реагировать-родной-маршрутизатор-поток).
Все работает нормально, за исключением того, что если вы быстро нажмете на TouchableHighlight
, сцена может отрендериться дважды.
Я бы хотел, чтобы пользователь не мог быстро нажать эту кнопку.
Каков наилучший способ сделать это в Native? Я заглянул в систему Gesture Responder System, но там нет примеров или чего-либо в этом роде, что, если вы новичок, как я, сбивает с толку.
12 ответов
То, что вы пытаетесь сделать, - это ограничить количество обратных вызовов, так что они будут работать только ОДИН РАЗ.
Это называется дросселированием, и вы можете использовать underscore
для этого:вот как:
_.throttle(
this.thisWillRunOnce.bind(this),
200, // no new clicks within 200ms time window
);
Вот как выглядит мой компонент реакции.
class MyComponent extends React.Component {
constructor(props) {
super(props);
_.throttle(
this.onPressThrottledCb.bind(this),
200, // no new clicks within 200ms time window
);
}
onPressThrottledCb() {
if (this.props.onPress) {
this.props.onPress(); // this only runs once per 200 milliseconds
}
}
render() {
return (
<View>
<TouchableOpacity onPress={this.onPressThrottledCb}>
</TouchableOpacity>
</View>
)
}
}
Я надеюсь, это поможет вам. Если вы хотите узнать больше, проверьте эту ветку.
Следующее работает, предотвращая маршрутизацию к одному и тому же маршруту дважды:
import { StackNavigator, NavigationActions } from 'react-navigation';
const App = StackNavigator({
Home: { screen: HomeScreen },
Details: { screen: DetailsScreen },
});
// Prevents double taps navigating twice
const navigateOnce = (getStateForAction) => (action, state) => {
const { type, routeName } = action;
return (
state &&
type === NavigationActions.NAVIGATE &&
routeName === state.routes[state.routes.length - 1].routeName
) ? state : getStateForAction(action, state);
};
App.router.getStateForAction = navigateOnce(App.router.getStateForAction);
Я делаю это так:
link(link) {
if(!this.state.disabled) {
this.setState({disabled: true});
// go link operation
this.setState({disabled: false});
}
}
render() {
return (
<TouchableHighlight onPress={() => this.link('linkName')}>
<Text>Go link</Text>
</TouchableHighlight>
);
}
Возможно, вы могли бы использовать новую функцию отключения, введенную для сенсорных элементов в 0.22? Я думаю что-то вроде этого:
Составная часть
<TouchableHighlight ref = {component => this._touchable = component}
onPress={() => this.yourMethod()}/>
метод
yourMethod() {
var touchable = this._touchable;
touchable.disabled = {true};
//what you actually want your TouchableHighlight to do
}
Я сам не пробовал. Так что я не уверен, что это работает.
Я исправил этот метод lodash ниже,
Шаг 1
import { debounce } from 'lodash';
Шаг 2
Поместите этот код в конструктор
this.loginClick = debounce(this.loginClick .bind(this), 1000, {
leading: true,
trailing: false,
});
Шаг 3
Напишите на своей кнопке onPress вот так
onPress={() => this.props.skipDebounce ? this.props.loginClick : this.loginClick ()}
Благодарность,
Вы можете отразить щелчок по фактическим методам приемника, особенно если вы имеете дело с состоянием для визуальных эффектов.
_onRefresh() {
if (this.state.refreshing)
return
this.setState({refreshing: true});
После прочтения нескольких тем GitHub, статей SO и опробования большинства решений я пришел к следующим выводам:
Предоставление дополнительного
key
Параметр для выполнения "идемпотентных толчков" не работает последовательно на данный момент. https://github.com/react-navigation/rfcs/issues/16С помощью
debounce
значительно замедляет UX. Навигация происходит только X мс после того, как пользователь нажал кнопку в последний раз. X должен быть достаточно большим, чтобы преодолеть время, когда могут произойти двойные нажатия. Что может быть что-то от 100-600 мс на самом деле.С помощью
_.throttle
не работал для меня. Он сохранил вызов функции удушения и выполнил его после того, как закончился таймер, что привело к задержке двойного нажатия.
Я подумывал переехать в react-native-navigation
но, видимо, проблема кроется глубже, и они тоже это испытывают.
Итак, на данный момент я создал свой собственный хак, который меньше всего мешает моему коду:
const preventDoubleTapHack = (component: any, doFunc: Function) => {
if (!component.wasClickedYet__ULJyRdAvrHZvRrT7) {
// eslint-disable-next-line no-param-reassign
component.wasClickedYet__ULJyRdAvrHZvRrT7 = true;
setTimeout(() => {
// eslint-disable-next-line no-param-reassign
component.wasClickedYet__ULJyRdAvrHZvRrT7 = false;
}, 700);
doFunc();
}
};
где бы мы ни находились
this.props.navigation.navigate('MyRoute');
делать
preventDoubleTapHack(this, () => this.props.navigation.navigate('MyRoute');
Красивая.
Это сработало для меня как обходной путь
import React from 'react';
import {TouchableOpacity } from 'react-native';
export default SingleClickTouchableOpacity = (props) => {
let buttonClicked = false
return(
<TouchableOpacity {...props} onPress={() => {
if(buttonClicked){
return
}
props.onPress();
buttonClicked = true
setTimeout(() => {
buttonClicked = false
}, 1000);
}}>
{props.children}
</TouchableOpacity>
)
}
Если вы используете react-navigation
Вы можете поставить key
собственность на navigate
чтобы гарантировать, что в стек будет добавлен только один экземпляр.
через https://github.com/react-navigation/react-navigation/issues/271
Не использовал функцию отключения, setTimeout или не устанавливал лишних вещей.
Таким образом, код выполняется без задержек. Я не избегал двойных нажатий, но я обеспечил выполнение кода только один раз.
Я использовал возвращенный объект из TouchableOpacity, описанный в документации https://reactnative.dev/docs/pressevent, и переменную состояния для управления отметками времени. lastTime - это переменная состояния, инициализированная 0.
const [lastTime, setLastTime] = useState(0);
...
<TouchableOpacity onPress={async (obj) =>{
try{
console.log('Last time: ', obj.nativeEvent.timestamp);
if ((obj.nativeEvent.timestamp-lastTime)>1500){
console.log('First time: ',obj.nativeEvent.timestamp);
setLastTime(obj.nativeEvent.timestamp);
//your code
SplashScreen.show();
await dispatch(getDetails(item.device));
await dispatch(getTravels(item.device));
navigation.navigate("Tab");
//end of code
}
else{
return;
}
}catch(e){
console.log(e);
}
}}>
Я использую асинхронную функцию для обработки отправлений, которые на самом деле извлекают данные, в конце концов я в основном перехожу на другой экран.
Я распечатываю первый и последний раз между касаниями. Я выбираю, чтобы между ними существовала разница не менее 1500 мс, и избегаю двойного нажатия паразитов.
Вы можете использовать debounce очень простым способом
import debounce from 'lodash/debounce';
componentDidMount() {
this.yourMethod= debounce(this.yourMethod.bind(this), 500);
}
yourMethod=()=> {
//what you actually want your TouchableHighlight to do
}
<TouchableHighlight onPress={this.yourMethod}>
...
</TouchableHighlight >
Я исправил эту ошибку, создав модуль, который вызывает функцию только один раз за прошедший интервал.
Пример: если вы хотите перейти от Home -> About, и вы дважды нажимаете кнопку About, скажем, 400 мс.
navigateToAbout = () => dispatch(NavigationActions.navigate({routeName: 'About'}))
const pressHandler = callOnce(navigateToAbout,400);
<TouchableOpacity onPress={pressHandler}>
...
</TouchableOpacity>
The module will take care that it calls navigateToAbout only once in 400 ms.
Вот ссылка на модуль NPM: https://www.npmjs.com/package/call-once-in-interval