【问题标题】:How is it possible my ReactJS component alternates between my old state and my new state?我的 ReactJS 组件怎么可能在旧状态和新状态之间交替?
【发布时间】:2019-07-13 09:56:33
【问题描述】:

我在 render() 函数中添加了一个 console.log。

我在函数中使用 setState()。然后我的组件更新,但在我可以检查状态是否恢复到旧状态之后立即更新。

编辑:现在我的问题是在扩展时正确显示状态,我认为其余的工作正常。

这是我的沙盒: https://codesandbox.io/s/77ymmnpr1

在我的代码中,我创建了三个可扩展的标签,然后我放置了一些 labelComponent 来覆盖它们。叠加层有一个按钮可以返回菜单。问题是,当我随后展开另一个 labelComponent 时,旧状态稍有滞后,然后显示新状态。因此,我提供给您的 console.log。

这会在控制台中添加如下内容:

PATHNAME: newState
PATHNAME: oldState
PATHNAME: newState
PATHNAME: oldState

编辑:现在我的控制台在第二次渲染时返回:

stateOne

currentState

然后,在第三次渲染时:

stateOne

stateTwo

currentState

第四次渲染:

stateOne

stateTwo

stateThree

currentState

这是什么意思?我调用 this.setState() 只是为了将其放入新状态,那么为什么它会回到旧状态呢?

这是我的 react.js 代码:

export default class Label extends Component {

  state={ 
    labelStock:[],

    sequence:undefined,
    labelToRemove:[],
    pathname:"",
    displayLabel:false,

    displayFreshFood:false,
    displayLocalFarm:false,
    displayBeerQuality:false
  }

  componentDidMount(){   
      this.setLabelArray()
  }

  shouldComponentUpdate(nextProps, nextState) {  
    let {pathname}= nextState   
    console.log("NEXTSTATE: ", pathname) 
    if(this.state.pathname !== nextState.pathname){ 
        if((
          pathname.includes("freshfood") ||
          pathname.includes("localfarm") ||
          pathname.includes("beerquality") ) && this.state.labelToRemove.length >0
          ){  
            this.setState({displayLabel:true})
          }
          else if(this.state.displayLabel !== nextState.displayLabel){ 
            this.setState({displayLabel:false})            
          }  

          return true
    }    
    else if(this.state.displayLabel !== nextState.displayLabel){ 
        return true 
    }

    return false
  }
  setLabelArray=()=>{ 
    let labelArray=[]

    labelStock.forEach(function (item) {
      labelArray.push(item.name);
    });

    this.setState({labelStock:labelArray})
  }

  renderLabel=()=>(
    labelStock.map((item, index) =>{

      return ( 
        <div 
          ref={item.name}
          key={index}
          style={{backgroundImage: `url(${item.image})`}}
          className={this.state.labelToRemove.includes(item.name) ? `${style.label_container} ${style.label_to_remove}` : style.label_container}
        >
          <div  
          style={{display: this.state.displayLabel ? 'none' : 'initial' }}
          onClick={() => this.expand(item.name)} 
          className={style.more_info_button}> 
              <img  src={moreInfoButton} alt=""/> 
          </div>
          <div id={style.label_item} > Here some text</div>
        </div>
      )
  })
)

  expand=(item)=>{ 

    console.log("IN EXPAND")
    let removeItem= this.state.labelStock.filter(word => word!== item);
    this.setState({
      labelToRemove:removeItem,
      displayLabel:true,
      pathname:""
    })
    let [removeOne, removeTwo]= removeItem 
    let removeOneStyle=this.refs[removeOne].style;
    let removeTwoStyle=this.refs[removeTwo].style;

    removeOneStyle.opacity="0";
    removeTwoStyle.opacity="0";
    removeOneStyle.top="-20%";
    removeTwoStyle.top="-20%";

    setTimeout(function(){ 
      removeOneStyle.width="0vw";
      removeTwoStyle.width="0vw";

    }, 750);

    removeOneStyle.visibility="hidden";
    removeTwoStyle.visibility="hidden";

    let itemStyle= this.refs[item].style
    let layoutStyle=this.refs.layout.style
    layoutStyle.padding="0";
    layoutStyle.gridColumnGap="0";


    this.refs[item].addEventListener('transitionend', (e)=> {
      // let label= item.toLowerCase()
      // this.setState({pathname:label})
      if(e.propertyName !=="width") return 
      setTimeout(() => {  
        let updatedPathname= "label/" + item.toLowerCase(); 
        let label= item.toLowerCase()
        if(e.propertyName ==="width") this.setState({pathname:label})
      }, 1000);
    }); item

    itemStyle.height= 100+"vh";
    itemStyle.width= 100+"vw";    
    itemStyle.zIndex="100";     
  } 


  menuBack=()=>{  
    this.setState({
      displayLabel:false,
      labelToRemove:""
    })

    let item = this.state.pathname

    let enterItem= this.state.labelStock.filter(word => word!== item);
    // this.setState({

    // })
    let [enterOne, enterTwo]= enterItem
    // console.log("removeOne, removeTwo: ", removeOne, removeTwo)
    let enterOneStyle=this.refs[enterOne].style;
    let enterTwoStyle=this.refs[enterTwo].style;

    enterOneStyle.opacity="1";
    enterTwoStyle.opacity="1";

    enterOneStyle.top="0%";
    enterTwoStyle.top="0%";

    setTimeout(function(){ 
      enterOneStyle.width="25vw";
      enterTwoStyle.width="25vw";

    }, 750);

    enterOneStyle.visibility="visible";
    enterTwoStyle.visibility="visible";

    let itemStyle= this.refs[item].style
    let layoutStyle=this.refs.layout.style
    layoutStyle.padding="0 1vw";
    layoutStyle.gridColumnGap="1.5vw";

    itemStyle.height= "50vh";
    itemStyle.width= "25vw";    
    itemStyle.zIndex="10";  
    this.setState({pathname:""})
  }

  render() { 
    var hiddenStyle={ visiblity:"hidden"};
    var displayStyle={display:"inline-block"};
    var displayFreshFood = this.state.pathname.includes("freshfood") && this.state.displayLabel
    var displayBeerQuality = this.state.pathname.includes("beerquality") && this.state.displayLabel
    var displayLocalFarm= this.state.pathname.includes("localfarm") && this.state.displayLabel

    console.log("PATHNAME: "+ this.state.pathname) 
          return (
            <div
              className={style.page}
            >             

              <div
                ref="freshfood"
                className={style.label_content}
                style={{display: this.state.pathname.includes("freshfood") && this.state.displayLabel ? 'block' : 'none' }}
              >              
                <FreshFood/>
              </div>

              <div
                ref="localfarm"
                className={style.label_content}
                style={{display: this.state.pathname.includes("localfarm") && this.state.displayLabel ? 'block' : 'none' }}
              >  
                <LocalFarm/>
              </div>

              <div
                ref="beerquality"
                className={style.label_content}
                style={{display: this.state.pathname.includes("beerquality") && this.state.displayLabel ? 'block' : 'none' }}
              >  
                <BeerQuality
                  style= {this.state.displayBeerQuality? 
                  displayStyle: hiddenStyle}
                />
               </div>

              <div
                        className={style.label_background}
                                style={{opacity: this.state.displayLabel ? 0.5: 1 }}

              >
                <div
                  id={style.presentation_video}
                > 
                <video 
                  autoPlay="autolay" 
                  muted
                  loop        
                  controls
                >
                    <source src={goldLightVideo} type="video/mp4"/>
                    <source src={goldLightVideo} type="video/ogg"/>
                    error: video module fails to display
                </video>        
                </div>
                <div 
                  ref="layout"
                  className={style.label_layout}
                >
                    {this.renderLabel()}
                </div>
              </div>      
            <BackButton
              className={style.back_button}
              visible={this.state.displayLabel}
              onClick={this.menuBack}
            >
              <img id={style.back_button_icon} src={backButtonIcon} alt=""/>
            </BackButton>
      </div> 
      )


  }
}

任何提示都会很棒, 谢谢

【问题讨论】:

  • 你能提供你的代码吗?
  • 如果没有MCVE 或者至少还有一些代码,就不可能说。您可能在 render 或某些事件处理程序中调用 setState
  • 这样的查询需要代码审查。请花一些时间分享代码,以便社区更好地接收和快速解决方案
  • 好的,我已经提供了我的代码
  • 如果需要根据更新后的 props/state 计算 nextState,请使用getderivedstatefromprops。或者 componentWillRecieveProps/componentDidUpdate 如果你使用的是旧版 React。

标签: javascript reactjs state


【解决方案1】:

您正在为transitionend 事件添加一个事件侦听器,它会在超时后设置状态。但是,您永远不会删除该侦听器,因此每次您在 DOM 中操作元素转换时都会调用它,这基本上是在每个单击事件上。您可以通过在完成工作后(即设置状态后)删除 transitionend 事件侦听器来解决此问题。以下代码应放在您的扩展函数中,替换您的 this.refs[item].addEventListener(...) 逻辑:

this.addTransitionListener = listener.bind(this);
function listener(e) {
  // let label= item.toLowerCase()
  // this.setState({pathname:label})
  if (e.propertyName !== "width") return
  setTimeout(() => {
    if (e.propertyName === "width") {
      let updatedPathname = "label/" + item.toLowerCase();
      let label = item.toLowerCase()
      this.setState({
        pathname: label,
        displayLabel: true
      }, () => {
        console.log('removing listener')
        this.refs[item].removeEventListener('transitionend', this.addTransitionListener)
      })
    }
  }, 1000);
}
this.refs[item].addEventListener('transitionend', this.addTransitionListener);

可以在此处找到包含更改的代码分支:https://codesandbox.io/s/34klvpyw9p

【讨论】:

  • 感谢这个很棒的答案,看起来效果很好,我会尽快阅读代码
  • 很高兴能帮上忙,Webwoman!如果我能够满意地回答您的问题,请不要忘记将其标记为答案。
  • 是的,我有一个关于你的代码的问题,你为什么要使用 bind 等等,我已经在我的身上测试过,只需在 listenerEvent 中添加 this.refs[item].removeEventListener( "transitionend", this.addTransitionListener ); 就可以了,所以我想知道是否也许你的举动有特殊原因?
  • 我让事情变得比需要的更复杂:)。我之前试图在类本身的expand 函数之外定义侦听器函数,而setState() 调用不起作用,因为类对象尚未绑定到该函数。该代码是溢出的。它也应该以你已经完成的方式工作。
  • 我的错 :),我在测试更改时使用了您的代码:p,实际上只是将其放入我的代码中,因为以下无法停止迭代
【解决方案2】:

问题在于 SCU 更新状态:

shouldComponentUpdate(nextProps, nextState) {  
    // ... edited for brevity

            // this setState will lead to unexpected side effects
            this.setState({displayLabel:true})

    // ... edited for brevity
  }

考虑 React 组件的生命周期 - setState 触发渲染阶段。在渲染阶段,React 来到 SCU 以查看它是否应该继续渲染或退出。如果您安排另一个渲染(通过 SCU 中的另一个 setState),则会导致意外的副作用。

SCU 应仅返回 true/false,具体取决于您是否希望继续进行渲染。

如果您需要根据更新后的 props/state 计算 nextState,请使用 getderivedstatefromprops。或者 componentWillRecieveProps/componentDidUpdate 函数,如果你使用的是旧版本的 React。

【讨论】:

  • 感谢我已经删除了 SCU 但现在我的状态列出了所有旧状态,因此我第二次渲染:state1 currentState,然后是第三次,state1 state2 currentState,等等。我想不通这个bug从哪里来的
  • 有趣的是,即使我在我的 React 类之外放置一个变量 var pathname = "" 它也会这样做,所以我认为问题可能在另一个层面
猜你喜欢
  • 1970-01-01
  • 2021-08-17
  • 2020-05-17
  • 2019-08-08
  • 1970-01-01
  • 2016-01-25
  • 2021-06-08
  • 2021-07-21
  • 2017-11-05
相关资源
最近更新 更多