简体   繁体   English

React-可能由于将函数作为prop传递而导致意外行为

[英]React - Unexpected behavior possibly caused by passing functions as props

I'm trying to build a list of thumbnail image components which when clicked will update the state of the parent. 我正在尝试建立缩略图组件的列表,单击这些缩略图将更新父级的状态。 Eventually, I do get the desired behavior, but it takes several clicks and the effect is usually one-click behind the input. 最终,我确实获得了期望的行为,但是它需要单击几下,并且效果通常是在输入后面单击一下。 I'm speculating that this has something to do with how the functions are being passed as props, but am completely at a loss for what's going on. 我推测这与如何将函数作为prop传递有关,但是对于正在发生的事情完全不知所措。

EDIT: I think the issue is that the page renders before the setState changes the image state, which is why each click executes with the image that was passed via the previous click. 编辑:我认为问题在于页面在setState更改图像状态之前呈现,这就是为什么每次单击都使用通过前一次单击传递的图像执行的原因。 I either need to fin a way to wait until the state updates to render, or do something in ComponentDidUpdate to rerender the page (which seems hackish but still a possibility). 我要么需要找到一种方法来等待状态更新以呈现,要么在ComponentDidUpdate中进行某些操作以重新呈现页面(这似乎有点黑,但仍然是可能的)。

This is the code I have so far: 这是我到目前为止的代码:

var ImageSelector = React.createClass({

getInitialState: function(){
    return{
        imgState:  "<%= image_path('image1.jpg') %>"
    }
},

_changePicState: function(thumbnail){
    var newImage = thumbnail.props.imageLink
    this.setState({imgState: newImage})
},

_getThumbnails: function(){
    console.log('_getThumbnails')
    const thumbnailList = [
    {id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
    {id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
    {id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
    ]

    return thumbnailList.map((e) => {
        return (
            <ImageThumbnail key={e.id} imageLink={e.imageLink} propFunc={this._changePicState}/>
            )
    });
},

render: function() {
const thumbnails = this._getThumbnails()

return (
    <div>
    {thumbnails}
    <MyCanvasComponent ref="canvasComp" imageLink={this.state.imgState}/>
    </div>

)
}

});

var ImageThumbnail = React.createClass({
_runPropFunc: function(){
    this.props.propFunc(this)
},

render: function(){
    return (
        <img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
        )
}
})

EDIT: Including the myCanvasComponent code below. 编辑:包括下面的myCanvasComponent代码。

var MyCanvasComponent = React.createClass({

getInitialState: function(){
    return {
        currentImage: this.props.imageLink
    }
},
componentDidUpdate: function(){
    this._draw()
},

_draw: function(){
    var draw = function(){
        ctx.drawImage(objectImg, 100, 100);
    }
    var can = this.refs.canvas;
    var ctx = can.getContext('2d');

    var objectImg = new Image();
    var imgPath = this.state.currentImage;
    objectImg.src = imgPath
    console.log('drawing ' + imgPath)

    objectImg.onload = function(){
        draw();
    }
},


componentWillReceiveProps: function(){
    this.setState({currentImage: this.props.imageLink});
},

componentDidMount: function(){
    console.log('canvas rendered')
    this._draw()

},

render: function() {
    return (
        <div>
        <canvas ref='canvas' width={867} height={600}/>
        </div>
    );
}
})

First off, I would drop the habit of prefixing your function names with underscores. 首先,我要习惯于在函数名前加下划线。 It serves no purpose: it doesn't actually make them private functions. 它没有任何目的:实际上并没有使它们成为私有功能。 It's code mess, basically. 基本上,这是代码混乱。 That's probably a bit subjective, but I thought I'd offer that up anyway. 这可能有点主观,但我认为无论如何我都会提出。 :) :)

Secondly, in ImageThumbnail , I wouldn't pass this back to the parent, when all you really need is the imageLink . 其次,在ImageThumbnail ,我不会通过this回父,当你真正需要的是imageLink So just pass that. 因此,只需通过。

Try the code below. 试试下面的代码。 I also renamed the prop to onClick so it's clearer what's going on. 我还将该道具重命名为onClick以便更清楚地了解发生了什么。 When making your own components that have event handlers, try to stick to conventional names ( onClick , onChange , onDeleteThumbnail ). 在制作自己的具有事件处理程序的组件时,请尝试使用常规名称( onClickonChangeonDeleteThumbnail )。 It'll make your life easier! 它将使您的生活更轻松!

在此处输入图片说明

Side note: If you have time, try to get on board with the ES2015 class way of doing things . 旁注:如果您有时间,请尝试使用ES2015类的处理方式

var ImageSelector = React.createClass({
  getInitialState: function () {
    return {
      imgState: "<%= image_path('image1.jpg') %>"
    }
  },

  changePicState: function (imageLink) {
    this.setState({ imgState: imageLink });
  },

  getThumbnails: function () {
    console.log('getThumbnails');
    const thumbnailList = [
      { id: 1, imageLink: "<%= image_path('image1.jpg') %>" },
      { id: 2, imageLink: "<%= image_path('image3.jpg') %>" },
      { id: 3, imageLink: "<%= image_path('image7.jpg') %>" },
    ];

    return thumbnailList.map((e) => {
      return (
        <ImageThumbnail key={e.id} imageLink={e.imageLink}
                        onClick={this.changePicState} />
      )
    });
  },

  render: function () {
    return (
      <div>
        <div>{this.getThumbnails()}</div>
        <div>{this.state.imgState}</div>
      </div>
    )
  }
});

var ImageThumbnail = React.createClass({
  runPropFunc: function () {
    this.props.onClick(this.props.imageLink);
  },

  render: function () {
    return (
      <img key={this.props.id} src={this.props.imageLink} className="thumbnail"
           onClick={this.runPropFunc} />
    )
  }
});

I still didn't get rid of the underscores, but it's on the to-do list. 我仍然没有消除下划线,但是它在待办事项清单上。 I feel like this component might be getting a bit bloated, but it ensures that the new canvas element gets drawn after the state change. 我觉得这个组件可能有点肿,但是它可以确保状态更改后绘制新的canvas元素。 Previously, the state change would be queued and the new component would be rendered (along with the draw function which used to be in the MyCanvasComponent) and the state would change after rendering, so everything lagged behind by one click. 以前,状态更改将排入队列,并且将渲染新组件(以及以前在MyCanvasComponent中使用的draw函数),并且渲染后状态将发生变化,因此所有操作都一键落后。

Thank you once again for your help! 再次感谢您的帮助!

var ImageSelector = React.createClass({

getInitialState: function(){
    return{
        imgState:  "<%= image_path('image1.jpg') %>"
    }
},

_draw: function(){
    var draw = function(){
        ctx.drawImage(objectImg, 100, 100);
    }
    var can = this.refs.canvas;
    var ctx = can.getContext('2d');

    var objectImg = new Image();
    var imgPath = this.state.imgState;
    objectImg.src = imgPath

    objectImg.onload = function(){
        draw();
    }
},

componentDidUpdate: function(){
    this._draw()
},
componentDidMount: function(){
    this._draw()
},

_changePicState: function(imageLink){
    this.setState({imgState: imageLink})
},

_getThumbnails: function(){
    const thumbnailList = [
    {id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
    {id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
    {id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
    ]

    return thumbnailList.map((e) => {
        return (
            <ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
            )
    });
},

render: function() {
const thumbnails = this._getThumbnails()

return (
    <div>
    {thumbnails}
    <canvas ref='canvas' width={867} height={600}/>
    </div>

)
}

});

var ImageThumbnail = React.createClass({

_runPropFunc: function(){
    this.props.onClick(this.props.imageLink)

},

render: function(){
    return (
        <img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
        )
}
})

The problem lies in your MyCanvasComponent . 问题出在您的MyCanvasComponent You're setting the new state in the componentWillReceiveProps lifecycle method by using this.props but this.props references the old props, the new props are passed as a parameter to the componentWillReceiveProps function 您正在使用this.propscomponentWillReceiveProps生命周期方法中设置新状态,但是this.props引用了旧的this.props新的 this.props 作为参数传递给componentWillReceiveProps函数。

By the way, you don't need to hold the current image in the MyCanvasComponent since this state is already managed by the ImageSelector component, passing down the current image from ImageSelector to MyCanvasComponent is sufficient in this case : 顺便说一句,您不需要将当前图像保存在MyCanvasComponent因为此状态已经由ImageSelector组件管理,在这种情况下,将当前图像从ImageSelectorMyCanvasComponent就足够了:

 var ImageSelector = React.createClass({ getInitialState: function(){ return{ imgState: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg" } }, _changePicState: function(imageLink){ this.setState({imgState: imageLink}) }, _getThumbnails: function(){ const thumbnailList = [ {id: 1, imageLink: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"}, {id: 2, imageLink: "https://s3.amazonaws.com/media-p.slid.es/uploads/jhabdas/images/969312/react-logo-1000-transparent.png"}, {id: 3, imageLink: "http://felknar.com/images/icon-react-7b609cd3.svg"}, ] return thumbnailList.map((e) => { return ( <ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/> ) }); }, render: function() { const thumbnails = this._getThumbnails() return ( <div> {thumbnails} <MyCanvasComponent imageLink={this.state.imgState}/> </div> ) } }); var ImageThumbnail = React.createClass({ _runPropFunc: function(){ this.props.onClick(this.props.imageLink) }, render: function(){ return ( <img key={this.props.id} width={50} height={50} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} /> ) } }) var MyCanvasComponent = React.createClass({ _draw: function(){ var draw = function(){ ctx.drawImage(objectImg, 100, 100); } var can = this.refs.canvas; var ctx = can.getContext('2d'); var objectImg = new Image(); var imgPath = this.props.imageLink; objectImg.src = imgPath objectImg.onload = function(){ draw(); } }, componentDidUpdate: function(){ this._draw() }, componentDidMount: function(){ this._draw() }, render: function() { return ( <canvas ref='canvas' width={867} height={600}/> ) } }) ReactDOM.render(<ImageSelector/>, document.getElementById('app')) 
 <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> <div id="app"></div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM