React - 将函数作为props传递可能导致的意外行为

时间:2016-07-09 04:22:18

标签: javascript reactjs

我正在尝试构建一个缩略图组件列表,点击这些组件时会更新父级的状态。最终,我确实得到了所需的行为,但它需要几次点击,效果通常只需在输入后面单击一下即可。我推测这与函数如何作为道具传递有关,但是对于正在发生的事情完全不知所措。

编辑:我认为问题是在setState更改图像状态之前页面呈现,这就是为什么每次点击都会执行前一次点击传递的图像。我要么想要一个等待状态更新渲染的方法,要么在ComponentDidUpdate中做一些事情来重新渲染页面(这似乎是hackish但仍有可能)。

这是我到目前为止的代码:

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} />
        )
}
})

编辑:包括下面的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>
    );
}
})

3 个答案:

答案 0 :(得分:0)

首先,我会放弃用下划线为函数名添加前缀的习惯。它没有任何意义:它实际上并没有使它们成为私有功能。基本上,这是代码混乱。这可能有点主观,但我认为无论如何我都会提供。 :)

其次,在ImageThumbnail中,当你真正需要的是this时,我不会将imageLink传递给父母。所以就过去吧。

尝试以下代码。我还将道具重命名为onClick,以便更清楚地发生了什么。制作具有事件处理程序的组件时,请尝试使用常规名称(onClickonChangeonDeleteThumbnail)。它会让你的生活更轻松!

enter image description here

旁注:如果您有时间,请尝试加入ES2015 class way of doing things

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} />
    )
  }
});

答案 1 :(得分:0)

我仍然没有摆脱下划线,但它在待办事项清单上。我觉得这个组件可能会变得有点臃肿,但它确保在状态更改后绘制新的canvas元素。以前,状态更改将排队并且将呈现新组件(以及曾经在MyCanvasComponent中的绘制函数),并且状态将在渲染后更改,因此一切都落后于一次。

再次感谢您的帮助!

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} />
        )
}
})

答案 2 :(得分:0)

问题在于你的MyCanvasComponent。您使用componentWillReceivePropsthis.props生命周期方法中设置了新状态,但this.props引用旧道具,the new props are passed as a parameter to the componentWillReceiveProps function

顺便说一下,您不需要在MyCanvasComponent中保留当前图像,因为此状态已由ImageSelector组件管理,从ImageSelector向下传递当前图像在这种情况下,MyCanvasComponent就足够了:

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>