2019: prueba ganchos + promesa de rebote
Esta es la versión más actualizada de cómo resolvería este problema. Yo usaría:
Este es un cableado inicial, pero está componiendo bloques primitivos por su cuenta, y puede hacer su propio gancho personalizado para que solo necesite hacer esto una vez.
// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {
// Handle the input text state
const [inputText, setInputText] = useState('');
// Debounce the original search async function
const debouncedSearchFunction = useConstant(() =>
AwesomeDebouncePromise(searchFunction, 300)
);
// The async callback is run each time the text changes,
// but as the search function is debounced, it does not
// fire a new request on each keystroke
const searchResults = useAsync(
async () => {
if (inputText.length === 0) {
return [];
} else {
return debouncedSearchFunction(inputText);
}
},
[debouncedSearchFunction, inputText]
);
// Return everything needed for the hook consumer
return {
inputText,
setInputText,
searchResults,
};
};
Y luego puedes usar tu gancho:
const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))
const SearchStarwarsHeroExample = () => {
const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
return (
<div>
<input value={inputText} onChange={e => setInputText(e.target.value)} />
<div>
{searchResults.loading && <div>...</div>}
{searchResults.error && <div>Error: {search.error.message}</div>}
{searchResults.result && (
<div>
<div>Results: {search.result.length}</div>
<ul>
{searchResults.result.map(hero => (
<li key={hero.name}>{hero.name}</li>
))}
</ul>
</div>
)}
</div>
</div>
);
};
Encontrará este ejemplo ejecutándose aquí y debe leer la documentación de react-async-hook para obtener más detalles.
2018: prueba la promesa de rebote
A menudo queremos eliminar las llamadas a la API para evitar inundar el backend con solicitudes inútiles.
En 2018, trabajar con devoluciones de llamada (Lodash / Underscore) se siente mal y propenso a errores. Es fácil encontrar problemas repetitivos y de simultaneidad debido a que las llamadas API se resuelven en un orden arbitrario.
He creado una pequeña biblioteca con React en mente para resolver tus dolores: awesome-debounce-promise .
Esto no debería ser más complicado que eso:
const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));
const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);
class SearchInputAndResults extends React.Component {
state = {
text: '',
results: null,
};
handleTextChange = async text => {
this.setState({ text, results: null });
const result = await searchAPIDebounced(text);
this.setState({ result });
};
}
La función antirrebote asegura que:
- Las llamadas a la API serán eliminadas
- la función eliminada siempre devuelve una promesa
- solo se resolverá la promesa devuelta de la última llamada
this.setState({ result });
sucederá un solo por llamada API
Eventualmente, puede agregar otro truco si su componente se desmonta:
componentWillUnmount() {
this.setState = () => {};
}
Tenga en cuenta que los Observables (RxJS) también pueden ser una excelente opción para eliminar las entradas, pero es una abstracción más poderosa que puede ser más difícil de aprender / usar correctamente.
<2017: ¿todavía quieres usar la devolución de llamada?
La parte importante aquí es crear una sola función sin rebote (o estrangulada) por instancia de componente . No desea volver a crear la función antirrebote (o acelerador) cada vez, y no desea que ninguna de las instancias múltiples comparta la misma función antirrebote.
No estoy definiendo una función de eliminación de rebotes en esta respuesta, ya que no es realmente relevante, pero esta respuesta funcionará perfectamente bien con _.debounce
subrayado o lodash, así como con cualquier función de eliminación de rebotes proporcionada por el usuario.
BUENA IDEA:
Debido a que las funciones sin rebote tienen estado, tenemos que crear una función sin rebote por instancia de componente .
ES6 (propiedad de clase) : recomendado
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6 (constructor de clase)
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.method = debounce(this.method.bind(this),1000);
}
method() { ... }
}
ES5
var SearchBox = React.createClass({
method: function() {...},
componentWillMount: function() {
this.method = debounce(this.method.bind(this),100);
},
});
Ver JsFiddle : 3 instancias están produciendo 1 entrada de registro por instancia (eso hace 3 globalmente).
No es Buena idea:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
No funcionará, porque durante la creación del objeto de descripción de clase, this
no se crea el objeto en sí mismo. this.method
no devuelve lo que espera porque el this
contexto no es el objeto en sí (que en realidad aún no existe, por cierto, ya que se está creando).
No es Buena idea:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
Esta vez está creando efectivamente una función sin rebote que llama a su this.method
. ¡El problema es que lo está recreando en cada debouncedMethod
llamada, por lo que la función antirrebote recién creada no sabe nada sobre las llamadas anteriores! Debe volver a usar la misma función de eliminación de rebote con el tiempo o la eliminación de rebote no sucederá.
No es Buena idea:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
Esto es un poco complicado aquí.
¡Todas las instancias montadas de la clase compartirán la misma función sin rebote, y la mayoría de las veces esto no es lo que quieres! Consulte JsFiddle : 3 instancias solo producen 1 entrada de registro a nivel mundial.
Debe crear una función sin rebote para cada instancia de componente , y no una sola función sin rebote a nivel de clase, compartida por cada instancia de componente.
Cuida la agrupación de eventos de React
Esto está relacionado porque a menudo queremos eliminar el rebote o limitar los eventos DOM.
En React, los objetos de evento (es decir, SyntheticEvent
) que recibe en devoluciones de llamada se agrupan (esto ahora está documentado ). Esto significa que después de que se haya llamado la devolución de llamada del evento, el SyntheticEvent que reciba se volverá a colocar en el grupo con atributos vacíos para reducir la presión del GC.
Por lo tanto, si accede a las SyntheticEvent
propiedades de forma asincrónica a la devolución de llamada original (como puede ser el caso si acelera / elimina el rebote), las propiedades a las que accede pueden borrarse. Si desea que el evento nunca se vuelva a colocar en el grupo, puede usar el persist()
método.
Sin persistencia (comportamiento predeterminado: evento agrupado)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
El segundo (asíncrono) se imprimirá hasNativeEvent=false
porque las propiedades del evento se han limpiado.
Con persistir
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
El segundo (asíncrono) se imprimirá hasNativeEvent=true
porque le persist
permite evitar volver a colocar el evento en el grupo.
Puede probar estos 2 comportamientos aquí: JsFiddle
Lea la respuesta de Julen para ver un ejemplo de uso persist()
con una función de aceleración / antirrebote.
debounce
. aquí, cuandoonChange={debounce(this.handleOnChange, 200)}/>
invocarádebounce function
cada vez. pero, de hecho, lo que necesitamos es invocar la función que devolvió la función antirrebote.