javascript reactjs

javascript - React.js: la forma más eficiente de pasar un parámetro a un controlador de eventos sin bind() en un componente



reactjs (4)

En lugar de .bind o crear una función de flecha anónima en render() , puede crear la función enlazada / anónima fuera de render() , como en el objeto instanciado, en el constructor, o algo así, y usar una referencia A esa función singular (nunca recreada). Por ejemplo, ejecute una vez :

this.boundHandleClick = this.handleClick.bind(this, 111);

o

this.boundHandleClick = () => this.handleClick(111);

Luego, en render , render referencia a boundHandleClick :

return ( <div className="App"> <button onClick={this.boundHandleClick}>Click</button> </div> );

Si necesita usar los parámetros ( 111 ) dentro del render , entonces podría usar la búsqueda de objetos para ver si existe una función vinculada con ese parámetro todavía. Si lo hace, simplemente use esa función vinculada; de lo contrario, créela ( una vez , para que no tenga que crearla cada vez que use ese parámetro en el futuro):

this.boundClicks = {}; // ... if (!this.boundClicks[''111'']) this.boundClicks[''111''] = () => this.handleClick(111); return ( <div className="App"> <button onClick={this.boundClicks[''111'']}>Click</button> </div> );

Cuando tiene que usar this palabra clave dentro de un controlador de eventos, debe vincular la función (controlador de eventos) con this kerword. De lo contrario, necesita utilizar la función de flecha .

p.ej

//This function isn''t bound whilst using "this" keyword inside of it. //Still, it works because it uses an arrow function handleClick = () => { this.setState({ isClicked:true }); } render() { return ( <div className="App"> <button onClick={this.handleClick}>Click</button> </div> ); }

Sin embargo, con el enfoque anterior, no puede pasar un parámetro. Necesitas usar cualquiera ...

  1. bind(this, param) después de la función
  2. la función de flecha anónima

es decir

<button onClick={this.handleClick}>Click</button> will be <button onClick={this.handleClick.bind(this, 111)}>Click</button> or <button onClick={() => this.handleClick(111)}>Click</button>

Aquí está la pregunta.

¿Cuál es la forma más eficiente de pasar un parámetro a un controlador de eventos?

Según el documento oficial , el uso de bind() puede minar el rendimiento, porque ...

El uso de Function.prototype.bind en render crea una nueva función cada vez que el componente se renderiza

Lo mismo ocurre con el uso de la función de flecha anónima. El doc dice que ...

El uso de una función de flecha en el render crea una nueva función cada vez que el componente se renderiza

Entonces, ¿cuál será la forma más eficiente de pasar un parámetro?

Cualquier contribucion sera apreciada.

PD

Algunas personas han preguntado cómo se determina param . Esto se determinará dinámicamente (es decir, no siempre 111 ). Por lo tanto, puede ser de estados, accesorios o algunas otras funciones definidas en esta clase.


Esto depende de cómo se obtiene el parámetro. Habrá ocasiones en las que no podrá evitar usar un .bind o una función de flecha fácilmente, pero la mayoría de las veces puede obtener el parámetro de alguna manera. Como puede ver en la respuesta de @ CertainPerformance si puede usar este argumento en el constructor, puede preferir de esta manera. Pero puede haber otros enfoques.

Por ejemplo, suponga que tiene una lista en el estado. En lugar de asignar esta lista directamente y usar una función .bind o una flecha allí, puede pasar los elementos de la lista a un componente secundario y luego usar un controlador de devolución de llamada allí.

class App extends React.Component { state = { list: [ "foo", "bar" ], }; handleClick(el) { console.log( el ) } render() { return ( <div> {this.state.list.map( el => ( <Child key={el} el={el} onClick={this.handleClick} /> ) )} </div> ); } } const Child = ( props ) => { const handleClick = () => props.onClick( props.el ); return ( <div> {props.el} <button onClick={handleClick}>Click</button> </div> ); }; ReactDOM.render( <App />, document.getElementById( "root" ) );

<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>

Estoy actualizando mi respuesta con una demostración de cómo usar una función de flecha en línea, o unirla, o usar una función al curry provoca una recreación aquí.

Suponga que tiene un componente y este componente tiene un componente hijo escrito como React.PureComponent . Normalmente, si los accesorios de este niño no cambian, no volverán a reproducirse. Guay. Tenemos un método de clase en nuestro componente principal y queremos pasarlo como un controlador a nuestro componente secundario. Veamos que está pasando aquí.

Primero, no paso el controlador y cuando incrementas el contador en el padre, el componente hijo no vuelve a ejecutarse (excepto el procesamiento inicial). Esto se debe a que lo definimos como un PureComponent . No queremos que se vuelva a entregar a menos que cambien sus accesorios.

class App extends React.Component { state = { counter: 0, }; increment = () => this.setState( currentState => ( { counter: currentState.counter + 1, } ) ); handleClick(param) { console.log( param ) } render() { return ( <div> <button onClick={this.increment}>Increment</button> Counter is: {this.state.counter} <Child /> </div> ); } } class Child extends React.PureComponent { render() { console.log( "child rendered" ); return ( <div> <button onClick={this.props.onClick}>Click</button> </div> ); } } ReactDOM.render( <App />, document.getElementById( "root" ) );

<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

Como puede ver, el componente hijo no se ha vuelto a entregar. Ahora hagamos esto con nuestro método de clase, usando una función de flecha en línea.

class App extends React.Component { state = { counter: 0, }; increment = () => this.setState( currentState => ( { counter: currentState.counter + 1, } ) ); handleClick( param ) { console.log( param ) } render() { return ( <div> <button onClick={this.increment}>Increment</button> Counter is: {this.state.counter} <Child onClick={() => this.handleClick( "some param" )} /> </div> ); } } class Child extends React.PureComponent { render() { console.log( "child rendered" ); return ( <div> <button onClick={this.props.onClick}>Click</button> </div> ); } } ReactDOM.render( <App />, document.getElementById( "root" ) );

<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

Vaya, el niño se renderiza cuando incrementamos el contador. Pero, no tiene ninguna relación con el estado contrario, no queremos esto. Entonces, ¿por qué se está volviendo a presentar? Esto se debe a que estamos utilizando una función de flecha en línea en la propiedad onClick que está obteniendo. Dado que esta función se recrea en cada representación del padre, su referencia cambia a una función diferente y el niño piensa que obtiene un nuevo apoyo. Pero en realidad no lo consigue. Podemos usar el parámetro con nuestro controlador, pero no hay representación innecesaria.

Ahora con el .bind . No uso this en el enlace ya que no usamos this en nuestro método simple. Simplemente registra un parámetro.

class App extends React.Component { state = { counter: 0, }; increment = () => this.setState( currentState => ( { counter: currentState.counter + 1, } ) ); handleClick( param ) { console.log( param ) } render() { return ( <div> <button onClick={this.increment}>Increment</button> Counter is: {this.state.counter} <Child onClick={this.handleClick.bind( null, "some param" )} /> </div> ); } } class Child extends React.PureComponent { render() { console.log( "child rendered" ); return ( <div> <button onClick={this.props.onClick}>Click</button> </div> ); } } ReactDOM.render( <App />, document.getElementById( "root" ) );

<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

Lo mismo aquí, podemos usar el parámetro pero no hay una representación innecesaria. Ahora con una función al curry.

class App extends React.Component { state = { counter: 0, }; increment = () => this.setState( currentState => ( { counter: currentState.counter + 1, } ) ); handleClick( param ) { return function() { console.log( param ) } } render() { return ( <div> <button onClick={this.increment}>Increment</button> Counter is: {this.state.counter} <Child onClick={this.handleClick( "some param" )} /> </div> ); } } class Child extends React.PureComponent { render() { console.log( "child rendered" ); return ( <div> <button onClick={this.props.onClick}>Click</button> </div> ); } } ReactDOM.render( <App />, document.getElementById( "root" ) );

<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

¡Sorpresa! Una vez más la prestación innecesaria. Ahora, para un componente esto no es tan importante. Pero, ¿qué pasa si su aplicación tiene cientos de componentes como este niño?

Ahora, asumamos que estoy obteniendo el parámetro de alguna manera. Lo estoy imitando con una cadena codificada aquí.

class App extends React.Component { state = { counter: 0, }; increment = () => this.setState( currentState => ( { counter: currentState.counter + 1, } ) ); handleClick() { console.log( "some param" ) } render() { return ( <div> <button onClick={this.increment}>Increment</button> Counter is: {this.state.counter} <Child onClick={this.handleClick} /> </div> ); } } class Child extends React.PureComponent { render() { console.log( "child rendered" ); return ( <div> <button onClick={this.props.onClick}>Click</button> </div> ); } } ReactDOM.render( <App />, document.getElementById( "root" ) );

<script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

Duh! No hay ninguna representación innecesaria como se esperaba ya que usamos la referencia de la función. Puedo usar el parámetro pero la vida no es tan fácil y OP realmente pregunta cómo podemos usar el parámetro sin usar una función de flecha en línea, o un enlace o con una función de curry. Todo el alboroto es sobre esto.

Aunque no pasamos este controlador a un componente, aún se recrea en cada render del padre como vemos aquí. Si tiene una lista de elementos, digamos 500 de ellos, y los asigna a los botones en el componente principal y usa una función de flecha, etc., esto significa que se recrearán (500 veces) en cada render.

Entonces, no hay una manera fácil de hacer esto. Si nuestro parámetro no proviene del objeto de evento, entonces usamos la solución de @ CertainPerformance o intentamos cambiar nuestra lógica como lo hago aquí.


Lo he explicado en mi otra publicación: haga clic en evento en reaccionar componente .

Nunca use la función de flecha en línea si está preocupado por su rendimiento. Todavía puede utilizar el método de clase pública y vincular el contexto this .

handleClick = () => () => { this.setState({ // this works now isClicked:true }); }

Puedes pasar cualquier parámetro que te guste así:

handleClick = (param1, param2, param3) => (event) => {

Según el comentario de devserkan ,

Esto es curry y lo mismo que otras opciones. Esta función también se recrea en cada render.

No. No lo hace. Vea la nota de los documentos :

Si esta devolución de llamada se pasa como apoyo a los componentes inferiores, esos componentes podrían hacer una representación adicional. En general, recomendamos enlazar en el constructor o usar la sintaxis de los campos de clase , para evitar este tipo de problema de rendimiento.

Además, vea el comentario de bigga-hd debajo de la respuesta de bigga-hd :

Evite declarar funciones de flecha o enlazar en render para obtener un rendimiento óptimo. Declara tus funciones fuera del render. No más asignaciones de funciones en cada render.

¿Cómo llamas a este controlador?

Puedes llamar al método así:

onClick={this.handleClick(param1,param2,param3)}

PD: no marqué esta publicación como duplicada, ya que el alcance de la pregunta es significativamente diferente. Por lo tanto, solo vinculé la publicación para profundizar en más detalles.


Solía ​​hacer este tipo de cosas para los nombres de los estados. Depende de lo que quieras, puedes probar esto. Así que, no tengo que volver a enlazar la función. Donde puede obtener el nombre del estado desde e.target.name

class App extends React.Component { state = { change: "" }; handleChange = e => { const { name, value } = e.target; this.setState({ [name]: value }) }; render() { return ( <input type="text" onChange={this.handleChange} name="change" value={this.state.change} /> ); } }