React-呈现先前状态更改后立即更改状态

时间:2019-12-02 19:39:44

标签: javascript reactjs

当用户单击它时,我想制作一个很酷的框增长动画(展开),我想通过以下方式做到这一点:

  1. 用户单击展开按钮->通过ref获取div尺寸和顶部/左侧位置,将其存储在状态中,然后将div的样式分配给这些值
  2. 更改了expanded状态变量,并将div的position更改为fixed,还更改了left, top值和width, height css值

我的问题是在最初的div扩展点击中。似乎两个状态的更改都在一个周期内呈现,因此我在第一次展开点击时看不到平滑的动画。我尝试通过setState回调来完成此操作,还尝试在div尺寸处于状态后通过expanded方法更新componentDidUpdate,除了通过{设置expanded {1}}。

通过setTimeout回调的代码示例

setState

如何从Charts中实现该样式在更改基于扩展值的类之前会渲染比例?我既不想使用setTimeout也不想在o​​nScroll事件等中更新所有图表比例。

2 个答案:

答案 0 :(得分:3)

您只需要向setState传递一个函数而不是一个对象,以确保按顺序应用状态更改:

this.setState(previousState => ({
  previousChange: "value" 
}))

this.setState(previousState => ({
  afterPreviousChange: previousState.previousChange 
}))

https://reactjs.org/docs/react-component.html#setstate

另一种选择是将回调传递给setState,该回调在应用状态更改后运行,例如:

this.setState({ someChange: "value" }, () => this.setState({ 
  otherChange: "value" 
}))

CSS过渡也可以帮上忙。

答案 1 :(得分:1)

使用React状态为属性设置动画不是正确的方法。状态更新总是会批量处理,您通常不希望每秒重新渲染整个组件60次

在您的状态下存储'expanded'布尔值,并相应地更改element的类。使用CSS在两个状态之间添加动画

handleClick = () => {
  this.setState({ expanded: !this.state.expanded })
}

render() {
  return (
    <div
      className={`box ${this.state.expanded ? 'expanded' : ''}`}
      onCLick={this.handleClick}
    />
  )
}

在您的CSS中

.box {
    width: 100px;
    height: 100px;
    transition: width 2s;
}

.expanded {
    width: 300px;
}

根据评论添加:

您想要做的是:

  1. position: fixed设置为您的元素。这将立即将其捕捉到屏幕顶部,因此您需要选择右上和左值,以便固定位置从静态位置开始(默认)。为此,您可以使用element.getBoundingClientRect()
  2. 计算所需的topleft属性,这些属性将使您的元素出现在屏幕中间,并应用它们。
  3. 非常重要:在第1步和第2步之间,浏览器必须渲染页面以应用位置以及初始的上下值,以便从中开始动画。如果我们一个接一个地同步应用这两种样式,将无法做到这一点,因为在JS堆栈框架清晰之前,页面不会呈现。将第2阶段的逻辑包装在setTimeout中,以确保浏览器使用在第1阶段应用的样式至少渲染一次。

粗略的工作示例:

class Example extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      expanded: false,
      style: {}
    }
  }

  handleClick = (e) => {
    if (!this.state.expanded) {
      const r = e.target.getBoundingClientRect()
      const style = {
        top: r.y,
        left: r.x,
      }
      this.setState({
        expanded: !this.state.expanded,
        style
      })
      setTimeout(() => {
        this.setState({
          style: {
            top: (window.innerHeight / 2) - 50,
            left: (window.innerWidth / 2) - 50,
          }
        })
      })
    } else {
      this.setState({
        expanded: false,
        style: {}
      })
    }
  }

  render() {
    return (
      <div className={'container'}>
        <div className={'empty'} />
        <div className={'empty'} />
        <div className={'empty'} />
        <div
          onClick={this.handleClick}
          className={`box ${this.state.expanded ? 'expanded' : ''}`}
          style={this.state.style}
        />
      </div>
    )
  }
}

和styles.css

* {
    box-sizing: border-box;
    padding: 0;
    margin: 0;
}
.container {
    height: 200vh;
}
.empty {
    height: 100px;
    width: 100px;
    border: 1px solid gray;
}
.box {
    height: 100px;
    width: 100px;
    border: 3px solid red;
    transition: all 0.5s;
}
.expanded {
    position: fixed;
}