tutorial react logo examples ejemplos create app javascript reactjs

javascript - logo - react js vs angular



Realizar rebote en React.js. (16)

¿Cómo se realiza el rebote en React.js?

Quiero rebotar el handleOnChange.

Intenté con debounce(this.handleOnChange, 200) pero no funciona.

function debounce(fn, delay) { var timer = null; return function () { var context = this, args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); }; } var SearchBox = React.createClass({ render:function () { return ( <input type="search" name="p" onChange={this.handleOnChange}/> ); }, handleOnChange: function (event) { //make ajax call } });


Componentes no controlados

Puedes usar el documented .

Un ejemplo sigue usando el _.debounce() guión _.debounce() :

var SearchBox = React.createClass({ componentWillMount: function () { this.delayedCallback = _.debounce(function (event) { // `event.target` is accessible now }, 1000); }, onChange: function (event) { event.persist(); this.delayedCallback(event); }, render: function () { return ( <input type="search" onChange={this.onChange} /> ); } });

Editar: ver este JSFiddle

Componentes controlados

Actualización: el ejemplo anterior muestra un componente no controlado . Uso elementos controlados todo el tiempo, así que aquí hay otro ejemplo de lo anterior, pero sin usar el "truco" event.persist() .

Un JSFiddle también está disponible . Ejemplo sin subrayado

var SearchBox = React.createClass({ getInitialState: function () { return { query: this.props.query }; }, componentWillMount: function () { this.handleSearchDebounced = _.debounce(function () { this.props.handleSearch.apply(this, [this.state.query]); }, 500); }, onChange: function (event) { this.setState({query: event.target.value}); this.handleSearchDebounced(); }, render: function () { return ( <input type="search" value={this.state.query} onChange={this.onChange} /> ); } }); var Search = React.createClass({ getInitialState: function () { return { result: this.props.query }; }, handleSearch: function (query) { this.setState({result: query}); }, render: function () { return ( <div id="search"> <SearchBox query={this.state.result} handleSearch={this.handleSearch} /> <p>You searched for: <strong>{this.state.result}</strong></p> </div> ); } }); React.render(<Search query="Initial query" />, document.body);

Edición: ejemplos actualizados y JSFiddles para Reaccionar 0.12

Edición: ejemplos actualizados para abordar el problema planteado por Sebastien Lorber

Edición: actualizado con jsfiddle que no usa el guión bajo y usa la función de rebote de JavaScript simple.


En 2018: tratar de prometer el rebote.

A menudo, queremos rechazar las llamadas de API para evitar inundar el backend con solicitudes inútiles.

En 2018, aún trabajando con devoluciones de llamada (Lodash / Underscore) me siento mal y propenso a errores. Es fácil encontrar problemas repetitivos y de concurrencia debido a que las llamadas a la API se resuelven sin ordenarse.

He creado una pequeña biblioteca con React en mente para resolver tus problemas: 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 de rebote asegura que:

  • las llamadas de api serán rebotadas
  • La función de rebote siempre devuelve una promesa.
  • solo la promesa devuelta de la última llamada se resolverá
  • un solo this.setState({ result }); sucederá por api-call

Eventualmente, puede agregar otro truco si su componente se desmonta:

componentWillUnmount() { this.setState = () => {}; }

Tenga en cuenta que los Observables (RxJS) también pueden ser un gran ajuste para el desalojo de entradas, pero es una abstracción más poderosa que puede ser más difícil de aprender / usar correctamente.

¿Todavía quieres usar el rebote de devolución de llamada?

La parte importante aquí es crear una función única rebatida (o limitada) por instancia de componente . No desea volver a crear la función de rebote (o aceleración) cada vez, y tampoco desea que varias instancias compartan la misma función de rebote.

No estoy definiendo la función de debouncing en esta respuesta ya que no es realmente relevante, pero esta respuesta funcionará perfectamente bien con _.debounce de guión bajo o lodash, así como la función de debouncing proporcionada por el usuario.

No es Buena idea:

var SearchBox = React.createClass({ method: function() {...}, debouncedMethod: debounce(this.method,100); });

No funcionará, porque durante la creación de un objeto de descripción de clase, this no es el objeto creado en sí mismo. this.method no devuelve lo que esperaba porque this contexto no es el objeto en sí mismo (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 de rebote que llama a este this.method . ¡El problema es que lo estás recreando en cada llamada a debouncedMethod , por lo que la función de rebote recién creada no sabe nada sobre las llamadas anteriores! Debe reutilizar la misma función de imputación a lo largo del tiempo, de lo contrario no se producirá la publicación.

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 de denuncia, y la mayoría de las veces esto no es lo que quieres. Ver JsFiddle : 3 instancias están produciendo solo 1 entrada de registro global.

Tiene que crear una función de rebote para cada instancia de componente , y no una función de rebote única en el nivel de clase, compartida por cada instancia de componente.

BUENA IDEA:

Debido a que las funciones de pago tienen estado, tenemos que crear una función de pago 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,1000); } method() { ... } }

ES5

var SearchBox = React.createClass({ method: function() {...}, componentWillMount: function() { this.method = debounce(this.method,100); }, });

Ver JsFiddle : 3 instancias están produciendo 1 entrada de registro por instancia (lo que hace 3 globalmente).

Cuida de la agrupación de eventos de React.

Esto está relacionado porque a menudo queremos rebotar o controlar los eventos DOM.

En React, los objetos de evento (es decir, SyntheticEvent ) que recibe en las devoluciones de llamada se agrupan (esto ahora está documented ). Esto significa que después de que se haya llamado a la devolución de llamada del evento, el SyntheticEvent que reciba se volverá a colocar en la piscina con atributos vacíos para reducir la presión del GC.

Por lo tanto, si accede a las propiedades de SyntheticEvent asíncronas a la devolución de llamada original (como puede ser el caso si estrangula / rebota), las propiedades a las que accede pueden borrarse. Si desea que el evento nunca se vuelva a poner en el grupo, puede usar el método persist() .

Sin persistencia (comportamiento por defecto: evento agrupado)

onClick = e => { alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`); setTimeout(() => { alert(`async -> hasNativeEvent=${!!e.nativeEvent}`); }, 0); };

El segundo (asíncrono) 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) imprimirá hasNativeEvent=true porque hasNativeEvent=true () permite evitar volver a colocar el evento en el grupo.

Puedes probar estos 2 comportamientos aquí JsFiddle

Lea la respuesta de Julen para ver un ejemplo del uso de persist() con una función de aceleración / rebote.


Aquí hay un ejemplo que se me ocurrió que envuelve a otra clase con un debouncer. Esto se presta muy bien para convertirse en un decorador / función de orden superior:

import React, {Component} from ''react''; import _ from ''lodash''; class MyComponent extends Component{ constructor(props){ super(props); this.handleChange = _.debounce(this.handleChange.bind(this),700); };


Después de luchar con las entradas de texto por un tiempo y no encontrar una solución perfecta por mi cuenta, encontré esto en npm https://www.npmjs.com/package/react-debounce-input

Aquí hay un ejemplo simple:

import React from ''react''; import ReactDOM from ''react-dom''; import {DebounceInput} from ''react-debounce-input''; class App extends React.Component { state = { value: '''' }; render() { return ( <div> <DebounceInput minLength={2} debounceTimeout={300} onChange={event => this.setState({value: event.target.value})} /> <p>Value: {this.state.value}</p> </div> ); } } const appRoot = document.createElement(''div''); document.body.appendChild(appRoot); ReactDOM.render(<App />, appRoot);

El componente DebounceInput acepta todas las propiedades que puede asignar a un elemento de entrada normal. Pruébalo en codepen

Espero que también ayude a alguien más y les ahorre algo de tiempo.


En lugar de envolver el handleOnChange en un debounce (), ¿por qué no envolver la llamada ajax dentro de la función de devolución de llamada dentro del rebote, por lo que no se destruye el objeto de evento. Así que algo como esto:

handleOnChange: function (event) { debounce( $.ajax({}) , 250); }


Estaba buscando una solución para el mismo problema y encontré este hilo así como algunos otros, pero tenían el mismo problema: si está intentando realizar una función de handleOnChange y handleOnChange y necesita el valor de un evento objetivo, no podrá cannot read property value of null o algún error similar. En mi caso, también necesitaba preservar el contexto de this dentro de la función de rebote ya que estoy ejecutando una acción fluible. Aquí está mi solución, funciona bien para mi caso de uso, así que lo dejo aquí en caso de que alguien encuentre este hilo:

// at top of file: var myAction = require(''../actions/someAction''); // inside React.createClass({...}); handleOnChange: function (event) { var value = event.target.value; var doAction = _.curry(this.context.executeAction, 2); // only one parameter gets passed into the curried function, // so the function passed as the first parameter to _.curry() // will not be executed until the second parameter is passed // which happens in the next function that is wrapped in _.debounce() debouncedOnChange(doAction(myAction), value); }, debouncedOnChange: _.debounce(function(action, value) { action(value); }, 300)


He encontrado esta publicación de Justin Tulk muy útil. Después de un par de intentos, en lo que uno percibe como la forma más oficial de reaccionar / redux, muestra que falla debido a documented . Su solución luego usa algún estado interno para rastrear el valor cambiado / ingresado en la entrada, con una devolución de llamada justo después de setState que llama a una acción de redux estrangulada / rebotada que muestra algunos resultados en tiempo real.

import React, {Component} from ''react'' import TextField from ''material-ui/TextField'' import { debounce } from ''lodash'' class TableSearch extends Component { constructor(props){ super(props) this.state = { value: props.value } this.changeSearch = debounce(this.props.changeSearch, 250) } handleChange = (e) => { const val = e.target.value this.setState({ value: val }, () => { this.changeSearch(val) }) } render() { return ( <TextField className = {styles.field} onChange = {this.handleChange} value = {this.props.value} /> ) } }


La solución de Julen es un poco difícil de leer, aquí hay un código de reacción más claro y preciso para cualquier persona que lo haya tropezado basándose en el título y no en los pequeños detalles de la pregunta.

tl; versión dr : cuando actualice a los observadores, envíe un método de programación de llamadas en su lugar y eso, a su vez, notificará a los observadores (o ejecutará ajax, etc.)

Complete jsfiddle con el componente de ejemplo http://jsfiddle.net/7L655p5L/4/

var InputField = React.createClass({ getDefaultProps: function () { return { initialValue: '''', onChange: null }; }, getInitialState: function () { return { value: this.props.initialValue }; }, render: function () { var state = this.state; return ( <input type="text" value={state.value} onChange={this.onVolatileChange} /> ); }, onVolatileChange: function (event) { this.setState({ value: event.target.value }); this.scheduleChange(); }, scheduleChange: _.debounce(function () { this.onChange(); }, 250), onChange: function () { var props = this.props; if (props.onChange != null) { props.onChange.call(this, this.state.value) } }, });


Mucha información buena aquí ya, pero para ser breve. Esto me funciona ...

export class DebouncedThingy extends React.Component { static ToDebounce = [''someProp'', ''someProp2'']; constructor(props) { super(props); this.state = {}; } // On prop maybe changed componentWillReceiveProps = (nextProps) => { this.debouncedSetState(); }; // Before initial render componentWillMount = () => { // Set state then debounce it from here on out (consider using _.throttle) this.debouncedSetState(); this.debouncedSetState = _.debounce(this.debouncedSetState, 300); }; debouncedSetState = () => { this.setState(_.pick(this.props, DebouncedThingy.ToDebounce)); }; render() { const restOfProps = _.omit(this.props, DebouncedThingy.ToDebounce); return <Thingy {...restOfProps} {...this.state} /> } }


Puede utilizar el método https://lodash.com/docs/4.17.5#debounce debounce de Lodash. Es simple y efectivo.

import * as lodash from lodash; const update = (input) => { // Update the input here. console.log(`Input ${input}`); } const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200}); doHandleChange() { debounceHandleUpdate(input); }

También puede cancelar el método de rebote utilizando el método siguiente.

this.debounceHandleUpdate.cancel();

Espero que te ayude. ¡¡Aclamaciones!!


Si está utilizando redux, puede hacerlo de una manera muy elegante con middleware. Puede definir un middleware Debounce como:

var timeout; export default store => next => action => { const { meta = {} } = action; if(meta.debounce){ clearTimeout(timeout); timeout = setTimeout(() => { next(action) }, meta.debounce) }else{ next(action) } }

A continuación, puede agregar debouncing a los creadores de acciones, como:

export default debouncedAction = (payload) => ({ type : ''DEBOUNCED_ACTION'', payload : payload, meta : {debounce : 300} }

En realidad, ya existe un middleware que puede quitar de npm para hacer esto por usted.


Si todo lo que necesita del objeto de evento es obtener el elemento de entrada DOM, la solución es mucho más simple, simplemente use ref

class Item extends React.Component { constructor(props) { super(props); this.saveTitle = _.throttle(this.saveTitle.bind(this), 1000); } saveTitle(){ let val = this.inputTitle.value; // make the ajax call } render() { return <input ref={ el => this.inputTitle = el } type="text" defaultValue={this.props.title} onChange={this.saveTitle} /> } }


También puedes usar un mixin auto escrito, algo como esto:

var DebounceMixin = { debounce: function(func, time, immediate) { var timeout = this.debouncedTimeout; if (!timeout) { if (immediate) func(); this.debouncedTimeout = setTimeout(function() { if (!immediate) func(); this.debouncedTimeout = void 0; }.bind(this), time); } } };

Y luego utilízalo en tu componente como este:

var MyComponent = React.createClass({ mixins: [DebounceMixin], handleClick: function(e) { this.debounce(function() { this.setState({ buttonClicked: true }); }.bind(this), 500, true); }, render: function() { return ( <button onClick={this.handleClick}></button> ); } });


Uno no necesita una tonelada de variables locales para una función decente del acelerador. El propósito de una función de aceleración es reducir los recursos del navegador, no aplicar tanta sobrecarga que está usando aún más. Como prueba de la evidencia de esta afirmación, he ideado una función de aceleración que tiene solo 4 variables "colgadas" en su alcance. (Una variable "colgada" es una variable que nunca se recolecta como basura porque siempre permanece referenciada por una función que podría llamarse, por lo tanto, absorbiendo memoria). Un puñado de funciones del acelerador por lo general no hacen ningún daño; pero, si hay miles de funciones aceleradas, entonces la memoria empieza a escasear si se utiliza una función de aceleración realmente ineficiente. Mi solución está abajo.

var timenow=self.performance ? performance.now.bind(performance) : Date.now; function throttle(func, alternateFunc, minInterval) { var lastTimeWent = -minInterval, freshArguments=null; function executeLater(){ func.apply(null, freshArguments); freshArguments = null; lastTimeWent = 0; } return function() { var newTimeWent = timenow(); if ((newTimeWent-lastTimeWent) > minInterval) { lastTimeWent = newTimeWent; return func.apply(null, arguments); } else { if (freshArguments === null) setTimeout(executeLater, minInterval-(newTimeWent-lastTimeWent)); freshArguments = arguments; if (typeof alternateFunc === "function") return alternateFunc.apply(null, arguments); } }; }

Luego, para ajustar esta función de aceleración alrededor de EventTarget para cosas como clics DOM, eventos de ventana, XMLHttpRequests onprogress, FileReader onprogress, [etc.], así:

var tfCache = []; // throttled functions cache function listen(source, eventName, func, _opts){ var i = 0, Len = tfCache.length, cF = null, options = _opts || {}; a: { for (; i < Len; i += 4) if (tfCache[i] === func && tfCache[i+1] === (options.ALTERNATE||null) && tfCache[i+2] === (options.INTERVAL||200) ) break a; cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200); tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF); } source.addEventListener(eventName, cF || tfCache[i+3], _opts); return cF === null; // return whether it used the cache or not }; function mute(source, eventName, func, _opts){ var options = _opts || {}; for (var i = 0, Len = tfCache.length; i < Len; i += 4) if (tfCache[i] === func && tfCache[i+1] === (options.ALTERNATE||null) && tfCache[i+2] === (options.INTERVAL||200) ) { source.removeEventListener(eventName, tfCache[i+3], options); return true; } return false; }

A continuación se muestra un ejemplo de un botón acelerado para escuchar un clic solo una vez por segundo. Cuando recibe este clic, cambia a un nuevo color aleatorio.

(function(){"use strict"; // The function throttler // var timenow=self.performance ? performance.now.bind(performance) : Date.now; function throttle(func, alternateFunc, minInterval) { var lastTimeWent = -minInterval, freshArguments=null; function executeLater(){ func.apply(null, freshArguments); freshArguments = null; lastTimeWent = 0; } return function() { var newTimeWent = timenow(); if ((newTimeWent-lastTimeWent) > minInterval) { lastTimeWent = newTimeWent; return func.apply(null, arguments); } else { if (freshArguments === null) setTimeout(executeLater,minInterval-(newTimeWent-lastTimeWent)); freshArguments = arguments; if (typeof alternateFunc === "function") return alternateFunc.apply(this, arguments); } }; } // The EventTarget wrapper: // var tfCache = []; // throttled functions cache function listen(source, eventName, func, _opts){ var i = 0, Len = tfCache.length, cF = null, options = _opts || {}; a: { for (; i < Len; i += 4) if (tfCache[i] === func && tfCache[i+1] === (options.ALTERNATE||null) && tfCache[i+2] === (options.INTERVAL||200) ) break a; cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200); tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF); } source.addEventListener(eventName, cF || tfCache[i+3], _opts); return cF === null; // return whether it used the cache or not }; function mute(source, eventName, func, _opts){ var options = _opts || {}; for (var i = 0, Len = tfCache.length; i < Len; i += 4) if (tfCache[i] === func && tfCache[i+1] === (options.ALTERNATE||null) && tfCache[i+2] === (options.INTERVAL||200) ) { source.removeEventListener(eventName, tfCache[i+3], options); return true; } return false; } // Finally, the color changing button: // function randHex(){ // weighted towards the ends of the scales for contrast var rand = Math.random()*2 - 1; // equally offcenter it from one var sign = rand < 0 ? -1 : 1; // get a sign-ish value rand = Math.sqrt(rand * sign) * sign; // stretch it further from zero rand = 128 + rand * 128; // next, recenter it to range from 0 to 255 var str = (rand | 0).toString(16); // make an integer string while (str.length < 2) str = "0" + str; // pad it return str; // finally, return it } var clickerEle = document.getElementById("clicker"); var dropperEle = document.getElementById("droppedClicks"); var deDs = dropperEle.dataset; // deDs = droperEle DataSet var dropSkips = 0; function whenClick(){ if (dropSkips > 10) { // if the user clicked fast enough mute(clickerEle, "click", whenClick, theOptions); dropperEle.textContent = "You won with " + dropSkips + " clicks per second! The button no longer changes color"; } dropSkips = 0; deDs ? delete deDs.numdrops : dropperEle.removeAttribute("data-numdrops"); clickerEle.setAttribute("style", "background:#"+randHex()+randHex()+randHex()); } var theOptions = { ALTERNATE: function(){ // whenever the main function is skipped: deDs.numdrops = dropSkips += 1; }, INTERVAL: 2000, passive: true }; listen(clickerEle, "click", whenClick, theOptions); whenClick(); // to start changing the color document.body.addEventListener("contextmenu", function(x){x.preventDefault()}); })();

#droppedClicks[data-numdrops]::before { content: "Dropped " attr(data-numdrops) " clicks"; color: green; }

Click the button below as fast as you can! You win when you are able to click the button more than ten times in a single second ().<br /> <br /> <button id="clicker"><h3>Click me</h3></button> <div id="droppedClicks"></div>

Después de guardar esta respuesta, descubrí que los comentarios negativos persistentes de las comunidades de SO tóxicos impiden que se ejecute este fragmento. Por lo tanto, aquí hay un enlace a un JSFiddle: https://jsfiddle.net/t7ymkzLx/2/

De forma predeterminada, esto limita la función a como máximo una llamada cada 200 ms. Para cambiar el intervalo a un número diferente de milisegundos, luego pase la opción optionsObject.INTERVAL en el argumento de las opciones y optionsObject.INTERVAL en los milisegundos mínimos deseados entre las ejecuciones. (Dado que los temporizadores no siempre son los más precisos). Si tiene un intervalo mínimo exacto deseado, le recomendaría que reste uno o dos de las optionsObject.INTERVAL deseadasObjeto.INTERVAL para asegurarse de que siempre se ejecute al menos cuando sea necesario. Si necesita hacer algo con los argumentos de la función acelerada cuando la ejecución de la función acelerada se retrasa (debido al exceso de llamadas), use la opción optionsObject.ALTERNATE . Esta "ALTERNA" es una función que se llama inmediatamente en lugar de la función primaria siempre que se descarta la llamada a la función primaria. Por ejemplo, si está utilizando su función regulada en un EventTarget, pero quiere preventDefault() en eventos eliminados, use {ALTERNATE: function(evt){ evt.preventDefault(); }} {ALTERNATE: function(evt){ evt.preventDefault(); }} para el objeto de opciones.


Usando ES6 CLASS y React 15.xx & lodash.debounce Im usando las referencias de React aquí, ya que el evento pierde este enlace internamente.

class UserInput extends React.Component { constructor(props) { super(props); this.state = { userInput: "" }; this.updateInput = _.debounce(this.updateInput, 500); } updateInput(userInput) { this.setState({ userInput }); //OrderActions.updateValue(userInput);//do some server stuff } render() { return ( <div> <p> User typed: { this.state.userInput } </p> <input ref = "userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type = "text" / > </div> ); } } ReactDOM.render( < UserInput / > , document.getElementById(''root'') );

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="root"></div>


para throttle o debounce la mejor manera es crear un creador de funciones para que pueda usarlo en cualquier lugar, por ejemplo:

updateUserProfileField(fieldName) { const handler = throttle(value => { console.log(fieldName, value); }, 400); return evt => handler(evt.target.value.trim()); }

y en tu método de render puedes hacer:

<input onChange={this.updateUserProfileField("givenName").bind(this)}/>

updateUserProfileField método updateUserProfileField creará una función separada cada vez que lo llame.

Tenga en cuenta que no intente devolver el controlador directamente, por ejemplo, esto no funcionará:

updateUserProfileField(fieldName) { return evt => throttle(value => { console.log(fieldName, value); }, 400)(evt.target.value.trim()); }

la razón por la que esto no funcionará porque generará una nueva función de aceleración cada vez que se llame al evento en lugar de usar la misma función de aceleración, por lo que básicamente la aceleración será inútil;)

Además, si usa debounce o throttle no necesita setTimeout o clearTimeout , es por eso que los usamos: P