【问题标题】:Why can't I receive ref in componentDidMount?(React)为什么我不能在 componentDidMount 中收到 ref?(React)
【发布时间】:2020-08-24 05:05:55
【问题描述】:

我正在使用 react 16.13.1 和 react-dom 16.13.1。我使用 React.createRef() 创建了一个 ref 并附加到我自己定义的组件。然后我想使用我在该组件中定义的方法,但它不起作用,因为 .current 为 null。这是我的代码。

class SomeComponent {
  //ref
  picturesRef = React.createRef();
  richTextRef = React.createRef();

  componentDidMount() {
    console.log("this.picturesRef", this.picturesRef);
    this.setState({ operation: "update" });
    const product = this.props.products.find(
      (item) => item._id === this.props.match.params.id,
    );
    const {
      name,
      price,
      categoryId,
      imgs,
      desc,
      detail,
    } = product;
    this.setState({
      name,
      price,
      categoryId,
      imgs,
      desc,
      detail,
    });
    this.picturesRef.current.setFileList(imgs);
  }

  render() {
    const {
      categories,
      isLoading,
      name,
      price,
      categoryId,
      desc,
      detail,
    } = this.state;
    return (
      <Card title={<div>Add Product</div>} loading={isLoading}>
        <Form
          {...layout}
          onFinish={this.onFinish}
          onFinishFailed={this.onFinishFailed}
          initialValues={{
            name,
            price,
            categoryId,
            desc,
            detail,
          }}
        >
          <Item label="Product Pictures" name="imgs">
            {/**Here I attach picturesRef to this component */}
            <PicturesWall ref={this.picturesRef} />
          </Item>
          <Item {...tailLayout}>
            <Button type="primary" htmlType="submit">
              Submit
            </Button>
          </Item>
        </Form>
      </Card>
    );
  }
}

(P.S. 当我在 onFinish() 中使用 this.picturesRef.current 时,它工作正常。)

以下是PictureWall中的代码

import React, { Component } from "react";
import { Upload, Modal, message } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { BASE_URL } from "../../config";
import { reqPictureDelete } from "../../api";

function getBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = (error) => reject(error);
  });
}

class PicturesWall extends Component {
  state = {
    previewVisible: false,
    previewImage: "",
    previewTitle: "",
    fileList: [],
  };

  handleCancel = () => this.setState({ previewVisible: false });

  handlePreview = async (file) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj);
    }

    this.setState({
      previewImage: file.url || file.preview,
      previewVisible: true,
      previewTitle:
        file.name ||
        file.url.substring(file.url.lastIndexOf("/") + 1),
    });
  };

  handleChange = ({ file, fileList }) => {
    console.log("file=", file);
    const { response, status } = file;

    if (status === "done") {
      if (response.status === 0) {
        fileList[fileList.length - 1].url = response.data.url;
        fileList[fileList.length - 1].name = response.data.name;
      } else {
        message.error(response.msg, 1);
      }
    }
    if (status === "removed") {
      this.deleteImg(file.name);
    }
    this.setState({ fileList });
  };

  deleteImg = async (name) => {
    const response = await reqPictureDelete(name);
    if (response.status === 0) {
      message.success("Successfully Delete", 1);
    } else {
      message.error("Failed", 1);
    }
  };

  getImgNames() {
    let imgs = [];
    this.state.fileList.forEach((item) => {
      imgs.push(item.name);
    });
    return imgs;
  }

  setFileList = (imgNames) => {
    let fileList = [];
    imgNames.forEach((item, index) => {
      fileList.push({
        uid: index,
        name: item,
        url: `${BASE_URL}/upload/${item}`,
      });
    });
    this.setState(fileList);
  };

  render() {
    const {
      previewVisible,
      previewImage,
      fileList,
      previewTitle,
    } = this.state;
    const uploadButton = (
      <div>
        <PlusOutlined />
        <div className="ant-upload-text">Upload</div>
      </div>
    );
    return (
      <div className="clearfix">
        <Upload
          action={`${BASE_URL}/manage/img/upload`}
          method="post"
          listType="picture-card"
          fileList={fileList}
          onPreview={this.handlePreview}
          onChange={this.handleChange}
          name="image"
        >
          {fileList.length >= 4 ? null : uploadButton}
        </Upload>

        <Modal
          visible={previewVisible}
          title={previewTitle}
          footer={null}
          onCancel={this.handleCancel}
        >
          <img
            alt="example"
            style={{ width: "100%" }}
            src={previewImage}
          />
        </Modal>
      </div>
    );
  }
}

export default PicturesWall;

在 componentDidMount 的第一行,我打印出 this.picturesRef,然后发生了一些奇怪的事情: 在第一行中,它显示 current 为空,但是当我打开它时,它似乎有内容。但是,当我打印 .current 时,它仍然为空。

【问题讨论】:

  • PicturesWall 是如何使用 ref 的?
  • PictureWall 类中有一个方法,在该方法中我使用给定的参数设置状态。
  • 能贴一下PictureWall组件代码
  • 这个post 可能会有所启发。您在Card 上有诸如loading 之类的道具,这使我认为您最初可能是在DOM 上渲染某些“正在加载”类型的组件,而不是Form 的子组件,例如PicturesWall 组件。这可能就是为什么在 componentDidMount 生命周期中无法访问 PicturesWall ref
  • 出现“奇怪”是因为 Chrome 检查器打印的是活动对象,而不是快照;当你展开对象来检查它时,current 属性已经设置好了。

标签: javascript reactjs frontend


【解决方案1】:

正如我在 OP 问题的 cmets 部分中指出的那样,我注意到 Card 组件有一个 prop loading

<Card title={<div>Add Product</div>} loading={isLoading}>
    <Form>
        <Item>
            <PicturesWall ref={this.picturesRef} />
            ...

这让我相信Card 组件具有阻止其子组件在完成加载之前渲染的条件,一个示例是在加载时不渲染其子组件 - 它呈现“正在加载”组件类型。

在这种情况下,this.picturesRef.current 将在 componentDidMount 生命周期中返回 null,因为 ref 不会引用任何内容,因为那时它还没有在 DOM 中。


我的原评论:

这个post 可能会有所启发。你有诸如加载之类的道具 Card 这让我觉得也许你最初是在渲染 DOM 上的一些“正在加载”类型的组件,而不是 Card 的子元素,例如 PicturesWall 组件。这可能是为什么 在 componentDidMount 生命周期中无法访问 PicturesWall ref

【讨论】:

    【解决方案2】:

    这并不能直接回答您的问题,但我认为您的反应可能是错误的。

    您的 componentDidMount 函数似乎基本上只是从 props 派生状态(然后在 reffed 组件上调用函数)。您可以在类组件的构造函数中派生状态,例如像

    constructor(props) {
          const product = props.products.find((item)=>item._id === props.match.params.id);
          const {name,price,categoryId,imgs,desc,detail} = product;
          this.state = {name,price,categoryId,imgs,desc,detail};
    }
    

    然后,您可以类似地将fileList 作为道具传递给PictureWall,而不是setFileList 函数,例如

    <PicturesWall fileList={this.state.imgs} />
    

    【讨论】:

    • 谢谢,我了解您的解决方案是将 imgs 传递给子组件,PictureWall 可以从 this.props.fileList 中获取它。但我仍然对这个问题感到困惑。我只是使用 React doc link 中的示例
    猜你喜欢
    • 2017-04-10
    • 2016-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-08
    • 1970-01-01
    • 2019-05-08
    相关资源
    最近更新 更多