javascript - son - No se puede acceder a la instancia de React(this) dentro del controlador de eventos
react comunicacion entre componentes (18)
Alexandre Kirszenberg tiene razón, pero otra cosa importante a la que debe prestar atención es dónde coloca su encuadernación. He estado atrapado en una situación durante días (probablemente porque soy un principiante), pero a diferencia de otros, sabía sobre la vinculación (que ya había aplicado), por lo que no podía entender por qué todavía tenía esos errores Resulta que tuve el enlace en el orden incorrecto.
Otro es quizás el hecho de que estaba llamando a la función dentro de "this.state", que no estaba al tanto del enlace porque estaba por encima de la línea de enlace,
A continuación se muestra lo que tenía (Por cierto, esta es mi primera publicación, pero pensé que era muy importante, ya que no pude encontrar la solución en ningún otro lado):
constructor(props){
super(props);
productArray=//some array
this.state={
// Create an Array which will hold components to be displayed
proListing:productArray.map(product=>{return(<ProRow dele={this.this.popRow()} prodName={product.name} prodPrice={product.price}/>)})
}
this.popRow=this.popRow.bind(this);//This was the Issue, This line //should be kept above "this.state"
Estoy escribiendo un componente simple en ES6 (con BabelJS) y funciona
this.setState
no funciona.
Los errores típicos incluyen algo como
No se puede leer la propiedad ''setState'' de undefined
o
this.setState no es una función
¿Sabes por qué? Aquí está el código:
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {inputContent: ''startValue''}
}
sendContent(e) {
console.log(''sending input content ''+React.findDOMNode(React.refs.someref).value)
}
changeContent(e) {
this.setState({inputContent: e.target.value})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" ref="someref" value={this.inputContent}
onChange={this.changeContent} />
<button onClick={this.sendContent}>Submit</button>
</div>
)
}
}
export default SomeClass
Aunque las respuestas anteriores han proporcionado una descripción general básica de las soluciones (es decir, enlace, funciones de flecha, decoradores que hacen esto por usted), todavía no he encontrado una respuesta que realmente explique por qué esto es necesario, que en mi opinión es la raíz de confusión, y conduce a pasos innecesarios, como un reencuentro innecesario y seguir ciegamente lo que otros hacen.
this
es dinámico
Para comprender esta situación específica, una breve introducción de cómo funciona.
La clave aquí es que
this
es un enlace de tiempo de ejecución y depende del contexto de ejecución actual.
Por lo tanto, por qué se conoce comúnmente como "contexto", es decir, dar información sobre el contexto de ejecución actual, y por qué necesita vincular es porque pierde "contexto".
Pero déjenme ilustrar el problema con un fragmento:
const foobar = {
bar: function () {
return this.foo;
},
foo: 3,
};
console.log(foobar.bar()); // 3, all is good!
En este ejemplo, obtenemos
3
, como se esperaba.
Pero tome este ejemplo:
const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!
Puede ser inesperado descubrir que registra registros indefinidos, ¿a dónde fueron los
3
?
La respuesta se encuentra en el
"contexto"
, o cómo
ejecuta
una función.
Compare cómo llamamos a las funciones:
// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();
Note la diferencia
En el primer ejemplo, estamos especificando exactamente dónde se encuentra el método de
bar
1
, en el objeto
foobar
:
foobar.bar();
^^^^^^
Pero en el segundo, almacenamos el método en una nueva variable, y usamos esa variable para llamar al método, sin indicar explícitamente dónde existe realmente el método, perdiendo así el contexto :
barFunc(); // Which object is this function coming from?
Y ahí radica el problema, cuando almacena un método en una variable, se pierde la información original sobre dónde se encuentra ese método (el contexto en el que se está ejecutando el método).
Sin esta información, en tiempo de ejecución, no hay forma de que el intérprete de JavaScript vincule
this
correctamente; sin un contexto específico,
this
no funciona como se esperaba
2
.
Relativo a reaccionar
Aquí hay un ejemplo de un componente React (acortado por brevedad) que sufre
this
problema:
handleClick() {
this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
clicks: clicks + 1, // increase by 1
}));
}
render() {
return (
<button onClick={this.handleClick}>{this.state.clicks}</button>
);
}
Pero, ¿por qué y cómo se relaciona la sección anterior con esto? Esto se debe a que sufren de una abstracción del mismo problema. Si observa cómo React maneja los controladores de eventos :
// Edited to fit answer, React performs other checks internally
// props is the current React component''s props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called
Entonces, cuando haces
onClick={this.handleClick}
, el método
this.handleClick
finalmente se asigna a la
listener
variable
3
.
Pero ahora puede ver surgir el problema: dado que hemos asignado
this.handleClick
al
listener
, ¡ya no especificamos exactamente de dónde
handleClick
!
Desde el punto de vista de React, el
listener
es solo una función, no asociada a ningún objeto (o en este caso, instancia del componente React).
Hemos perdido contexto y, por lo tanto, el intérprete no puede inferir
this
valor para usar
dentro de
handleClick
.
Por qué funciona la encuadernación
Quizás se pregunte, si el intérprete decide
this
valor en tiempo de ejecución, ¿por qué puedo vincular el controlador para que
funcione
?
Esto se debe a que puede usar la
Function#bind
para
garantizar
this
valor en tiempo de ejecución.
Esto se hace estableciendo una propiedad interna de enlace en una función, lo que le permite no inferir
this
:
this.handleClick = this.handleClick.bind(this);
Cuando se ejecuta esta línea, presumiblemente en el constructor,
la corriente se captura
(la instancia del componente React) y se establece como un enlace interno de una función completamente nueva, devuelta desde la
Function#bind
.
Esto asegura que cuando
this
se calcule en tiempo de ejecución, el intérprete no tratará de inferir nada, sino que usará el valor que le proporcionó.
Por qué funcionan las propiedades de la función de flecha
Las propiedades de clase de función de flecha actualmente funcionan a través de Babel en función de la transpilación:
handleClick = () => { /* Can use this just fine here */ }
Se convierte en:
constructor() {
super();
this.handleClick = () => {}
}
Y esto funciona debido al hecho de que las funciones de flecha no
se
unen a esto, sino que toman
this
de su alcance envolvente.
En este caso, el
constructor
es
this
, que apunta a la instancia del componente React, lo que le da la respuesta correcta.
4 4
1 Uso "método" para referirme a una función que se supone que está vinculada a un objeto, y "función" para aquellos que no lo están.
2
En el segundo fragmento, undefined se registra en lugar de 3 porque el valor predeterminado es el contexto de ejecución global (
window
cuando no está en modo estricto o no está
undefined
) cuando no se puede determinar a través de un contexto específico.
Y en el ejemplo
window.foo
no existe, por lo tanto, no se
window.foo
.
3
Si va por la madriguera del conejo de cómo se ejecutan los eventos en la cola de eventos, se
invokeGuardedCallback
en el oyente.
4
En realidad
es mucho más complicado
.
React trata internamente de usar
Function#apply
en los oyentes para su propio uso, pero esto no funciona con las funciones de flecha, ya que simplemente no vinculan
this
.
Eso significa que cuando
this
se evalúa dentro de la función de flecha, se resuelve en cada entorno léxico de cada contexto de ejecución del código actual del módulo.
El contexto de ejecución que finalmente resuelve tener un enlace
this
es
el constructor, que apunta a la instancia actual del componente React, lo que le permite funcionar.
En caso de que desee mantener el enlace en la sintaxis del constructor, puede usar el proposal-bind-operator y transformar su código de la siguiente manera:
constructor() {
this.changeContent = ::this.changeContent;
}
En lugar de :
constructor() {
this.changeContent = this.changeContent.bind(this);
}
mucho más simple, sin necesidad de
bind(this)
o
fatArrow
.
Está utilizando ES6, por lo que las funciones no se unirán a "este" contexto automáticamente. Debe vincular manualmente la función al contexto.
constructor(props) {
super(props);
this.changeContent = this.changeContent.bind(this);
}
Este problema es una de las primeras cosas que la mayoría de nosotros experimentamos, cuando
React.createClass()
la transición de la sintaxis de definición del componente
React.createClass()
a la forma de la clase ES6 de extender
React.Component
.
Es causado por las diferencias de
this
contexto en
React.createClass()
vs
extends React.Component
.
El uso de
React.createClass()
automáticamente
this
contexto (valores) correctamente, pero ese no es el caso cuando se usan clases ES6.
Al hacerlo de la manera ES6 (al extender
React.Component
),
this
contexto es
null
por defecto.
Las propiedades de la clase no se unen automáticamente a la instancia de la clase React (componente).
Enfoques para resolver este problema
Conozco un total de 4 enfoques generales.
-
Vincula tus funciones en el constructor de la clase . Considerado por muchos como un enfoque de mejores prácticas que evita tocar JSX y no crea una nueva función en cada renderizado de componentes.
class SomeClass extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } }
-
Vincula tus funciones en línea . Todavía puede encontrar este enfoque utilizado aquí y allá en algunos tutoriales / artículos / etc., por lo que es importante que lo sepa. Es el mismo concepto que el # 1, pero tenga en cuenta que vincular una función crea una nueva función por cada representación.
class SomeClass extends React.Component { handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick.bind(this)}></button> ); } }
-
Utiliza una función de flecha gruesa . Hasta que las funciones de flecha, cada nueva función definió su propio valor. Sin embargo, la función de flecha no crea su propio contexto, por
this
tiene el significado original de la instancia del componente React. Por lo tanto, podemos:class SomeClass extends React.Component { handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={ () => this.handleClick() }></button> ); } }
o
class SomeClass extends React.Component { handleClick = () => { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } }
-
Use la biblioteca de funciones de utilidad para vincular automáticamente sus funciones . Existen algunas bibliotecas de utilidades, que automáticamente hacen el trabajo por usted. Estos son algunos de los populares, solo por mencionar algunos:
-
Autobind Decorator es un paquete NPM que une los métodos de una clase a la instancia correcta de
this
, incluso cuando los métodos están separados. El paquete utiliza@autobind
antes de los métodos para vincularthis
a la referencia correcta al contexto del componente.import autobind from ''autobind-decorator''; class SomeClass extends React.Component { @autobind handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } }
Autobind Decorator es lo suficientemente inteligente como para permitirnos vincular todos los métodos dentro de una clase de componentes a la vez, al igual que el enfoque n. ° 1.
-
Class Autobind es otro paquete de NPM que se usa ampliamente para resolver este problema de enlace. A diferencia de Autobind Decorator, no utiliza el patrón de decorador, sino que realmente usa una función dentro de su constructor que vincula automáticamente los métodos del Componente a la referencia correcta de
this
.import autobind from ''class-autobind''; class SomeClass extends React.Component { constructor() { autobind(this); // or if you want to bind only only select functions: // autobind(this, ''handleClick''); } handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } }
PD: Otra biblioteca muy similar es React Autobind .
-
Recomendación
Si yo fuera usted, me quedaría con el enfoque # 1. Sin embargo, tan pronto como obtenga un montón de enlaces en el constructor de su clase, le recomendaría que explore una de las bibliotecas auxiliares mencionadas en el enfoque n. ° 4.
Otro
No está relacionado con el problema que tiene, pero no debe usar en exceso las referencias .
Su primera inclinación puede ser usar referencias para "hacer que las cosas sucedan" en su aplicación. Si este es el caso, tómese un momento y piense más críticamente sobre dónde debe pertenecer el estado en la jerarquía de componentes.
Para fines similares, al igual que el que necesita, el uso de un
componente controlado
es la forma preferida.
Le sugiero que considere usar su
state
Componente
.
Entonces, simplemente puede acceder al valor de esta manera:
this.state.inputContent
.
Este problema ocurre después de react15.0, que el controlador de eventos no se vinculó automáticamente al componente. por lo que debe vincular esto al componente manualmente siempre que se llame al controlador de eventos.
Existen varios métodos para resolver el problema. pero necesita saber qué método es mejor y por qué? En general, recomendamos vincular sus funciones en el constructor de la clase o usar una función de flecha.
// method 1: use a arrow function
class ComponentA extends React.Component {
eventHandler = () => {
console.log(this)
}
render() {
return (
<ChildComponent onClick={this.eventHandler} />
);
}
// method 2: Bind your functions in the class constructor.
class ComponentA extends React.Component {
constructor(props) {
super(props);
this.eventHandler = this.eventHandler.bind(this);
}
render() {
return (
<ChildComponent onClick={this.eventHandler} />
);
}
estos dos métodos no crearán una nueva función cuando el componente se procese cada vez. por lo tanto, nuestro ChildComponent no se volverá a representar debido al cambio de los accesorios de la nueva función, o puede producir el problema de rendimiento.
Este problema ocurre porque
this.changeContent
y
onClick={this.sendContent}
no están vinculados a
esto
de la instancia del componente.
Hay otra solución (además de usar bind () en el constructor ()) para usar las funciones de flecha de ES6 que comparten el mismo alcance léxico del código circundante y mantener esto , para que pueda cambiar su código en render () a ser:
render() {
return (
<input type="text"
onChange={ () => this.changeContent() } />
<button onClick={ () => this.sendContent() }>Submit</button>
)
}
Hola, si no quieres preocuparte por vincular tu llamada de función. Puede usar ''class-autobind'' e importarlo así
import autobind from ''class-autobind'';
class test extends Component {
constructor(props){
super(props);
autobind(this);
}
No escriba autobind antes de la súper llamada porque no funcionará
Mi recomendación es usar las funciones de flecha como propiedades
class SomeClass extends React.Component {
handleClick = () => {
console.log(this); // the React Component instance
}
render() {
return (
<button onClick={this.handleClick}></button>
);
}
}
y no use las funciones de flecha como
class SomeClass extends React.Component {
handleClick(){
console.log(this); // the React Component instance
}
render() {
return (
<button onClick={()=>{this.handleClick}}></button>
);
}
}
porque el segundo enfoque generará una nueva función en cada llamada de renderizado, de hecho, esto significa una nueva versión de puntero nueva, que si luego se preocupa por el rendimiento, puede usar React.PureComponent o en React.Component puede anular shouldComponentUpdate (nextProps, nextState) y control superficial cuando llegaron los accesorios
Morhaus es correcto, pero esto se puede resolver sin
bind
.
Puede usar una función de flecha junto con la propuesta de propiedades de clase :
class SomeClass extends React.Component {
changeContent = (e) => {
this.setState({inputContent: e.target.value})
}
render() {
return <input type="text" onChange={this.changeContent} />;
}
}
Debido a que la función de flecha se declara en el alcance del constructor, y porque las funciones de flecha mantienen
this
desde su alcance de declaración, todo funciona.
La desventaja aquí es que estas no serán funciones en el prototipo, todas serán recreadas con cada componente.
Sin embargo, esto no es un gran inconveniente, ya que la
bind
da como resultado lo mismo.
Necesitamos vincular la función de evento con el componente en el constructor de la siguiente manera,
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {inputContent: ''startValue''}
this.changeContent = this.changeContent.bind(this);
}
sendContent(e) {
console.log(''sending input content ''+React.findDOMNode(React.refs.someref).value)
}
changeContent(e) {
this.setState({inputContent: e.target.value})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" ref="someref" value={this.inputContent}
onChange={this.changeContent} />
<button onClick={this.sendContent}>Submit</button>
</div>
)
}
}
export default SomeClass
Gracias
Puedes abordar esto de tres maneras
1. Vincula la función de evento en el propio constructor de la siguiente manera
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {inputContent: ''startValue''}
this.changeContent = this.changeContent.bind(this);
}
sendContent(e) {
console.log(''sending input content ''+React.findDOMNode(React.refs.someref).value)
}
changeContent(e) {
this.setState({inputContent: e.target.value})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" ref="someref" value={this.inputContent}
onChange={this.changeContent} />
<button onClick={this.sendContent}>Submit</button>
</div>
)
}
}
export default SomeClass
2.Enlazar cuando se llama
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {inputContent: ''startValue''}
}
sendContent(e) {
console.log(''sending input content ''+React.findDOMNode(React.refs.someref).value)
}
changeContent(e) {
this.setState({inputContent: e.target.value})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" ref="someref" value={this.inputContent}
onChange={this.changeContent} />
<button onClick={this.sendContent.bind(this)}>Submit</button>
</div>
)
}
}
export default SomeClass
3. Mediante el uso de funciones de flecha
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {inputContent: ''startValue''}
}
sendContent(e) {
console.log(''sending input content ''+React.findDOMNode(React.refs.someref).value)
}
changeContent(e) {
this.setState({inputContent: e.target.value})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" ref="someref" value={this.inputContent}
onChange={this.changeContent} />
<button onClick={()=>this.sendContent()}>Submit</button>
</div>
)
}
}
export default SomeClass
Puedes resolver esto siguiendo estos pasos
Cambie la función sendContent con
sendContent(e) {
console.log(''sending input content ''+this.refs.someref.value)
}
Cambiar la función de renderizado con
<input type="text" ref="someref" value={this.state.inputContent}
onChange={(event)=>this.changeContent(event)} />
<button onClick={(event)=>this.sendContent(event)}>Submit</button>
Solución:
-
Sin un enlace explícito,
bind
con el nombre del método, puedes usar la sintaxis de funciones de flecha gruesa () => {} que mantiene el contexto dethis
.
import React from ''react''
class SomeClass extends React.Component {
constructor(props) {
super(props)
this.state = {
inputContent: ''startValue''
}
}
sendContent = (e) => {
console.log(''sending input content '',this.state.inputContent);
}
changeContent = (e) => {
this.setState({inputContent: e.target.value},()=>{
console.log(''STATE:'',this.state);
})
}
render() {
return (
<div>
<h4>The input form is here:</h4>
Title:
<input type="text" value={this.state.inputContent}
onChange={this.changeContent} />
<button onClick={this.sendContent}>Submit</button>
</div>
)
}
}
export default SomeClass
Otras soluciones:
-
Vincula tus funciones en el constructor de la clase.
-
Vincula tus funciones en la plantilla JSX que se escapa de las llaves {} {this.methodName.bind (this)}
Tenemos que
bind
nuestra función con
this
para obtener la instancia de la función en clase.
Me gusta así en ejemplo
<button onClick={this.sendContent.bind(this)}>Submit</button>
De esta manera,
this.state
será un objeto válido.
Tus funciones deben vincularse para jugar con estado o accesorios en controladores de eventos
En ES5, vincule sus funciones de controlador de eventos solo en el constructor pero no se vincule directamente en el render. Si vincula directamente en el renderizado, crea una nueva función cada vez que su componente se renderiza y vuelve a renderizar. Así que siempre debes vincularlo en constructor
this.sendContent = this.sendContent.bind(this)
En ES6, use las funciones de flecha
Cuando utiliza las funciones de flecha, no necesita hacer enlaces y también puede mantenerse alejado de los problemas relacionados con el alcance
sendContent = (event) => {
}
bind(this)
puede solucionar este problema, y hoy en día podemos usar otras 2 formas de lograrlo si no le gusta usar
bind
.
1) Como es la forma tradicional, podemos usar
bind(this)
en el constructor, de modo que cuando usamos la función como devolución de llamada JSX, el contexto de
this
es la clase misma.
class App1 extends React.Component {
constructor(props) {
super(props);
// If we comment out the following line,
// we will get run time error said `this` is undefined.
this.changeColor = this.changeColor.bind(this);
}
changeColor(e) {
e.currentTarget.style.backgroundColor = "#00FF00";
console.log(this.props);
}
render() {
return (
<div>
<button onClick={this.changeColor}> button</button>
</div>
);
}
}
2) Si definimos la función como un atributo / campo de la clase con función de flecha, ya no necesitamos usar
bind(this)
.
class App2 extends React.Component {
changeColor = e => {
e.currentTarget.style.backgroundColor = "#00FF00";
console.log(this.props);
};
render() {
return (
<div>
<button onClick={this.changeColor}> button 1</button>
</div>
);
}
}
3) Si usamos la función de flecha como devolución de llamada JSX, tampoco necesitamos usar
bind(this)
.
Y aún más, podemos pasar los parámetros.
Se ve bien, ¿no?
pero su inconveniente es el problema del rendimiento, para más detalles, consulte
ReactJS doco
.
class App3 extends React.Component {
changeColor(e, colorHex) {
e.currentTarget.style.backgroundColor = colorHex;
console.log(this.props);
}
render() {
return (
<div>
<button onClick={e => this.changeColor(e, "#ff0000")}> button 1</button>
</div>
);
}
}
Y he creado un Codepen para hacer una demostración de estos fragmentos de código, espero que ayude.
this.changeContent
debe estar vinculado a la instancia del componente a través de
this.changeContent.bind(this)
antes de pasarlo como el accesorio
onChange
, de lo contrario, la variable
this
en el cuerpo de la función no se referirá a la instancia del componente sino a la
window
.
Ver
Function::bind
.
Cuando se usa
React.createClass
lugar de las clases ES6, todos los métodos que no son de ciclo de vida definidos en un componente se vinculan automáticamente a la instancia del componente.
Ver
Autobinding
.
Tenga en cuenta que vincular una función crea una nueva función. Puede vincularlo directamente en render, lo que significa que se creará una nueva función cada vez que se renderiza el componente, o vincularlo en su constructor, que solo se activará una vez.
constructor() {
this.changeContent = this.changeContent.bind(this);
}
vs
render() {
return <input onChange={this.changeContent.bind(this)} />;
}
Las referencias se establecen en la instancia del componente y no en
React.refs
: debe cambiar
React.refs.someref
a
this.refs.someref
.
También deberá vincular el método
sendContent
a la instancia del componente para que
this
refiera a él.