内部onMouseEnter有时在父的onMouseMove调用setState

时间:2016-09-19 02:55:41

标签: javascript reactjs

在外部组件的onMouseMove中调用this.setState可防止内部组件的onMouseEnter触发有时

看起来像onMouseMove中的setState中断或以其他方式阻止内部元素onMouseEnter被触发。我知道setState是异步的但是我不相信我这里有一个陈旧的读取问题,至少在我的调用代码中没有。也许在React中有一个陈旧的读取,或者其他一些bug?我也是React的新手,所以我完全可能忽略了一些显而易见的东西!!

动画GIF演示症状:http://imgur.com/a/9V0WR

为了在重现问题时获得最佳效果,请运行FULL SCREEN代码段,以便查看整个页面。然后将鼠标从列表外部的顶部向下拖动到第0列,并一直向下移动到底部。另请参阅随附的GIF,以证明此问题。

const logEl = document.querySelector('#eventlog')
const logEl2 = document.querySelector('#eventlog2')

const log = (el, msg) => {
  el.innerText = msg + '\n' + logEl.innerText
}

const Table = ({ data, onItemHover }) => {
  return <table>
    <tbody>{data.map((row, y) => (
      <tr key={y}>{row.map((item, x) => (
        <td key={x} onMouseEnter={() => onItemHover(item)} style={{ color: '#aaa' }}>{item}</td>
      ))}</tr>
    ))}</tbody>
  </table>
}

const Tooltip = ({ text, position }) => (
  <div style={{ position: 'absolute', left: position.x, top: position.y }}>{text}</div>
)

class TableContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      hoveredItem: null,
      mouse: { x: 0, y: 0 }
    }
    this.handleMouseLeave = this.handleMouseLeave.bind(this)
    this.handleMouseMove = this.handleMouseMove.bind(this)
    this.handleItemHover = this.handleItemHover.bind(this)
  }
  
  handleMouseMove(e) {
    const rect = e.currentTarget.getBoundingClientRect()
    const mouse = { x: e.pageX - rect.left, y: e.pageY - rect.top }
    
    // XXX: this setState call can prevent the <td onMouseEnter=> from firing
    if (this.props.showBug)
      this.setState({ mouse })
  }
  
  handleMouseLeave(e) {
    this.setState({ hoveredItem: null })
  }
  
  handleItemHover(hoveredItem) {
    this.props.logger('handleItemHover ' + hoveredItem)
    this.setState({ hoveredItem })
  }
  
  render() {
    const {data} = this.props
    const {hoveredItem, mouse} = this.state
    
    return <div style={{ position: 'relative' }} onMouseLeave={this.handleMouseLeave} onMouseMove={this.handleMouseMove}>
      <Table data={data} onItemHover={this.handleItemHover} />
      {hoveredItem && <Tooltip text={hoveredItem} position={mouse} />}
    </div>
  }
}

const width = 10
const height = 10

const range = (stop) => Array.from(Array(stop).keys())

const data = range(height).map(y => (
	range(width).map(x => (
    `${x},${y}`
  ))
))

ReactDOM.render(
  <TableContainer data={data} showBug={true} logger={log.bind(null, logEl)} />,
  document.getElementById('container')
);

ReactDOM.render(
  <TableContainer data={data} showBug={false} logger={log.bind(null, logEl2)} />,
  document.getElementById('container2')
);
.col {
  width:50%;
  float:left;
}
<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>


<p>

<strong>
Problem: Calling this.setState in outer component's onMouseMove prevents inner component's onMouseEnter from firing sometimes?
</strong>

<p style="opacity:0.5;">


<strong>Pre-recorded GIFs. Interactive version below.</strong>
<br>
<img src="http://i.imgur.com/xNUxhGI.jpg" style="width:200px; opacity:0.8;">
<img src="http://i.imgur.com/YlNKujg.jpg" style="width:200px; opacity:0.8;">
<br>
<br>

</p>
<small>To reproduce, move mouse around both grids rapidly. Starting outside and moving from top to bottom seems to reproduce it easily.</small>
</p>

<div class="col">
<strong style="color:red">doesn't work</strong>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

<div id="eventlog">
</div>
</div>


<div class="col">
<strong style="color:green">works</strong>

<div id="container2">
    <!-- This element's contents will be replaced with your component. -->
</div>

<div id="eventlog2">
</div>
</div>

1 个答案:

答案 0 :(得分:0)

您的工具提示正在掩盖表格并直接在鼠标下方,因此mouseEnter事件不一致。请参阅下面的代码片段,其中我将工具提示偏移50像素,它似乎完美无缺。

&#13;
&#13;
const logEl = document.querySelector('#eventlog')
const logEl2 = document.querySelector('#eventlog2')

const log = (el, msg) => {
  el.innerText = msg + '\n' + logEl.innerText
}

const Table = ({ data, onItemHover }) => {
  return <table>
    <tbody>{data.map((row, y) => (
      <tr key={y}>{row.map((item, x) => (
        <td key={x} onMouseEnter={() => onItemHover(item)} style={{ color: '#aaa' }}>{item}</td>
      ))}</tr>
    ))}</tbody>
  </table>
}

const Tooltip = ({ text, position }) => (
  <div style={{ position: 'absolute', left: position.x, top: position.y }}>{text}</div>
)

class TableContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      hoveredItem: null,
      mouse: { x: 0, y: 0 }
    }
    this.handleMouseLeave = this.handleMouseLeave.bind(this)
    this.handleMouseMove = this.handleMouseMove.bind(this)
    this.handleItemHover = this.handleItemHover.bind(this)
  }
  
  handleMouseMove(e) {
    const rect = e.currentTarget.getBoundingClientRect()
    const mouse = { x: e.pageX - rect.left + 50, y: e.pageY - rect.top + 50 }
    
    // XXX: this setState call can prevent the <td onMouseEnter=> from firing
    if (this.props.showBug)
      this.setState({ mouse })
  }
  
  handleMouseLeave(e) {
    this.setState({ hoveredItem: null })
  }
  
  handleItemHover(hoveredItem) {
    this.props.logger('handleItemHover ' + hoveredItem)
    this.setState({ hoveredItem })
  }
  
  render() {
    const {data} = this.props
    const {hoveredItem, mouse} = this.state
    
    return <div style={{ position: 'relative' }} onMouseLeave={this.handleMouseLeave} onMouseMove={this.handleMouseMove}>
      <Table data={data} onItemHover={this.handleItemHover} />
      {hoveredItem && <Tooltip text={hoveredItem} position={mouse} />}
    </div>
  }
}

const width = 10
const height = 10

const range = (stop) => Array.from(Array(stop).keys())

const data = range(height).map(y => (
	range(width).map(x => (
    `${x},${y}`
  ))
))

ReactDOM.render(
  <TableContainer data={data} showBug={true} logger={log.bind(null, logEl)} />,
  document.getElementById('container')
);

ReactDOM.render(
  <TableContainer data={data} showBug={false} logger={log.bind(null, logEl2)} />,
  document.getElementById('container2')
);
&#13;
.col {
  width:50%;
  float:left;
}
&#13;
<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>


<p>

<strong>
Problem: Calling this.setState in outer component's onMouseMove prevents inner component's onMouseEnter from firing sometimes?
</strong>

<p style="opacity:0.5;">


<strong>Pre-recorded GIFs. Interactive version below.</strong>
<br>
<img src="http://i.imgur.com/xNUxhGI.jpg" style="width:200px; opacity:0.8;">
<img src="http://i.imgur.com/YlNKujg.jpg" style="width:200px; opacity:0.8;">
<br>
<br>

</p>
<small>To reproduce, move mouse around both grids rapidly. Starting outside and moving from top to bottom seems to reproduce it easily.</small>
</p>

<div class="col">
<strong style="color:red">doesn't work</strong>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

<div id="eventlog">
</div>
</div>


<div class="col">
<strong style="color:green">works</strong>

<div id="container2">
    <!-- This element's contents will be replaced with your component. -->
</div>

<div id="eventlog2">
</div>
</div>
&#13;
&#13;
&#13;