React Native- onPress извлечь идентификатор из "currentTarget"

Я создал игру для React и пытаюсь адаптировать свой код для React Native. Меня беспокоит то, как перевести эти три строки, так как в RN нет DOM-решений, на которые можно было бы положиться:

handleClick(e) {

this.props.change(e.currentTarget.id);

}  

Здесь происходит то, что дочерний элемент без состояния собирает идентификатор элемента, по которому щелкнули (currentTarget), и использует его для вызова метода, определенного внутри родительского элемента. Этот вид формулировки e.currentTarget.id Однако не работает в RN.

Есть ли красноречивый способ переписать этот лайнер в RN?

Примечание: есть два вопроса, смутно напоминающие этот, здесь и здесь, однако ответы выглядят скорее как патчи, чем как структурное элегантное решение. Если вы знаете что-то, пожалуйста, напишите ответ.

Изменить: кажется, что нельзя обойти ReactNativeComponentTree.

У меня так много, но это пока не работает:

handlePress(event) {

let number =  ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;

this.props.change(number);

}  

Второе редактирование: Хорошо, возможно, мне следует добавить упрощенный пример того, чего я пытаюсь достичь. Когда я нажимаю на любой из элементов плоского списка, его идентификатор должен отображаться в нижней части дочернего элемента. Нажатие на сброс восстановит состояние по умолчанию.

Код упрощенного примера ниже:

    import React, { Component } from 'react';
    import { AppRegistry, FlatList, StyleSheet, Text, View, Button } from 'react-native';
    import ReactNativeComponentTree from 'react-native';

    export default class Parent extends Component {

      constructor(props) {
        super(props);    

        this.state = {  

                quotes: ["a","bnaskdkahhahskkdk","c","d","e","a","b","c","d"],
                    size: [true, true, true, true, true, true, true, true, true],
                    color: [false, false, false, false, false, false, false, false, false],
                    progress: "me"

        };

        this.change = this.change.bind(this);
        this.reset = this.reset.bind(this);

      }

      change(number) {

      this.setState({color: [true, true, true, true, true, true, true, true, true],              progress: number});

      }

      reset() {

        this.setState({color: [false, false, false, false, false, false, false, false, false],
                       progress: "me"
        });

      }

      render() {
        return (
          <View style={styles.container}>
    <Child change={this.change} reset={this.reset} quotes={this.state.quotes} 
           size={this.state.size} color={this.state.color} 
           progress={this.state.progress} />
          </View>
        );
      }
    }

    class Child extends Component {

        constructor(props) {

        super(props);    

        this.handlePress = this.handlePress.bind(this);
        this.handleReset = this.handleReset.bind(this);
      }

        /*handlePress(e) {
          let number = e.currentTarget.id;
            this.props.change(number);
        }*/

        handlePress(event) {

    let number =  ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;

    this.props.change(number);

    }  

        handleReset() {
          this.props.reset();
        }

      render() {

        let ar = [];

        for (let i=0; i<this.props.quotes.length; i++) {
          let b = {key: `${i}`, id: i, 
              classSize: this.props.size[i] ? (i%2===0 ? styles.size : styles.oddsize) : "", 
              classColor: this.props.color[i] ? (i%2===0 ? styles.color : styles.oddcolor) : ""}
          ar.push(b);      

        }

        return (
        <View style={styles.container}>
          <Button onPress={this.handleReset} title="Reset" />
            <FlatList
              data={
                ar
              }

    renderItem={({item}) => <Text onPress={this.handlePress} 
    style={[item.classSize, item.classColor]}> {item.id+1} 
    {this.props.quotes[item.id]} </Text> }

            /> 

        <Text style={styles.size}>{this.props.progress}</Text>

        </View>
        );
      }
    }


    const styles = StyleSheet.create({
      container: {
       flex: 1,
       flexDirection: "column",
       //justifyContent: "center",
       alignItems: "center",
       paddingTop: 22,
       //backgroundColor: "purple" 
      },
      size: {
        flex: 1,
        padding: 10,
        fontSize: 18,
        backgroundColor: "grey",
        margin: 1,
        height: 44,
        color: 'gold',
        borderColor: "white",
        borderWidth: "1",
        textAlign: "center"
      },
      oddsize: {
        flex: 1,
        padding: 10,
        fontSize: 18,
        backgroundColor: "white",
        margin: 1,
        height: 44,
        color: 'gold',
        borderColor: "white",
        borderWidth: "1",
        textAlign: "center"
      },
      color: {
        flex: 1,
        padding: 10,
        backgroundColor: 'grey',
        //borderRadius: "25%",
        margin: 1,
        fontSize: 18,
        height: 44,
        color: 'pink',
        borderColor: "red",
        borderWidth: "1"
      },
    oddcolor: {
        flex: 1,
        padding: 10,
        backgroundColor: 'white',
        //borderRadius: "25%",
        margin: 1,
        fontSize: 18,
        height: 44,
        color: 'pink',
        borderColor: "red",
        borderWidth: "1"
      }
    })

    // skip this line if using Create React Native App
    AppRegistry.registerComponent('AwesomeProject', () => Parent);

2 ответа

Лучший способ (избегая создания обратных вызовов событий при каждом рендере)
получить текущие свойства нажатого элемента (идентификатор в этом примере)
это оборачивая его в родительский компонент, передавая данные
и привязка всех событий только один раз (в конструкторе)

  1. Сначала объявите обертку вашего компонента:
    Это должен быть класс, а не функциональный компонент без состояния, иначе вы не сможете избежать создания обратного вызова при каждом рендеринге.

    Чтобы увидеть разницу, вот более продвинутый пример в действии:
    https://snack.expo.io/ByTEKgEsZ ( пример исходного кода)

class TouchableText extends React.PureComponent {
  constructor(props) {
    super(props);
    this.textPressed = this.textPressed.bind(this);
  }

  textPressed(){
    this.props.onPressItem(this.props.id);
  }

  render() {
    return (
      <Text style={styles.item} onPress={this._onPress}>
        {this.props.children}
      </Text>
    );
  }
}

Если вы используете const JSX-объект (функциональный компонент без сохранения состояния), он работает, но он не оптимален, обратный вызов события будет создаваться каждый раз при визуализации компонента, так как функция стрелки фактически является ярлыком для функции рендеринга.

const TouchableText = props => {
  const textPressed = () => {
    props.onPressItem(props.id);
  };
  return <Text onPress={textPressed} />;
};
  1. Затем используйте эту обертку вместо вашего компонента следующим образом:
class Test extends React.Component {
  constructor(props) {
    super(props);
    //event binding in constructor for performance (happens only once)
    //see facebook advice: 
    //https://facebook.github.io/react/docs/handling-events.html
    this.handlePress = this.handlePress.bind(this);
  }

  handlePress(id) {
    //Do what you want with the id
  }

  render() {
    return (
      <View>
        <FlatList
          data={ar}
          renderItem={({ item }) => (
            <TouchableText
              id={item.id}
              onPressItem={this.handlePress}
            >
              {`Component with id ${item.id}`}
            </TouchableText>
          )}
        />
      </View>
    );
  }
}

Как написано в React Native Handling Events Doc, существует другой возможный синтаксис, позволяющий избежать привязки в конструкторе (хотя экспериментальный: то есть, который может поддерживаться или не поддерживаться в будущем!):

Если вызов bind вас раздражает, есть два способа обойти это. Если вы используете экспериментальный синтаксис инициализатора свойств, вы можете использовать инициализаторы свойств для правильной привязки обратных вызовов

это означает, что мы могли только написать

handlePress = (id) => {
    //`this` is already bound!
}

вместо

constructor(props) {
    super(props);
    //manually bind `this` in the constructor
    this.handlePress = this.handlePress.bind(this);
}

+

handlePress(id) {

}

Некоторые ссылки:
Обработчики событий и функциональные компоненты без сохранения состояния
Реагировать Шаблоны привязки: 5 подходов для обработки этого

После 8 часов поиска я нашел решение самостоятельно, благодаря фантастическому руководству по калькулятору Кайла Бэнкса.

https://kylewbanks.com/blog/react-native-tutorial-part-3-developing-a-calculator

Ну, в основном решение заключается в связывании item.id вместе с этим в назначении прослушивателя события onPress, например, так:

renderItem={({item}) => <Text
  onPress={this.handlePress.bind(this, item.id)} 
  style={[item.classSize, item.classColor]}>
  {item.id+1} 
  {this.props.quotes[item.id]}
</Text> }

После этого единственное, что вам нужно сделать, это определить handlePress следующим образом:

handlePress(e) {
    this.props.change(e);
}

Где e это item.id в Child и change() в Parent:

  change(number) {

  this.setState({color: [true, true, true, true, true, true, true, true, true], progress: number});

  }

Полный код работает как задумано.

import React, { Component } from 'react';
import { AppRegistry, FlatList, StyleSheet, Text, View, Button } from 'react-native';
import ReactNativeComponentTree from 'react-native';

export default class Parent extends Component {

  constructor(props) {
    super(props);    

    this.state = {  

            quotes: ["a","bnaskdkahhahskkdk","c","d","e","a","b","c","d"],
                size: [true, true, true, true, true, true, true, true, true],
                color: [false, false, false, false, false, false, false, false, false],
                progress: "me"

    };

    this.change = this.change.bind(this);
    this.reset = this.reset.bind(this);

  }

  change(number) {

  this.setState({color: [true, true, true, true, true, true, true, true, true], progress: number});

  }

  reset() {

    this.setState({color: [false, false, false, false, false, false, false, false, false],
                   progress: "me"
    });

  }

  render() {
    return (
      <View style={styles.container}>
<Child change={this.change} reset={this.reset} quotes={this.state.quotes} 
       size={this.state.size} color={this.state.color} 
       progress={this.state.progress} />
      </View>
    );
  }
}

class Child extends Component {

    constructor(props) {

    super(props);    

    this.handlePress = this.handlePress.bind(this);
    this.handleReset = this.handleReset.bind(this);
  }

handlePress(e) {
        this.props.change(e);
    }

    /*  handlePress(event) {

let number =  ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.id;

this.props.change(number);

}  */

    handleReset() {
      this.props.reset();
    }

  render() {

    let ar = [];

    for (let i=0; i<this.props.quotes.length; i++) {
      let b = {key: `${i}`, id: i, 
          classSize: this.props.size[i] ? (i%2===0 ? styles.size : styles.oddsize) : "", 
          classColor: this.props.color[i] ? (i%2===0 ? styles.color : styles.oddcolor) : ""}
      ar.push(b);      

    }

    return (
    <View style={styles.container}>
      <Button onPress={this.handleReset} title="Reset" />
        <FlatList
          data={
            ar
          }

renderItem={({item}) => <Text onPress={this.handlePress.bind(this, item.id)} 
style={[item.classSize, item.classColor]}> {item.id+1} 
{this.props.quotes[item.id]} </Text> }

        /> 

    <Text style={styles.size}>{this.props.progress}</Text>

    </View>
    );
  }
}


const styles = StyleSheet.create({
  container: {
   flex: 1,
   flexDirection: "column",
   //justifyContent: "center",
   alignItems: "center",
   paddingTop: 22,
   //backgroundColor: "purple" 
  },
  size: {
    flex: 1,
    padding: 10,
    fontSize: 18,
    backgroundColor: "grey",
    margin: 1,
    height: 44,
    color: 'gold',
    borderColor: "white",
    borderWidth: "1",
    textAlign: "center"
  },
  oddsize: {
    flex: 1,
    padding: 10,
    fontSize: 18,
    backgroundColor: "white",
    margin: 1,
    height: 44,
    color: 'gold',
    borderColor: "white",
    borderWidth: "1",
    textAlign: "center"
  },
  color: {
    flex: 1,
    padding: 10,
    backgroundColor: 'grey',
    //borderRadius: "25%",
    margin: 1,
    fontSize: 18,
    height: 44,
    color: 'pink',
    borderColor: "red",
    borderWidth: "1"
  },
oddcolor: {
    flex: 1,
    padding: 10,
    backgroundColor: 'white',
    //borderRadius: "25%",
    margin: 1,
    fontSize: 18,
    height: 44,
    color: 'pink',
    borderColor: "red",
    borderWidth: "1"
  }
})

// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => Parent);
Другие вопросы по тегам