帮同事debug发现的问题,记录一下,
弗曼学习大法好
interface RefFunType {
hdlUpdate(): void,
name: string
}
父组件使用useRef
useRef<RefFunType>()
传入的泛型是子组件存放在ref.current中的数据格式,
(如果子组件内部绑定了自己的ref,那用的泛型为useRef
ref是绑定在自定义组件上,子组件直接从props中获取ref是获取不到的,
这是因为ref不是prop属性,会像key一样,被React进行特殊处理
所以需要使用
React.forwardRef((props: any, React.Ref<unknown> | undefined) => {...})
将ref再重新转发到子组件中
在子组件中使用useImperativeHandle
useImperativeHandle(ref, () => ({
[key:string]: any
}));
第一个param为转发的ref,第二个参数为一个fun,返回一个对象,这个对象就是可以在父组件中通过ref.current?.XXX获取到的方法和数据
在父组件中,需要注意的是,ref 对象内容发生变化时,useRef 并不会通知你,当.current改变时,组件并不会rerender。
因此,当我们需要在父组件中使用子组件的数据时,有两种方法。
- 一种是在useEffect中将ref.current.XXX存放到state中,因为useEffect的调用是在组件完成渲染之后,也就是didMount之后才会调用,这个时候子组件已经渲染完成,内部的数据也已经构建完成,所以可以获取到内部数据
useEffect(() => {
setName(childRef.current!.name)
})
- 另外一种是使用回调ref,使用一个回调函数代替useRef
(官方:使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。)
const sexRef = useCallback(node => {
console.log('node && node.name', node && node.name)
node && setSex(node.name);
}, []);
<SexChild ref={sexRef}></SexChild>
而需要在父组件中调用子组件的方法的时候,我们可以使用这种方法
<button onClick={() => childRef.current?.hdlUpdate()}>update</button>
将子组件的方法嵌套多一层方法,如果不创建一个自己的方法的话,在第一次render的时候,current中的方法还没有创建完成,所以我们会将undefined传递给onClick。
而当我们创建了一个自己的方法,在内部去调用current.hdlUpdate的时候,此时调用的是current这个引用上的方法(把ref看成一个不会通知组件render,也不会重新创建的对象),此时hdlUpdate方法已经存在了。
一个比较具体的例子:
interface RefFunType {
hdlUpdate(): void,
name: string
}
// 子组件,使用forwardRef转发
const NameChild = forwardRef((props: any, ref: React.Ref<unknown> | undefined) => {
const [name, setName] = useState('jack');
useImperativeHandle(
ref,
// 将需要的方法暴露出去
() => ({
hdlUpdate: () => {
console.log('name', name)
},
name: 'name'
})
)
return <>
name: <input value={name} onChange={e => setName(e.target.value)}></input>
</>
});
// 另一个一样的子组件
const SexChild = forwardRef((props: any, ref: React.Ref<unknown> | undefined) => {
const [sex, setSex] = useState('man');
useImperativeHandle(
ref,
() => ({
hdlUpdate: () => {
console.log('sex', sex)
},
name: sex
})
)
return <>
sex: <input value={sex} onChange={e => setSex(e.target.value)}></input>
</>
})
interface SwitchComponentProps {
Component: React.ReactNode
}
// 中间组件,用来测试每次调用的current是否是当前子组件的,和直接在父组件写if渲染一样
const SwitchComponent:FC<SwitchComponentProps> = ({Component}) => {
return <div>
{Component}
</div>
}
// 父组件
const Index: FC = (props) => {
const [type, setType] = useState(0);
const [name, setName] = useState("");
// 创建ref
const childRef = useRef<RefFunType>();
const childrens = [
{ component: <NameChild ref={childRef} /> },
{ component: <SexChild ref={childRef} /> }
];
// 获取数据
useEffect(() => {
setName(childRef.current!.name)
})
// console.log('childRef.current', childRef.current)
// const timer = setInterval(() => {
// console.log('childRef.current', childRef.current)
// }, 3000)
// 在onClick绑定自己的方法,调用方法
return <div>
{name}
<SwitchComponent Component={childrens[type].component}/>
<button onClick={() => type ? setType(0) : setType(1)}>switch type</button>
<button onClick={() => childRef.current?.hdlUpdate()}>update</button>
</div>
}
这个是官方父组件调用子组件中的input的focus方法的例子
const FancyInput = forwardRef((props: any, ref: React.Ref<unknown> | undefined) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current?.focus();
}
}));
return <input ref={inputRef} />;
})
版权声明:本文为weixin_43375908原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。