1.forwardRef 两种用法
1.forwardRef+useImperativeHandle 定义 ref 上的方法
1.useImperativeHandle 介绍
useImperativeHandle 是 React 中的一个 Hook,通常与 forwardRef 一起使用,用来 暴露组件内部实例的特定值或方法 给父组件。它允许你控制从子组件传递给父组件的 ref 的内容,而不是直接暴露整个组件实例。
主要用途:
useImperativeHandle 允许你 定制通过 **ref** 传递给父组件的接口。通常情况下,父组件可以通过 ref 获取子组件的完整实例,但有时你可能只希望父组件能访问到某些特定的方法或属性,而不是整个实例。这时,useImperativeHandle 就派上用场了。
语法:
useImperativeHandle(ref, createHandle, [deps]);ref:来自父组件的ref,通常是通过forwardRef转发的。createHandle:一个函数,返回你希望暴露给父组件的值或方法。[deps]:依赖项数组,类似于useEffect的依赖项数组,表示只有当这些值发生变化时,才会重新生成暴露的接口。
使用场景:
- 控制暴露的接口:当父组件需要调用子组件的方法,但你不希望直接暴露整个子组件实例时,可以通过
useImperativeHandle来实现。 - 封装逻辑:你可以只暴露对子组件某些行为的控制,而隐藏其他不需要暴露的实现细节。
示例:
假设有一个自定义的 Input 组件,父组件可以通过 ref 控制它的焦点或清除它的内容,但不暴露其他方法。
子组件(Input.js):
import React, { useImperativeHandle, useRef, forwardRef } from "react";
// 使用 forwardRef 转发 ref 给子组件
const Input = forwardRef((props, ref) => {
const inputRef = useRef();
// 使用 useImperativeHandle 来暴露给父组件的接口
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = "";
},
}));
return <input ref={inputRef} />;
});
export default Input;父组件:
import React, { useRef } from "react";
import Input from "./Input";
const ParentComponent = () => {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current.focus(); // 调用子组件暴露的 focus 方法
};
const handleClear = () => {
inputRef.current.clear(); // 调用子组件暴露的 clear 方法
};
return (
<div>
<Input ref={inputRef} />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleClear}>Clear Input</button>
</div>
);
};
export default ParentComponent;关键点:
**forwardRef**:用来将父组件传递的ref转发到子组件内部。**useImperativeHandle**:在子组件中定制ref暴露给父组件的内容。
总结:
useImperativeHandle主要用于与forwardRef一起使用,以控制子组件通过ref暴露给父组件的内容。- 它使得子组件可以选择性地暴露某些方法或属性,而不是暴露整个组件实例,这有助于提高代码的封装性和可维护性。
2.例子:使用 forwardRef 获取组件实例,调用内部函数
内部

暴露方法:使用 useImperativeHandle

外部


3.效果:外部点击算子的时候,通过 ref 调用子组件侧边栏弹出的方法
如果你想在外部调用 ResizableRightDiv 组件中的 handleShrink 函数,你需要通过 Ref 来引用 ResizableRightDiv 组件,并使用引用来调用 handleShrink 函数。
首先,在 ResizableRightDiv 组件中,需要使用 useImperativeHandle 钩子函数将 handleShrink 函数暴露给父组件。以下是示例代码:
import React, { forwardRef, useImperativeHandle } from 'react';
// ...
const ResizableRightDiv = forwardRef(({ onShrink }, ref) => {
// ...
const handleShrink = () => {
// 执行收缩操作
// ...
// 调用传递进来的 onShrink 回调函数
onShrink();
};
// 将 handleShrink 函数暴露给父组件
useImperativeHandle(ref, () => ({
handleShrink,
}));
// ...
return (
// ...
);
});
export default ResizableRightDiv;在上述代码中,我们使用 forwardRef 函数来创建一个引用转发的组件。然后,在 useImperativeHandle 钩子函数中,将 handleShrink 函数暴露给父组件。
接下来,在父组件中,你需要创建一个引用,并将其传递给 ResizableRightDiv 组件。然后,你就可以通过引用来调用 handleShrink 函数。以下是示例代码:
import React, { useRef } from "react";
import ResizableRightDiv from "./ResizableRightDiv";
export default function ParentComponent() {
const resizableRef = useRef(null);
const handleShrinkButtonClick = () => {
resizableRef.current.handleShrink();
};
return (
<div>
<button onClick={handleShrinkButtonClick}>收缩</button>
<ResizableRightDiv ref={resizableRef} />
</div>
);
}在上述代码中,我们创建了一个引用 resizableRef,并将其传递给 ResizableRightDiv 组件的 ref 属性。然后,在点击收缩按钮的事件处理程序中,我们通过引用调用 handleShrink 函数。
2.forwardRef 直接绑定到子组件的 dom 上
直接使用 forwardRef 转发 ref
const Input = forwardRef((props, ref) => {
return <input ref={ref} />;
});- 在这个例子中,你只是简单地将
ref转发给了子组件中的input元素。这意味着父组件传递给ref的 将直接指向**input**元素,而不是通过一个自定义的接口暴露给父组件。 - 如果你仅仅想让父组件控制
input元素(例如,聚焦、获取值等),那么这种写法就足够了。
如果改成使用 useImperativeHandle 暴露方法
import React, { useRef, forwardRef, useImperativeHandle } from "react";
const Input = forwardRef((props, ref) => {
const inputRef = useRef();
// 使用 useImperativeHandle 来暴露接口给父组件
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus(); // 聚焦到 input 元素
},
clear: () => {
inputRef.current.value = ""; // 清空输入框内容
},
}));
return <input ref={inputRef} />;
});**useImperativeHandle**允许你定制父组件通过ref能访问到的接口。这种方式适用于当你需要暴露自定义方法(如focus,clear等)时。- 通过
useImperativeHandle,你能够控制父组件能访问到哪些特定的属性或方法,而不是直接暴露整个input元素的ref。
何时使用哪种方式?两者的对比
- **简单转发 **
**ref**:如果父组件只需要直接访问 DOM 元素的ref,比如操作一个<input />的值或调用它的focus()方法,你不需要useImperativeHandle。直接转发ref就足够了。 - 暴露特定方法或接口:如果你希望父组件通过
ref控制子组件的某些特定行为(例如调用自定义方法),那么就需要使用useImperativeHandle来控制暴露给父组件的接口。
3.React 组件通讯方式的总结
forwardRef 和 useImperativeHandle 是 React 中用来实现 父子组件之间的通信 的一种特定方式,尤其是在 管理子组件的实例方法 时。这种组合方式的确可以看作是一种特殊的父到子通讯方式,允许父组件通过 ref 访问子组件暴露的特定接口。
但 React 提供了多种组件间通信的方式,下面是几种常见的父子、子父之间的通信方式以及它们的使用场景。
1. 通过 **props** 传递数据(父到子)
这是最常见的通信方式。父组件通过 props 向子组件传递数据或方法。子组件通过 props 接收并使用这些数据或方法。
- 父到子:通过
props传递数据或方法给子组件。 - 子到父:通过调用父组件传递的函数,父组件可以接收来自子组件的数据。
示例:
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return <Child count={count} increment={increment} />;
}
// 子组件
function Child({ count, increment }) {
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}2. 通过 **ref** 和 **forwardRef**(父<->子,控制子组件实例)
ref 是用于访问组件实例或 DOM 元素的方式。通过 ref,父组件可以控制子组件的实例方法或 DOM 元素,尤其是在类组件中比较常见。
- 父到子:父组件通过
ref获取子组件实例或 DOM。 - 子到父:子组件没有直接向父组件传递数据,而是通过
ref暴露某些方法。
示例:
// 子组件
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
focus: () => {
// 子组件暴露给父组件的方法
console.log("Focus method called");
},
}));
return <input />;
});
// 父组件
function Parent() {
const childRef = useRef();
const handleFocus = () => {
childRef.current.focus(); // 父组件调用子组件暴露的方法
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}forwardRef用于将ref转发到子组件。useImperativeHandle用来定制通过ref暴露给父组件的内容(如暴露focus方法)。
3. **Context**** API(深层次的组件传递数据):父到子**
Context 是一种用于跨组件树传递数据的机制。它适合用于多个组件之间共享状态,避免通过层层传递 props。
- 父到子:通过
Context.Provider将数据传递给子组件及其嵌套的后代组件。 - 子到父:子组件可以通过
useContext来访问父组件通过Context提供的值。
示例:
const MyContext = React.createContext();
function Parent() {
const [value, setValue] = useState("Hello, World!");
return (
<MyContext.Provider value={value}>
<Child />
</MyContext.Provider>
);
}
function Child() {
const value = useContext(MyContext); // 从 context 中获取父组件提供的数据
return <p>{value}</p>;
}4. 传递操作函数:子到父
子组件通过调用父组件传递下来的更新函数,来实现将数据传递给父组件。通常通过 useState 和 useEffect 来管理和传递状态。
示例:
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const handleCountChange = (newCount) => {
setCount(newCount); // 父组件更新状态
};
return <Child onCountChange={handleCountChange} />;
}
// 子组件
function Child({ onCountChange }) {
const handleClick = () => {
onCountChange(5); // 子组件通过回调函数传递数据给父组件
};
return <button onClick={handleClick}>Update Count</button>;
}5. **useReducer**(共享状态的父子通信):props+传操作函数:父<->子
useReducer 提供了一种管理组件复杂状态的方法,适用于需要多个操作来处理数据变化的场景。父子组件都可以通过分发动作(dispatch)来更新状态。
示例:
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
default:
return state;
}
}
function Parent() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return <Child state={state} dispatch={dispatch} />;
}
function Child({ state, dispatch }) {
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}总结:React 组件通信方式
React 提供了多种父子组件间的通信方式,每种方式都有其特定的使用场景。
**props**:最常见的父子通信方式,父组件通过props向子组件传递数据,子组件可以通过回调函数将数据传递回父组件。**ref**** 和 ****forwardRef**:父组件通过ref获取子组件实例或 DOM 元素,用于访问和控制子组件的特定行为或方法。**Context**:用于跨层级的组件共享数据,避免逐层传递props,适用于全局状态管理。**useState**** 和 ****useEffect**:子组件通过回调函数更新父组件的状态,父组件通过useState管理数据,子组件通过props与父组件交互。**useReducer**:适用于复杂状态管理,父子组件可以通过dispatch来更新状态。
**forwardRef**** 和 ****useImperativeHandle** 是一种父到子、控制子组件实例行为的方式,通常用于需要暴露子组件特定方法(而非整个实例)时,帮助父组件控制子组件的行为,而不是直接操作子组件的 DOM。
