如何全局控制组件行为(eg:快捷键实现input自动聚焦)

背景:

因为组件嵌套的关系,有时候在实现一些全局功能控制的时候,但是组件在内层。举个例子,如果想要通过快捷键让Input的光标能自动Focus。我们在外面不好控制内部input组件的focus方法,其实就是简单的让父组件去获取子组件的实例方法。

概念:

  • useRef

useRef是React提供的一个hook函数,用于创建一个可变的引用,它返回一个包含一个可变值的对象。useRef可以用来获取DOM元素的引用保存组件中的变量、进行组件之间的通信等。useRef可以在组件渲染期间创建,并且在整个组件的生命周期内保持不变。它的主要作用是在函数组件中模拟类组件中的实例变量,因为函数组件没有实例。

  • ForwardRefRenderFunction

它是一个函数类型,用于定义一个React组件的渲染逻辑。该函数接收两个参数:propsref,其中props包含组件的所有属性,而ref用于访问组件实例的引用。 ForwardRefRenderFunction的定义通常用于创建高阶组件(Higher-Order Component,HOC),即接受一个组件作为参数并返回一个新的组件。

通过使用ForwardRefRenderFunction,可以将ref传递给内部组件,从而使得外部组件能够访问内部组件的引用。

 const MyComponent = React.forwardRef((props, ref) => {
    return <div ref={ref}>Hello, {props.name}!</div>;
 });

在该组件中,使用React.forwardRef函数定义了一个ForwardRefRenderFunction,并将其作为参数传递给React.forwardRef函数。该函数接收props和ref参数,并返回一个React元素,其中ref属性被设置为传递进来的ref。这样,当该组件被渲染时,外部组件可以通过ref属性访问到该组件的引用。

  • forwardRef

React中的一个API,它允许我们将ref从组件传递到其子组件。它的作用是在组件中传递ref时,可以避免出现循环依赖的问题。使用forwardRef可以将ref传递给函数组件或类组件的内部元素,从而让我们可以在父组件中直接引用子组件的某个DOM节点或组件实例。forwardRef接受一个函数作为参数,这个函数返回一个React组件。这个函数有两个参数:props和ref,其中ref是从父组件传递下来的。

    export default forwardRef(Component);
  • useImperativeHandle

用于在函数组件中自定义暴露给父组件的实例值。它接受两个参数:

  1. ref:父组件传递的ref对象。
  2. createHandle:一个函数,返回一个对象,这个对象就是可以在父组件中通过ref.current?.XXX获取到的方法和数据,用于创建需要暴露给父组件的实例值。
  useImperativeHandle(ref,()=>({
   
  }));

createHandle函数返回的对象将会与传递给useImperativeHandle的ref对象关联起来。父组件可以通过ref.current来访问这个实例值。 使用useImperativeHandle可以避免在父组件中使用refs来访问子组件的内部状态和方法,从而提高代码的可读性和可维护性。

在父组件中,需要注意的是,ref 对象内容发生变化时,useRef 并不会通知你,当.current改变时,组件并不会rerender。

因此,当我们需要在父组件中使用子组件的数据时,有两种方法。

  • 一种是在useEffect中将ref.current.XXX存放到state中,因为useEffect的调用是在组件完成渲染之后,也就是didMount之后才会调用,这个时候子组件已经渲染完成,内部的数据也已经构建完成,所以可以获取到内部数据
  useEffect(()=>{
    setName(childRef.current?.name)
  },[])
  • 另外一种是使用回调ref,使用一个回调函数代替useRef(官方:使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。)
  const getRef = useCallback(node => {
    console.log(node, 'nodedededee'); //得到的是子组件暴露给父组件的实例值
  }, []);
  
  <ChildComp ref={getRef}/>

基于封装好的一个搜索组件实现:定义一个快捷键输入后,触发搜索框光标,可直接输入关键词

win: ctrl+F

mac: ⌘ +F

可以定义一个容器存储快捷键所具备的功能

//根据当前环境传入是否WIN还是Mac
export default (isMac: boolean, isWindows: boolean, e: any) => {
   //**ctrl+K**
  if (((isMac && e.metaKey) || (isWindows && e.ctrlKey)) && [70].includes(e.keyCode)) {
    e.preventDefault();
    return { mode: 'searchInput' };
  }
  return { mode: '' };
};

根据navigator.userAgent判断是WIN还是Mac


const getCurrentSystem = () => {
  let isMac = /macintosh|mac os x/i.test(navigator.userAgent);
  let isWindows = /windows|win32/i.test(navigator.userAgent);
  return isMac ? 'mac' : isWindows ? 'windows' : 'other';
};

子组件

import React, {
  useEffect,
  useCallback,
  useState,
  KeyboardEventHandler,
  useRef,
  ForwardRefRenderFunction,
  useImperativeHandle,
  forwardRef
} from 'react';
import { Input } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import clsx from 'clsx';
import lessStyles from './index.module.less';

type Props = React.PropsWithChildren<{
  defaultValue?: string;
  onChange: (val: string) => void;
  classes?: Record<string, any>;
  placeholder?: string;
  delay?: number;
  prefix?: any;
  autoFocus?: boolean;
  children?: React.ReactNode;
  disabled?: boolean;
  disabledEnterChinese?: boolean; // 是否禁止输入中文和空格
  addonBefore?: any;
  onPressEnter?: KeyboardEventHandler<HTMLInputElement>;
  onClear?: (event: React.ChangeEventHandler<HTMLInputElement>) => void;
}>;

const Comp: ForwardRefRenderFunction<unknown, Props> = (props, ref) => {
  const {
    defaultValue,
    prefix,
    autoFocus,
    classes: classesCustom = {},
    onChange,
    placeholder,
    delay = 200,
    children,
    disabled = false,
    disabledEnterChinese = false,
    addonBefore,
    onPressEnter,
    onClear
  } = props;
  const inputRef = useRef<any>(null);
  const [searchValue, setSearchValue] = useState<string>('');
  const [inputValue, setInputValue] = useState<string>('');
  const [isInit, setIsInit] = useState<boolean>(true);

  useEffect(() => {
    setSearchValue(defaultValue || '');
  }, [defaultValue]);

  useEffect(() => {
    const handler = setTimeout(() => {
      setInputValue(searchValue);
    }, delay);
    return () => {
      clearTimeout(handler);
    };
  }, [searchValue]);

  const onSearch = useCallback(
    (event: any) => {
      setIsInit(false);
      let val = event.target.value;
      if (!val && event.type !== 'change') {
        onClear && onClear(event);
      }
      if (disabledEnterChinese) {
        val = val.replace(/[\u4e00-\u9fa5]/gi, '').replace(/(^\s*)|(\s*$)/g, '');
      }
      setSearchValue(val || '');
    },
    [disabledEnterChinese, onClear]
  );

  useEffect(() => {
    if (!isInit) {
      onChange(inputValue);
    }
  }, [inputValue]);

  const inputAutoFocus = useCallback(() => {
    inputRef.current?.focus();
  }, [inputRef]);

  useImperativeHandle(
    ref,
    () => ({
      focus: () => {
        inputAutoFocus();
      }
    }),
    [inputAutoFocus]
  );

  return (
    <>
      <Input
        disabled={disabled}
        autoFocus={autoFocus}
        className={clsx(classesCustom.searchinput, lessStyles.searchinput)}
        placeholder={placeholder}
        onPressEnter={onPressEnter}
        allowClear
        value={searchValue}
        prefix={prefix !== undefined ? prefix : <SearchOutlined style={{ color: '#ccc' }} />}
        suffix={children}
        onChange={onSearch}
        addonBefore={addonBefore}
        ref={inputRef}
      />
    </>
  );
};

export default React.memo(forwardRef(Comp));

注意:要结合PropsWithChildren、ForwardRefRenderFunction、forwardRef使用,否则调用子组件的时候会报错

如何全局控制组件行为(eg:快捷键实现input自动聚焦)

父组件调用


const Search: FunctionComponent<Props> = props => {

  //创建ref用于接收
  const ref = useRef<any>();

  useEffect(() => {
    let myfunc = e => {
      let currSystem = getCurrentSystem();
      let isMac = currSystem === 'mac';
      let isWindows = currSystem === 'windows';
      const { mode } = getKeyboard(isMac, isWindows, e);
      if (mode === 'searchInput') {
        ref.current.focus();

      }
    };
    document.addEventListener('keydown', myfunc);

    return () => {
      document.removeEventListener('keydown', myfunc);
    };
  }, [ref]);

  //获取搜索的值做业务逻辑
  const onSearch = useCallback(value => {
  
  }, []);

  const getRef = useCallback(node => {
    console.log(node, 'nodedededee');
  }, []);

  return (
    <>
      <SearchInput
       placeholder={header.searchplaceholder4header}
       onChange={onSearch}
       onPressEnter={onPressEnter}
       ref={ref}
       />
    </>
  );
};

export default React.memo(Search);

以上功能主要就是通过useRef、ForwardRefRenderFunction、forwardRef、useImperativeHandle实现父组件直接获取子组件暴露给父组件的实例值,去达到预期的效果。

原文链接:https://juejin.cn/post/7221820151397072933 作者:顶级自由人

(0)
上一篇 2023年4月15日 上午11:01
下一篇 2023年4月15日 上午11:12

相关推荐

发表回复

登录后才能评论

评论列表(1条)

  • 头像
    lutt 2023年4月18日 下午3:09

    竟然被收录了,amazing偷笑