推送新对象时状态值未定义

时间:2019-12-13 19:46:18

标签: javascript reactjs

我正在尝试将对象{link:href}推入项目中的状态。当我尝试在{link:href}中添加href时,它说未定义:

image

这是我的代码:

class Download extends React.Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);

    this.inputRef = React.createRef();

    this.state = {
      files: []
    };
  }

  handleClick = () => {
    const node = this.inputRef.current;

    const self = this;

    let file;
    let name;
    let href;

    node.addEventListener("change", function() {
      const fileList = [];

      for (let x = 0, xlen = this.files.length; x < xlen; x++) {
        file = this.files[x];
        name = file.name;

        fileList.push({ name: name });
        let reader = new FileReader();
        reader.onload = e => {
          href = e.target.result;
        };

        fileList.push({ link: href });
        reader.readAsDataURL(file);
      }

      self.setState({ files: fileList });
      console.log(self.state);
    });
  };

  render() {
    return (
      <div className="input">
        <input
          onClick={this.handleClick}
          id="upload-file"
          className="inputName"
          type="file"
          multiple
          ref={this.inputRef}
        />
        <div>
          <ul ref={this.ulRef}>
            {this.state.files.map((file, index) => (
              <li key={index}>
                <Link to={file.link}>{file.name}</Link>
              </li>
            ))}
          </ul>
        </div>
      </div>
    );
  }
}

export default Download;

我尝试在reader.onload函数之后推送{link:href}的原因是,因为for循环运行了x次之后,reader.onload函数才被加载,并且仅显示最后一项。

1 个答案:

答案 0 :(得分:0)

第一件事,您不需要使用任何refs。而且,您不需要向node添加事件侦听器(根本不需要node),因为可以使用react的onChange而不是onClick。您也不需要为此使用Link。您可以仅使用带有href的锚标记,可以将文件数据分配给该标记。我更喜欢使用处理下载的功能,因为我觉得它可以提供更多控制权,并且不会影响浏览器的窗口或DOM元素。本质上,您可以创建一个锚标记,触发下载然后将其删除。

您还错误地将元素推入。通过推入{name: filename}然后推入{link: href},您刚刚将两个对象推入了数组,并且这两个对象都将被映射。而是创建一个对象,然后将这两个键值对都添加到该对象中并将该单个对象推入。在到达reader.onload之前,无需将任何东西推入数组。通过将某物推入reader.onload之前,然后再将其推入reader.onload中,您只是将某物推入两次。

name添加到对象。删除第一个推送,然后在reader.onload中将link值添加到同一对象,然后将该对象推送到数组中。您也不应在reader.onload之外设置状态,因为这会触发重新渲染并渲染不完整的对象,从而导致错误。仅在reader.onload中设置一次状态,以便在触发重新渲染时它将在列表中具有完整的对象。

如果您希望文件持久存在(意味着允许将多个文件添加到文件列表中),则不必将fileList每次触发时都将onChange设置为空数组,将其设置为{ {1}}。我们可以使用this.state.files.slice()来创建一个新数组,而不能直接更改slice

Here is a working code sandbox

如您所见,我也删除了您的this.state.files,因为仅使用constructor就足够了。如果您喜欢使用state = {...},可以根据需要将其添加回去,我只是为了节省几行代码而已。

constructor
class Download extends React.Component {

  state = {
    files: []
  };

  handleClick = event => {
    let file, name, href;

    var breh = event.target;

    const fileList = this.state.files.slice();

    for (let x = 0, xlen = breh.files.length; x < xlen; x++) {
      file = breh.files[x];
      name = file.name;

      var obj = {};
      obj["name"] = name;

      let reader = new FileReader();
      reader.onload = e => {
        href = e.target.result;
        obj["link"] = href;
        fileList.push(obj);
        this.setState({ files: fileList });
      };

      reader.readAsDataURL(file);
    }
  };

  download = (uri, name) => {
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  render() {
    console.log(this.state);
    return (
      <div className="input">
        <input
          onChange={this.handleClick}
          id="upload-file"
          className="inputName"
          type="file"
          multiple
        />
        <div>
          <ul>
            {this.state.files.map((file, index) => {
              return (
                <li
                  key={index}
                  onClick={() => {
                    this.download(file.link, file.name);
                  }}
                >
                  {file.name}
                </li>
              );
            })}
          </ul>
        </div>
        <output id="list" />
      </div>
    );
  }
}

export default Download;