【问题标题】:React does not update style on rerenderReact 不会在重新渲染时更新样式
【发布时间】:2017-07-20 14:07:57
【问题描述】:

我有一个 React 组件,它的可见性和位置可以由用户更改。

通过添加和删除 CSS 类的可见性,通过函数的位置,将拖放后的新位置设置为顶部和左侧。

这可行,但我的问题是 React 不会更新样式(因此不会将位置重写为初始位置),当组件为可见性重新渲染时。

class MoveableCard extends React.Component {
    ...
    render() {
        ...
        return <div className={(this.props.isVisible ? '' : 'hide')}
                    draggable="true" onDragStart={dragStart}
                    style={{top:'initial', left:'initial'}}>
            ...
        </div>
    }
}



function dragStart(event) {
    var style = window.getComputedStyle(event.target, null)
    event.dataTransfer.setData("text/plain", JSON.stringify({
        id:event.target.getAttribute('data-reactid'),
        x:(parseInt(style.getPropertyValue("left"),10) - event.clientX),
        y:(parseInt(style.getPropertyValue("top"),10) - event.clientY)
    }))
}
function dragOver(event) {
    event.preventDefault()
    return false
} 
function drop(event) {
    let data = JSON.parse(event.dataTransfer.getData("text/plain"))
    let el = document.querySelectorAll("[data-reactid='" + data.id + "']")[0]

    el.style.left = (event.clientX + parseInt(data.x, 10)) + 'px'
    el.style.top = (event.clientY + parseInt(data.y, 10)) + 'px'

    event.preventDefault()
    return false
}
document.body.addEventListener('dragover',dragOver,false)
document.body.addEventListener('drop',drop,false)

第一次渲染卡片时,样式类似于style="top: initial; left: initial;"

当卡片被移动时,样式看起来像style="top: 162px; left: 320px;"

当卡片关闭时,类hide被添加,但样式保持style="top: 162px; left: 320px;",不管我如何创建样式对象。

那么,我怎样才能强制 React 更新样式呢? 还是有其他方法可以做到这一点?

【问题讨论】:

  • 我认为你错过了 React 的重点。您应该使用 React 的 state 并调用 this.setState 而不是操纵 dom 元素的样式,这将自动为您调用 render facebook.github.io/react/docs/state-and-lifecycle.html
  • 就像上面的评论一样,您应该使用 react API 库来控制您的元素/组件,以便它可以在 state/prop 更改时重新渲染。直接使用 DOM 与 react 在幕后所做的相反,因为 react 创建了一个虚拟 DOM,而您在常规 DOM 上添加了一个事件侦听器。注册到 DOM 元素的任何事件都有效,但不会被 react 重新渲染,因为 react 是在单独的 DOM(虚拟 DOM)上运行的
  • 但是我怎样才能只通过 dom 元素访问我的 React 对象呢?包含我的位置的放置函数在我放置元素的元素上调用,而不是在移动的元素上调用。
  • 你可以给它添加一个引用:facebook.github.io/react/docs/refs-and-the-dom.html 或者使用父组件作为属性传递一个回调。 medium.com/@ruthmpardee/…

标签: javascript css reactjs drag-and-drop


【解决方案1】:

答案的简短版本:

使用内部状态和组件生命周期

加长版:

首先,我建议将事件处理程序放在组件中,而不是全局方法:

class MoveableCard extends React.Component {
  dragStart(event) {}
  dragOver(event) {}
  drop(event) {}
}

其次,在组件的构造函数中,将组件的 this-context 绑定到那些处理程序。 (好吧,或者你在渲染方法中使用箭头函数)

constructor() {
  this.dragStart = this.dragStart.bind(this);
  this.dragOver = this.dragOver.bind(this);
  this.drop = this.drop.bind(this);
}

为了让组件“更新”或重新渲染,我建议在这种情况下改变其内部状态。因此,您首先在componentWillMount内的初始状态中添加一个初始值。

componentWillMount() {
  this.state = { top: 0, left: 0 };
}

在事件处理程序中,您现在可以使用 this.setState 更新 innerState 上的 top 和 left(这就是您绑定 this 所需要的)。

drop() {
   // Assuming you filled in this.left and this.top in the dragOver method
   this.setState({ top: this.top, left: this.left });
}

通过使用 setState,将使用内部状态的新值触发重新渲染。现在,您可以在渲染方法中使用this.state.topthis.state.left

render() {
  return (
    <div className={(this.props.isVisible ? '' : 'hide')}
         draggable="true"
         onDragStart={this.dragStart}
         style={{top: this.state.top, left: this.state.left}}>
    </div>
  );
}

【讨论】:

    【解决方案2】:

    好的,根据 Andrew、dejakob 和 Chris 的回答得到了解决方案,谢谢大家 :)

    我最初认为,我无法将函数移动到组件中,因为具有最终位置的 Drop 事件是由我放置卡片的元素发出的,而不是卡片本身。

    但是有一个dragend事件,由卡片发出,包含位置。

    现在我只需要使用它来设置状态中的位置(并通过对父级中unsetPosition 的引用将其删除)。

    class MoveableCard extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                styles: {top:'initial', left:'initial'}
            }
            this.drop = this.drop.bind(this);
        }
    
        dragStart(e) {
            let style = window.getComputedStyle(e.target, null)
    
            this.setState({l: parseInt(style.getPropertyValue("left")) - e.clientX, y: parseInt(style.getPropertyValue("top")) - e.clientY})
        }
    
        drop(e) {
            this.setState({left: this.state.l + e.clientX, top: this.state.y + e.clientY})
    
            e.preventDefault()
            return false
        }
    
        unsetPosition() {
           this.setState({styles: {top:'initial', left:'initial'}})
        }
    
        render() {
            return <div className={(this.props.isVisible ? '' : 'hide')}
                        draggable="true"
                        onDragStart={this.dragStart}
                        onDragEnd={this.drop}
                        style={this.state.styles}>
                ...
            </div>
        }
    }
    

    【讨论】: