我有一个名为editPhoto.js的react组件,它有一个onDeleteFile方法,我想在扩展它时覆盖它。问题是editPhoto.js连接到redux存储。继承是错误的方法吗?或者我将如何扩展和覆盖EditPhoto onDeleteFile并保存方法?
更新: 11-1
editPhoto.js 执行editBlog所需的所有功能。因此,在OOP术语中,通过使用editBlog扩展editPhoto,这是继承的经典案例,因此editBlog继承了editPhoto的所有功能和属性。通过继承,我还可以覆盖editBlog使用与editPhoto不同的两种方法。 保存和 onDeleteFile 功能。
以下是这两种功能的使用方式不同。
save()方法应该调用不同的操作:
save(){
all the same (save) method code in editPhoto goes here....
// this.props.editPhoto(formData, this.props.history) should be changed to
this.props.editBlog(formData, this.props.history).
}
onDeleteFile()方法应该调用不同的操作:
onDeleteFile(){
all the same (onDeleteFile) method code in editPhoto goes here....
// this.props.editBlog(this.state.id, this.props.history) should be changed to
this.props.deletePhoto(this.state.id, this.props.history)
}
这是代码中唯一的两个变化,似乎很难复制112行代码,只需更改2行。 我还想知道如何使用合成覆盖一个方法,但在这种情况下它不是必需的(也许最适合另一个帖子)。
如果有一种方法可以使用合成,我完全可以不使用继承,因为在EditPhoto中使用Redux连接时它不会继承。
END OF UPDATE 11-1 -------------------------------
请参阅下面的所有代码,尝试在继承中执行相同的操作。
import React, {Component} from 'react';
import EditPhoto from './editPhoto';
export default class EditBlog extends EditPhoto{
constructor(props){
super(props);
}
// override original EditPhoto
onDeleteFile(event){
event.preventDefault();
console.log("DeleteBlog : onDeleteFile");
this.props.deleteBlog(this.state.id, this.props.history)
}
save( event ){
event.preventDefault();
const formData .....
// call different action for editBlog(formData, this.props.history);
}
}
这是我的editPhoto.js代码
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Dropzone from 'react-dropzone';
import FieldEditor from '../../components/admin/fieldEditor';
import DropzoneArea from '../../components/admin/dropzoneArea';
import {editPhoto, deletePhoto} from '../../actions/index';
import style from '../../../styles/admin/editPhoto.scss';
class EditPhoto extends Component{
constructor(props){
super(props);
this.onUpdateState = this.onUpdateState.bind(this);
this.handleDrop = this.handleDrop.bind(this);
this.save = this.save.bind(this);
this.onDeleteFile = this.onDeleteFile.bind(this);
this.state = {
fieldEdits:'',
preview:'',
file:'',
id:''
}
}
componentWillMount(){
if(this.props.location.state)
this.setState({
preview: this.props.location.state.photo.photo,
fieldEdits: this.props.location.state.photo,
id: this.props.location.state.photo._id
})
}
onUpdateState(fieldEdits){
this.setState({fieldEdits});
}
handleDrop(acceptedFiles, rejectedFiles){
acceptedFiles.forEach( (file, index) => {
const reader = new FileReader();
reader.onload = ( event ) => {
const img = event.target.result;
this.setState({preview:img, file});
};
reader.onabort = () => console.log('file reading was aborted');
reader.onerror = () => console.log('file reading has failed');
reader.readAsDataURL(file);
});
}
onDeleteFile(event){
event.preventDefault();
this.props.deletePhoto(this.state.id, this.props.history);
}
save( event ){
event.preventDefault();
var form = document.forms.namedItem("photoEditUpload");
var formData = new FormData(form);
if(this.state.file.name){
formData.append('photos', this.state.file, this.state.file.name);
}
formData.append('id', this.state.id);
// for (var pair of formData.entries()) {
// console.log(pair[0]+ ' = ' + pair[1]);
// }
this.props.editPhoto(formData, this.props.history);
}
render(){
if(!this.props.location.state){
// TO DO : ADD IN A ERROR COMPONENT FOR THIS.
return <div>No photo to edit. GO BACK !</div>
}
return(
<div id="edit-photo" className='container'>
<form onSubmit={this.save} ref='form' role="save" encType="multipart/form-data" name="photoEditUpload" method="post">
<div className="row">
<div className="photo-container col-sm-8">
<img src={this.state.preview} className="edit-photo"></img>
</div>
<div className="col-sm-4">
<div>
<Dropzone onDrop={this.handleDrop} multiple={false} accept="image/jpeg" className="dropzone">
<DropzoneArea />
</Dropzone>
</div>
<div className="delete-container">
<button onClick={this.onDeleteFile} className='btn btn-primary' type='button'>Delete</button>
</div>
</div>
</div>
<FieldEditor callback={this.onUpdateState} {...this.state.fieldEdits} />
<div className="button-container">
<button className='btn btn-primary btn-save' type='submit'>Save</button>
</div>
</form>
</div>
)
}
}
export default connect(null, {editPhoto, deletePhoto})(EditPhoto);
注意: 这是jsfiddle中的精简版本,在palsrealm建议传递上下文后显示问题,因此商店不会被定义。 https://jsfiddle.net/chapster11/jzprkx96/31/
答案 0 :(得分:5)
您的问题是您正在扩展由
创建的组件export default connect(null, {editPhoto, deletePhoto})(EditPhoto);
如果您查看react-redux
文档,可以看到connect
返回Connect
组件which extends React.Component。
宣布
时export default class EditBlog extends EditPhoto {
您可能看起来正在扩展class EditPhoto
,但实际上您正在扩展Connect
。 Connect
通过撰写包裹EditPhoto
,并且不知道EditPhoto
的方法和属性是什么。没有继承链来帮助你。 Connect
也会生气并抛出错误,因为它不会像这样被使用。
导入import EditPhoto from './editPhoto';
的命名具有误导性。
如果要扩展类EditPhoto
,则需要导出类本身
export class EditPhoto extends Component {
并将其导入EditBlog
模块
import {EditPhoto} from './editPhoto';
export default class EditBlog extends EditPhoto {
但我不建议这样做。
继承是React中的反模式。查看the React docs on Composition vs Inheritance
在Facebook,我们在数千个组件中使用React,我们还没有找到任何建议创建组件继承层次结构的用例。
所以...我会退后一步,尝试用作文再次解决问题。
更新 - 撰写示例
根据您在更新中提供的额外详细信息,我可能会从通用的EditAsset
组件开始,类似于......
class EditAsset extends React.Component {
static propTypes = {
deleteAsset: PropTypes.func.isRequired,
updateAsset: PropTypes.func.isRequired,
...any other asset-specific props
}
handleDelete = () => {
...do generic stuff...
this.props.deleteAsset(id, whatever, whatever)
}
handleSave = () => {
...do generic stuff...
this.props.updateAsset(id, whatever, whatever)
}
render = () => {
...all your generic render stuff...
}
}
然后,您可以使用redux connect
组合不同的组件来处理特定的资产类型。 e.g。
import {EditAsset} from './path/to/EditAsset';
import {deletePhoto, updatePhoto, deleteBlog, editBlog} from './path/to/actions';
export const EditPhoto = connect(
undefined,
dispatch => ({
deleteAsset: (...args) => dispatch(deletePhoto(...args)),
updateAsset: (...args) => dispatch(updatePhoto(...args)),
})
)(EditAsset);
export const EditBlog = connect(
undefined,
dispatch => ({
deleteAsset: (...args) => dispatch(deleteBlog(...args)),
updateAsset: (...args) => dispatch(updateBlog(...args)),
})
)(EditAsset);
因此,您编写一次通用组件并使用组合创建一个,两个,一千个特定组件,并将适当的数据和方法作为道具传递下来。
在这个示例中,我们只传递了两个操作调度程序,但您显然可以使用mapStateToProps
传递您需要的任何特定数据。
这有意义吗?
答案 1 :(得分:1)
使用store
在Redux
中传递context
,然后使用connect
HOC将其连接到班级。因此,如果您希望EditBlog
组件有权访问store
,则需要传递context
中的constructor
。
constructor(props,context){
super(props,context);
}
构造函数中对super
的调用会为类创建this
关键字。当您在构造函数中传递上下文然后传递给super时,上下文也用于为类创建this
以及props。这样就可以访问store
类EditBlog
。
有关详情,请访问:https://reactjs.org/docs/context.html
示例代码:https://codesandbox.io/s/1qrnmqror3
编辑11/1 :如@MarcDavies answer中所述,没有继承链来帮助您实现您的目标。相反,你应该使用组合。要使用合成,您可以重新设计组件,使得常见的部件位于一个组件中,而更改的部件位于不同的组件中。
我不了解您的组件以进行明智的设计,但根据您从更新中获得的内容,您将根据是删除照片还是博客来改变删除按钮的功能。为此,您可以创建DeletePhoto
按钮和DeleteBlog
按钮,以获得props
的常见删除功能。公共父组件将知道它是博客还是照片,并相应地呈现相应的按钮。