扩展反应自定义组件

时间:2017-10-31 01:52:58

标签: reactjs react-redux

我有一个名为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/

2 个答案:

答案 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,但实际上您正在扩展ConnectConnect通过撰写包裹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)

使用storeRedux中传递context,然后使用connect HOC将其连接到班级。因此,如果您希望EditBlog组件有权访问store,则需要传递context中的constructor

constructor(props,context){
    super(props,context);
  }

构造函数中对super的调用会为类创建this关键字。当您在构造函数中传递上下文然后传递给super时,上下文也用于为类创建this以及props。这样就可以访问storeEditBlog

有关详情,请访问:https://reactjs.org/docs/context.html

示例代码:https://codesandbox.io/s/1qrnmqror3

编辑11/1 :如@MarcDavies answer中所述,没有继承链来帮助您实现您的目标。相反,你应该使用组合。要使用合成,您可以重新设计组件,使得常见的部件位于一个组件中,而更改的部件位于不同的组件中。

我不了解您的组件以进行明智的设计,但根据您从更新中获得的内容,您将根据是删除照片还是博客来改变删除按钮的功能。为此,您可以创建DeletePhoto按钮和DeleteBlog按钮,以获得props的常见删除功能。公共父组件将知道它是博客还是照片,并相应地呈现相应的按钮。