简体   繁体   中英

How to access `this` inside a renderRow of a ListView?

I'm having trouble to run a function for the onPress event inside a row of a ListView . I'm following the React Native tutorial trying to continue from there. It seems it's using ES6 syntax style.

This is the relevant part of the code.

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */

import React, {  
  TouchableHighlight,
  AppRegistry,
  Component,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
  Alert,
} from 'react-native';

class AwesomeProject extends Component {
  constructor(props) {
    super(props);
    this.something = this.something.bind(this); // <-- Trying this, not even sure why
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

//
//Irrelevant code here. Fetching stuff and renderLoadingView
//

  something = function(){
    console.log('something');
    Alert.alert(
      'Alert Title',
      'alertMessage',
    );
  }


  render() {
    console.log('this', this); //this is an instance of AwesomeProject
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie}
        style={styles.listView}
        />
    );
  }

  renderMovie(movie) {
    console.log('Not this', this); //this is not an instance of AwesomeProject
    return (
      <TouchableHighlight onPress={() => {console.log(this); this.something()}}>
     <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}                    
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}

          >{movie.title}</Text>
          <Text style={styles.year}>{movie.year}</Text>
        </View>
      </View>
       </TouchableHighlight>
    );
  }
}

//
//More irrelevant code here. Styles and the 
//

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

I'm unable to run the something function. I've tried various syntax without success. The problem seems to be that this is not what I expect it to be, and it doesn't have the variable something defined.

The only way I had to make it work was to declare an external variable with var something = function(){...} outside the AwesomeProject class.

Is there a way to access something while being declared inside AwesomeProject ? Is this maybe a bad practice according to flux architecture or something?

Turns out I found the solution myself, here: https://github.com/goatslacker/alt/issues/283#issuecomment-115391700

The problem is that with ES6 syntax you have no autobinding of this , so you need to do it yourself.

I've learned a bit more since I posted this answer. The real problem is that this is "bound" when the function is executed, not when it's declared. It points to the object the function is attached to when called . For example:

 obj.foo(); //inside foo, this points to obj bar(); //inside bar, this points to the global object (or undefined in strict mode) 

In my example the function this.renderMovie() is passed as a parameter. So inside ListView it will be a "local variable". It will be called "detached" from any object, like the bar() example before, so this will not be what I expect.

It's possible to force the binding of this .

There are 3 possible solutions to my problem, and I like the 3rd one the best:

Option 1: Manually bind this on call

On the code, replace the referece to {this.renderMovie} whith {this.renderMovie.bind(this)}

  render() {
    console.log('this', this); //this is an instance of AwesomeProject
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie.bind(this)} //<-- change here
        style={styles.listView}
        />
    );
  }

Option 2: Do the binding on the constructor

And of course, bind the right function:

  constructor(props) {
    super(props);
    this.something = this.something.bind(this); // <-- Not this function
    this.renderMovie = this.renderMovie.bind(this); // <-- This function!!
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

Option 3: use the arrow syntax and let them do the job

I like this one the best. You just need to declare the involved function using the arrow syntax so this:

  renderMovie(movie) {
    //code here
  }

becomes this:

  renderMovie = (movie) => {
    //code here
  }

In addition to https://stackoverflow.com/a/36779517/6100821

Option 4: use bind operator

<ListView
    dataSource={this.state.dataSource}
    renderRow={::this.renderMovie} //<-- cleaner, than bind
    style={styles.listView}
    />

Be careful, it's just a proposal , not a standard!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM