头部
终于到了这个系列最后一篇 hook 教程,其实还有几个 hook 没有介绍,有的是几乎用不到的,还有的是 React 18 版本新出的(不要再更新啦,老夫真的学不动啦)
说回正题,众所周知,React 是单向数据流,是由父组件传参给子组件,所以一般只能子组件调用父组件方法,但是实际业务复杂多变,有些场景就是需要父组件调用子组件,所以 React 也提供了这一方式,但是官方也说了,应当避免这种命令式的代码,破坏了 React 父不知子的原则
中部
useImperativeHandle 应当与 forwardRef 和 useRef 一起使用
- useImperativeHandle 定义子组件暴露给父组件的属性或方法
- forwardRef 暴露子组件的引用给父组件
- useRef 父组件获取子组件的引用
正经
import React from "react";
import "./App.css";
type TChildRef = {
data: number;
getValue: (value: string) => string;
};
type TChildProps = {
value: number;
};
const Child = React.forwardRef<TChildRef, TChildProps>((props, ref) => {
const [state, setState] = React.useState(props.value);
React.useImperativeHandle(ref, () => {
return {
data: state,
getValue: (value) => {
return value.repeat(5);
},
};
});
return <h1 onClick={() => setState((state) => state + 1)}>child state:{state}</h1>;
});
const App: React.FC = () => {
const childRef = React.useRef<TChildRef>(null);
return (
<div>
<Child ref={childRef} value={0} />
<button
onClick={() => {
console.log("data", childRef.current?.data);
console.log("getValue", childRef.current?.getValue("123"));
}}
>
call child
</button>
</div>
);
};
export default App;
运行效果如下:
写起来还是有点小麻烦,要用 3 个 hook 才能实现,寻思着能不能有简单的方式呢
还真被我想出一种,不过没在实际项目中用过,不知道会有啥 BUG,需要用到 2 个 hook
- useRef 利用其跨生命周期特性保存值,而JS里的值可以为函数,那么可以在父组件初始化,传给子组件去赋值
- useEffect 子组件重新渲染时赋值,组件销毁时设为空,避免内存泄露
歪纬
import React from "react";
import "./App.css";
type TChildRef = {
data?: number;
getValue?: (value: string) => string;
};
type TChildProps = {
handle: React.MutableRefObject<TChildRef>;
value: number;
};
const Child: React.FC<TChildProps> = (props) => {
const { handle, value } = props;
const [state, setState] = React.useState(value);
React.useEffect(() => {
console.log("init");
handle.current = {
data: state,
getValue: (value) => {
return value.repeat(5);
},
};
return () => {
console.log("uninit");
handle.current = {};
};
});
return <h1 onClick={() => setState((state) => state + 1)}>child state:{state}</h1>;
};
const App: React.FC = () => {
const childRef = React.useRef<TChildRef>({});
return (
<div>
<Child handle={childRef} value={0} />
<button
onClick={() => {
console.log("data", childRef.current.data);
console.log("getValue", childRef.current.getValue?.("123"));
}}
>
call child
</button>
</div>
);
};
export default App;