背景:
因为组件嵌套的关系,有时候在实现一些全局功能控制的时候,但是组件在内层。举个例子,如果想要通过快捷键让Input的光标能自动Focus。我们在外面不好控制内部input组件的focus方法,其实就是简单的让父组件去获取子组件的实例方法。
概念:
- useRef
useRef是React提供的一个hook函数,用于创建一个可变的引用,它返回一个包含一个可变值的对象。useRef可以用来获取DOM元素的引用、保存组件中的变量、进行组件之间的通信等。useRef可以在组件渲染期间创建,并且在整个组件的生命周期内保持不变。它的主要作用是在函数组件中模拟类组件中的实例变量,因为函数组件没有实例。
- ForwardRefRenderFunction
它是一个函数类型,用于定义一个React组件的渲染逻辑。该函数接收两个参数:props和ref,其中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
用于在函数组件中自定义暴露给父组件的实例值。它接受两个参数:
- ref:父组件传递的ref对象。
- 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使用,否则调用子组件的时候会报错
父组件调用
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 作者:顶级自由人
评论列表(1条)
竟然被收录了,amazing