React核心概念:单向数据流(Unidirectional Data Flow)
React 中数据的流动像瀑布一样,只能从上层组件(父组件)流向下层组件(子组件)。
子组件无法直接反向修改父组件的数据,只能通过父组件传递的回调函数来“通知”父组件修改数据。
(他即使子组件调用了,也只是通知📢没法修改)
【父调子】
useImperativeHandle
这个东西是父组件想要用一下子的,就比较难,因为有门槛不希望你这样做。
分三步:
- 子组件用
forwardRef
包裹:允许父组件传递ref
进来。 - 子组件内用
useImperativeHandle
:定义要暴露给父组件的内容。 - 父组件通过
ref
调用子组件的方法:比如ref.current.focus()
。
const ChildInput = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
con: () => {
console.log('122aaa ');
},
}
父组件那边 childRef.current
<ChildInput ref={childRef} />
那还有,如果你是用ts,总是报错 ref.current.xxx,那是因为你没有定义好类型。
// 定义子组件暴露给父组件的方法类型
export type ChildInputHandle = {
focus: () => void;
clear: () => void;
};
然后也要forwardRef<ChildInputHandle>
父组件里也类型
const childRef = useRef<ChildInputHandle>(null);
最后if xxx .current再操作
关键点解释:
-
为什么用
forwardRef
?React 默认不允许组件直接接收
ref
(除非是原生 DOM 元素),所以需要用forwardRef
包裹子组件,让它能接收父组件传递的ref
。 -
useImperativeHandle
的作用它像一个“过滤器”,决定父组件通过
ref
能访问子组件的哪些方法/属性。比如上面的例子中,父组件只能调用focus
和clear
,而无法直接操作子组件的 DOM(比如改输入框的值)。 -
调用时的注意事项
父组件必须通过
ref.current.方法名()
调用,且要确保子组件已经正确暴露了该方法(否则会报错xxx is not a function
)。
子组件使用父亲的方法
那如果是是子组件去用父组件用的,那么就会简单一点,直接传props就行了
- 父组件给子组件一个“对讲机”(回调函数)
- 子组件需要传数据时,按对讲机说话(调用回调函数并传数据)
- 父组件自动响应:父组件收到数据后,更新自己的状态(比如
setState
),触发重新渲染
如果发现父组件频繁调用子组件方法,很可能你的组件结构需要重构(比如状态提升到父组件)。useImperativeHandle
应当是最后的选择,而不是首选方案
关键点解释:
- 数据流向:
- 父 → 子:父组件通过
props
(如value={count}
)将数据传递给子组件。 - 子 → 父:子组件通过调用父组件传递的回调函数(如
onChildClick()
)触发父组件更新数据。
- 父 → 子:父组件通过
- 为什么说它是单向的?
- 父组件的数据变化会自动流向子组件(子组件的
value
会更新)。 - 子组件不能直接修改父组件的
count
,只能通过回调函数“请求”父组件自己修改。
- 父组件的数据变化会自动流向子组件(子组件的
- “从大到小”的层级:
- 这里的“大”指的是组件层级更高(如父组件),数据从高层级流向低层级(子组件)。
附 父调子的react代码
// 父组件 Parent.tsx
import React, { useRef } from 'react';
import ChildInput, { ChildInputHandle } from './childInput';
export function Parent() {
// 明确指定 ref 类型为 ChildInputHandle | null
const childRef = useRef<ChildInputHandle>(null);
const handleClick = () => {
if (childRef.current) {
childRef.current.focus(); // TS 不再报错
}
};
return (
<div>
<ChildInput ref={childRef} />
<button onClick={handleClick}>Focus</button>
</div>
);
}
// 子组件 ChildInput.tsx
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
// 定义子组件暴露给父组件的方法类型
export type ChildInputHandle = {
focus: () => void;
clear: () => void;
};
const ChildInput = forwardRef<ChildInputHandle>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
}));
return <input ref={inputRef} />;
});
ChildInput.displayName = 'childInput';
export default ChildInput;