javascript - 我的 ReactJS 组件怎么可能在旧状态和新状态之间交替?

标签 javascript reactjs state

我在 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> 
      )


  }
}

任何提示都会很棒, 谢谢

最佳答案

您正在为 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

关于javascript - 我的 ReactJS 组件怎么可能在旧状态和新状态之间交替?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54773997/

相关文章:

javascript - 通用 HTML 表单验证,首选 jQuery

javascript - 状态内部定义的数据和构造函数内部创建的外部状态的数据有什么区别?

javascript - 如何从 saga 获取 action.params

c# - 对跟踪状态的类进行单元测试

javascript - 如何在 mustache 中呈现动态生成的 key

javascript - 在升级事件中异步更新 IndexedDB

javascript - react |网页包 |在 Azure 上获取 process.env.NODE_ENV== 未定义

javascript - 无法检查所有 3 个条件并在 JS 中返回它们

javascript - updateProfile 不是函数 (Firebase)

javascript - 如何在 React/JSX 中指定一个空的 alt 属性?