react hook 实现父调子

这篇博客探讨了在React中如何实现组件间的通信,特别是函数组件通过`useImperativeHandle`和`forwardRef`进行通信。文章提供了一个案例,详细展示了如何在一个上传图片的场景中,通过父组件调用子组件的方法来验证项目名称是否为空,如果为空则阻止图片上传。此外,还介绍了如何处理上传前的检查、文件类型的限制以及批量上传的逻辑。
摘要由CSDN通过智能技术生成

拿项目进行案例讲解,可以更好的理解应用场景

思想:不管是react、vue还是ag都提倡组件化开发,组件开发不避免不了组件间的通信。

需求:在点击上传图片检查项目名称是否有值,没有值提示请输入项目名称,阻止upload的默认行为,有值可以选中图片上传,此页面涉及form、upload、table、button等标签,upload抽离出单独成组件开发

 

 

 这里用的是hook ,使用函数组件不能香我们在class类组件中通过ref直接调用,函数组件没有实例对象,这时是不是感觉vue 调用组件通信很简单,那怕时vue3.0也很简单

useImperativeHandle 的使用

正常情况下 ref 是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle 为我们提供了一个类似实例的东西。它帮助我们通过 useImperativeHandle 的第 2 个参数,所返回的对象的内容挂载到 父组件的 ref.current 上。

forwardRef会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。

子组件   

// 父调子重要api    useImperativeHandle, forwardRef, ——————此为抽离代码

import React, { useImperativeHandle , forwardRef }  from 'react';

const UploadDom = (props: IProps, ref) => {

useImperativeHandle(ref, () => ({
    // 暴露给父组件的方法
    refreshInfo: (val) => {
      setFormVal(val);
    },
  }));

}

let Child = forwardRef(UploadDom);

export default Child;

//完整页面代码
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload, Row, Col, message, Form, Input } from 'antd';
import type { RcFile, UploadProps } from 'antd/es/upload';
import type { UploadFile } from 'antd/es/upload/interface';
import React, {
  useCallback,
  useState,
  useImperativeHandle,
  forwardRef,
} from 'react';
import './upload.less';
import up from '@/assets/images/up.png';
import _ from 'lodash';
import { AnalyzationUploadFile } from '@/app/request/requestApi';
import { t } from '@/uitls/public';
import { useEffect } from '@storybook/addons';
const getBase64 = (file: RcFile): Promise<string> =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result as string);
    reader.onerror = (error) => reject(error);
  });
// 父传子数据类型
interface IProps {
  showUploadDom?: any; // 方法
  onhandleBeforeUpload?: any;
}
const UploadDom = (props: IProps, ref) => {
  const arr = [];
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewImage, setPreviewImage] = useState('');
  const [previewTitle, setPreviewTitle] = useState('');
  const [url, setUrl] = useState(
    'https://www.mocky.io/v2/5cc8019d300000980a055e76',
  );
  const [fileList, setFileList] = useState<any>(arr);
  const [didInit, setDidInit] = useState<boolean>(false);
  const [form] = Form.useForm();
  const [formVal, setFormVal] = useState<any>(sessionStorage.getItem('pjName'));

  const handleCancel = () => setPreviewOpen(false);
  const handlePreview = async (file: UploadFile) => {
    if (!file.url && !file.preview) {
      file.preview = await getBase64(file.originFileObj as RcFile);
    }

    setPreviewImage(file.url || (file.preview as string));
    setPreviewOpen(true);
    setPreviewTitle(
      file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1),
    );
  };
  useImperativeHandle(ref, () => ({
    // 暴露给父组件的方法
    refreshInfo: (val) => {
      setFormVal(val);
    },
  }));
  let listFileData = [];
  let arrUpload = [];
  let imgType = t('You-can-only-upload-JPG/PNG-file');
  const onBatchBeforeUpload = (file) => {
    const isJpgOrPng =
      file.type === 'image/jpeg' ||
      file.type === 'image/png' ||
      file.type === 'image/jpg';
    if (!isJpgOrPng) {
      message.error(imgType);
      return false;
    }
    listFileData.push(file.name);
    arrUpload.push(file);
    if (arrUpload) {
      let hash = {};
      var newArr = arrUpload.reduce((item, next) => {
        hash[next.name] ? '' : (hash[next.name] = true && item.push(next));
        return item;
      }, []);
      arrUpload = newArr;
    }
  };
  // 一个组别的图片不能少于6张
  let theNumber = t('the-number-of-pictures-in-a-group-cannot-be-less-than-6');
  const handleChange = () => {
    if (didInit) return false; //执行一次
    setDidInit(true);
    if (listFileData.length < 6) {
      message.error(theNumber);
      setDidInit(false);
    } else {
      onBatchUpload(arrUpload, listFileData, formVal);
    }
  };
  // 防抖  反复触发执行最后一次 //使用useCallback防止debounce失效
  const debounce = _.debounce;
  const getSuggestion = useCallback(
    debounce((val: string, type: boolean) => {
      type ? message.success(val) : message.error(val);
    }, 300),
    [],
  );
  let uploadSucceeded = t('upload-succeeded');

  const onBatchUpload = useCallback(
    debounce((val, listData, form_Val) => {
      let formData = new FormData();
      val.map((item) => {
        formData.append('FileInfos', item);
      });
      formData.append('projectName', form_Val);
      console.log(form_Val);
      AnalyzationUploadFile(formData)
        .then((res) => {
          if (res.success) {
            setDidInit(false);
            message.success(uploadSucceeded);
            sessionStorage.setItem('pjName', form_Val);
            // getSuggestion('上传成功', true);
            props.showUploadDom(res?.data);
          } else {
            setDidInit(false);
            message.error(res.msg);
          }
        })
        .catch((err) => {
          message.error(err.msg);
          setDidInit(false);
        });
    }, 300),
    [],
  );
  const onChangeText = (e) => {
    setFormVal(e.target.value);
  };

  // 阻止事件冒泡到父元素 这个方法不能写在Upload标签中,必须在 uploadButton 上,此处可以理解成botton
  function handleBeforeUpload(e) {
    props.onhandleBeforeUpload(e); //调用父方法,判断项目名称是否有值
  }
  const uploadButton = (
    <div className="img_box" onClick={handleBeforeUpload}>
      <div className="text_img">{t('upload-pictures')}</div>
      <img className="img_up" src={up} alt="" />
    </div>
  );

  return (
    <div>
      <div className="box_img">
        <Upload
          multiple
          action={url}
          listType="picture-card"
          fileList={fileList}
          onPreview={handlePreview}
          beforeUpload={async (file) => onBatchBeforeUpload(file)}
          customRequest={handleChange}
        >
          {uploadButton}
        </Upload>
        <Modal
          visible={previewOpen}
          title={previewTitle}
          footer={null}
          onCancel={handleCancel}
        >
          <img alt="example" style={{ width: '100%' }} src={previewImage} />
        </Modal>
      </div>
    </div>
  );
};
let Child = forwardRef(UploadDom);
export default Child;

子组件css 

.box_img {
  background: #ffffff;
  border: 1px dotted #aeaeae;
  height: 24vh;
  margin: 20px;
  border-radius: 10px;
}
.ant-upload.ant-upload-select {
  display: block !important;
}

.ant-upload.ant-upload-select-picture-card {
  margin-right: 8px;
  margin-bottom: 8px;
  text-align: center;
  vertical-align: top;
  background-color: #fff !important;
  border: 0px dashed #d9d9d9 !important;
  border-radius: 2px;
  cursor: pointer;
  transition: border-color 0.3s;
  margin: 0 auto !important;
}
.img_up {
  width: 170px;
  height: 126px;
}
@media (max-width: 1919px) and (min-width: 768px) {
  .img_up {
    width: 111px !important;
    height: 75px !important;
  }
  .text_img {
    font-size: 15px !important;
  }
}
.img_box {
  margin-top: 30px !important;
  // width: 100%;
  height: 100%;
  margin: 0 auto;
}
// .img_up {
//   width: 100%;
//   height: 100%;
// }
// .ant-upload-list {
//   margin: 0 auto !important;
// }
.ant-upload-list-picture-card-container {
  display: none !important;
}
.text_img {
  margin-top: 15px;
  margin-bottom: 5px;
  text-align: center;
  font-size: 18px;
  font-family: Microsoft YaHei;
  font-weight: bold;
  color: #333333;
  width: 200px;
}

父(只写方法)

// HTML 

import UploadDom from '../Upload/index';
export default (props: any) => {
  <div>
    <UploadDom
    ref={modalMethodRef}
    showUploadDom={showUploadDom}
    onhandleBeforeUpload={onhandleBeforeUpload}
  />
}
import { useRef } from 'react';
// ts
// 判断项目名称是否有值
export default (props: any) => {
const modalMethodRef = useRef<HTMLCanvasElement | null | any>();
  const onhandleBeforeUpload = (e) => {
    let enterNameVal = formVal;
    if (!enterNameVal) {
      message.error(enterNameValT);
      e.stopPropagation(); // 阻止事件冒泡到父元素
    } else {
      modalMethodRef.current.refreshInfo(formVal); //父调子
    }
  };
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jim-zf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值