React Native——onPress 从“currentTarget”中提取 id

Posted

技术标签:

【中文标题】React Native——onPress 从“currentTarget”中提取 id【英文标题】:React Native-- onPress extract id from "currentTarget" 【发布时间】:2018-01-24 03:58:39 【问题描述】:

我已经在 React 上创建了一个游戏,我正在尝试使我的代码适应 React Native。困扰我的一件事是如何翻译这三行,因为在 RN 中没有可依赖的 DOM 解决方案:

handleClick(e) 

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

  

这里发生的情况是,一个无状态的子节点正在获取一个被点击的元素 id(currentTarget 的),并使用它来调用父节点内部定义的方法。但是这种公式e.currentTarget.id 在 RN 中不起作用。

有没有一种雄辩的方法可以在 RN 中重写这一行?

注意:有两个问题与这个问题有点相似,here 和 here,但是答案看起来更像是补丁,而不是结构优雅的解决方案。如果您知道某些事情,请发布答案。

编辑:似乎无法绕过 ReactNativeComponentTree。

到目前为止我有这么多,但这还不行:

handlePress(event) 

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

this.props.change(number);

  

第二次编辑:好吧,也许我应该添加一个简单的例子来说明我想要实现的目标。当我点击任何平面列表的元素时,它的 id 应该显示在 Child's bottom 上。点击重置将恢复默认状态。

下面的简单示例代码:

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

【问题讨论】:

【参考方案1】:

更好的方法(避免在每次渲染时创建事件回调) 获取当前按下的元素属性(本例中为 id) 是通过将其包装在父组件中,传递数据 并且只绑定一次所有事件(在构造函数中)

    首先声明您的组件包装器:它必须是一个类而不是无状态的功能组件,否则您无法避免在每次渲染时创建回调

要查看差异,这里有一个更高级的示例:https://snack.expo.io/ByTEKgEsZ (example source code)

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.textPressed>
        this.props.children
      </Text>
    );
  

如果你使用 const JSX 对象(无状态功能组件),它可以工作,但不是最优的,每次渲染组件时都会创建事件回调,因为箭头函数实际上是渲染函数的快捷方式

const TouchableText = props => 
  const textPressed = () => 
    props.onPressItem(props.id);
  ;
  return <Text onPress=textPressed />;
;
    然后使用此包装器代替您的组件,如下所示:
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 让您烦恼,有两种方法可以解决这个问题。如果您使用 experimental 属性初始化器语法,则可以使用属性初始化器正确绑定回调

这意味着我们只能写

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

而不是

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

handlePress(id) 


一些参考资料:Event handlers and Functional Stateless ComponentsReact Binding Patterns: 5 Approaches for Handling this

【讨论】:

啊哈!正是我想要的。这在 SO 上出现了很多,但问题的措辞不同,而且很少交叉链接。提供类似答案的人似乎坚持认为包装器必须是我参与的项目使用 const JSX 对象的类,而我无法找到两者是否兼容。到现在为止。 感谢您的评论!我检查了你所说的,实际上其他人是对的。如果你想避免重新渲染事件回调,你需要包装器成为一个类,否则它会在每次重新渲染时重新创建事件回调:/你可以在这里查看我的测试snack.expo.io/ByTEKgEsZ。您可以通过 x)) 包装包装器,如下所示以获得 const: const ComponentConst = props => ;我已经更新了我的答案,以更好地反映您的观察结果。再次感谢! 是的,当我回家尝试你的代码时,我无法让它工作。事实上,这个问题在 SO 和互联网上都有错误和错误的答案。我失去了几天试图让一些东西工作。最后,我什至不得不为 ListItem 类使用 PureComponent。 干净的解释清楚的答案。刚接触 RN,你的回答很有帮助,谢谢!【参考方案2】:

经过 8 小时的搜索,我自己找到了解决方案,这要感谢 Kyle Banks 出色的计算器教程。

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

基本上解决方案是在 onPress 事件侦听器的分配中沿 this 绑定 item.id,如下所示:

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 上,在 Parent 上 change():

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

【讨论】:

@P. Myer Nore:这正是您在***.com/questions/2236747/… 中评论的内容。 您不应该在 render 方法中使用 JSX / 中的 bind 或箭头函数。他们每次都创建一个新函数,这会导致组件被更改,从而导致每次都重新渲染组件,这首先破坏了使用 React 的大部分好处。然而,这方面的记录极差。

以上是关于React Native——onPress 从“currentTarget”中提取 id的主要内容,如果未能解决你的问题,请参考以下文章

react-native-swipeout onPress 方法禁用包含组件的 onPress 方法

react-native : onPress 不起作用

React Native Hamburger onPress 问题

React Native 改变视图 onPress

React Native 如何在“onPress”之后播放声音?

返回 JSX 组件 onPress react-native