Skip to content

9.1 笔记

知识点整理

forwardRef的使用

使用场景

INFO

当你需要将子组件的DOM节点暴露给父组件使用的时候

代码

jsx
import { useRef, useEffect, forwardRef } from "react";

export function Parent() {
  const elementRef = useRef();

  useEffect(() => {
    // Works!
    console.log(elementRef.current); // logs HTMLDivElement <div>Hello, World!</div>
  }, []);

  return <Child ref={elementRef} />; // assign the ref
}

const Child = forwardRef(function (props, ref) {
  return <div ref={ref}>Hello, World!</div>;
});

forwardRef(props,ref)

  • props:就是父组件传给子组件的参数(不包括ref)
  • ref:就是父组件传递给子组件的ref(这个ref会放置到父组件想要访问子组件的DOM的元素身上)

useImperativeHandle使用

使用场景

INFO

一般这个Hooks都是和useforwardRef一起使用的,作用就是,当你使用forwardRef的时候暴露的其实是子组件的整个dom。这个方法就是,你只暴露其中的某几个方法,让父组件使用。

代码

jsx
import { useRef, useEffect, forwardRef, useImperativeHandle } from "react";

export function Parent() {
  const elementRef = useRef();

  useEffect(() => {
    // Works!
    console.log(elementRef.current); // logs undefined
  }, []);

  return <Child ref={elementRef} params={11} />; // assign the ref
}

const Child = forwardRef(function (props, ref) {
  // 使用useImperativeHandle暴露其中一部分
  useImperativeHandle(ref,()=>{

  })
  return <div ref={ref}>Hello, World!</div>;
});

就像上面那样,我们其实并没有暴露,所以父组件里面接收到的的就是undefined

jsx
import { useRef, forwardRef, useImperativeHandle } from "react";

export function Main() {
  const methodsRef = useRef();

  const focus = () => methodsRef.current.focus();
  const blur = () => methodsRef.current.blur();

  return (
    <>
      <FocusableInput ref={methodsRef} />
      <button onClick={focus}>Focus input</button>
      <button onClick={blur}>Blur input</button>
    </>
  );
}

const FocusableInput = forwardRef(function (props, ref) {
  const inputRef = useRef();

  useImperativeHandle(
    ref,
    function () {
      // 暴露出ref身上的focus和blur方法
      return {
        focus() {
          inputRef.current.focus();
        },
        blur() {
          inputRef.current.blur();
        }
      };
    },
    []
  );

  return <input type="text" ref={inputRef} />;
});

代码解读:

  1. 首先父组件创建一个ref并将它传递给子组件
  2. 子组件使用forwardRef包裹,这样的父组件就可以使用子组件的ref
  3. 子组件将refuseImperativeHandle()包裹起来,这样就可以限制父组件可以访问的DOM元素的范围
  4. 然后父组件直接去访问就可以了

useImperativehandle(ref,createhandle,dependencies)

  • ref:这个就是父组件传给子组件的那个ref,就是forwardRef(props,ref)的第二个参数
  • createhandle:这是一个函数,返回的返回值是一个对象,对象里面是你所有想要暴露出去的函数
  • dependenciescreatehandle代码中所用到的反应式的值的列表

INFO

但是需要注意的是,不要滥用ref,如果这个操作可以通过props来实现,那么就少用ref

参考资料:

forwardRef – React 中文文档

useImperativeHandle – React 中文文档

React forwardRef(): How to Pass Refs to Child Components

项目整理

场景 select双栏展示

现在对于表单的item,希望他是一个select搜索框,并且它的下拉列表需要多列展示,就像下图展示的这样。

下拉表格

思路

主要思路就是,我们希望下拉框里面展示的是一个二维的table,那么我们只要借助antd selectdropdownRender这个API就可以实现了。

INFO

dropdownRender的作用:就是定义下拉框的内容。我们将table组件放在这个方法里面,让他渲染出来即可。

假如你需要实现,选中一行,对选中的行进行某些操作的时候,这时候就不能使用**onSelect**了,因为你点击的其实是表格里面的一行,所以你需要借助**onRow**这个方法

INFO

onRow的作用,用来设置行属性,它里面可以监听一些事件,包括点击,双击,鼠标悬停,鼠标移入等

jsx
<Table
  onRow={(record) => {
    return {
      onClick: (event) => {}, // 点击行
      onDoubleClick: (event) => {},
      onContextMenu: (event) => {},
      onMouseEnter: (event) => {}, // 鼠标移入行
      onMouseLeave: (event) => {},
    };
  }}
  onHeaderRow={(columns, index) => {
    return {
      onClick: () => {}, // 点击表头行
    };
  }}
  />

为了提高用户的体验,一般情况下,当用户选中一行以后,就建议将这个下拉框收回去(但是我还没有实现

代码

jsx
<ProFormSelect
  width="md"
  name="bomCode"
  label="机种料号"
  showSearch
  placeholder="请选择"
  // request发送请求获取
  request={async ({ keyWords }) => {
    let res = await getBomData({ pageSize: 10, current: 1, bomCode: keyWords })
    if (res.success) {
      setBomTable(res.data)
    }
    return []
  }}
  // 校验规则
  rules={[
    {
      required: true,
      message: '请选择机种料号!',
    },
  ]}
  // 设置防抖时间
  debounceTime={500}
  // 自定义下拉框内容
  fieldProps={{
    // 设置下拉框的内容,是一个表格
    dropdownRender: () => {
      return (
        <div style={{ height: '200px', overflowY: 'auto' }}>
          <Table
            size="small"
            showHeader={false}
            columns={bomColumns}
            dataSource={bomTable}
            pagination={false}
            rowKey={(record: { bomCode: string, bomName: string, bomSpec: string }) => record.bomCode}
            // 设置点击行的时候,将数据回填到表单中
            onRow={(record: { bomCode: string, bomName: string, bomSpec: string }) => {
              return {
                onClick: () => {
                  formRef.current?.setFieldsValue({
                    bomCode: record.bomCode,
                    bomName: record.bomName,
                    bomSpec: record.bomSpec,
                  })
                }
              }
            }}
            />
        </div>
      )
    },
    // 当清空的时候,将表单的值也清空
    onClear: () => {
      formRef.current?.setFieldsValue({
        bomName: "",
        bomSpec: "",
      })
    }
  }}
  >

</ProFormSelect>