React hooks 和功能组件 ref
React hooks and functional component ref
const Comp1 = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
print: () => {
console.log('comp1')
}
}), []);
return <div>comp1</div>
});
const Comp2 = () => <div>comp2</div>;
const App = () => {
const ref1 = useRef(null);
const ref2 = useRef(null);
useEffect(() => {
console.log(ref1); // prints ref1 with the expected current
console.log(ref2); // prints ref2 with current: null
})
return <div><Comp1 ref={ref1}/><Comp2 ref={ref2}/></div>
}
- Comp1 和 Comp2 refs 有什么区别?
- 为什么我必须将 forwardRef 与 useImperativeHandle 一起使用才能真正获得对 Comp1 的引用?
我会尝试从问题 2 开始回答
useImperativeHandle 描述引用调用语法将与
公开给父级的实例值的一些自定义方法。
forwardRef 帮助通过高阶组件转发引用以引用内部 DOM 节点。
当您尝试聚焦时:ref1.current.focus(),子输入将被聚焦,而不是包装子输入的高阶组件。
查看与使用 useImperativeHandle 的 setValue 方法和 ChildInput 中的转发输入相关的答案相关的简单示例:
https://codesandbox.io/s/react-hook-useimperativehandle-huktt
React 文档说:
You may not use the ref attribute on function components because they don’t have instances. (more)
这意味着您无法将引用绑定到功能组件。这就是为什么你的 ref2.current
是 null
。如果要将 ref 绑定到组件,则需要使用 class 组件。您的 ref1
不是 Comp1
组件的引用。它实际上包含一个您在 useImperativeHandle
挂钩中传递的对象。即它包含下一个对象:
{
print: () => {
console.log('comp1')
}
}
如果要将 ref 与组件呈现的某些 HTML 元素或 class 组件绑定,则必须将 forwardRef
与功能组件一起使用。或者您可以使用 useImperativeHandle
钩子将您的 ref 与某个对象绑定。
更新
使用 useImperativeHandle
与向 class 组件添加方法相同:
class Comp1 extends React.Component {
print() {
console.log('comp1');
}
render() {
return (<div>comp1</div>)
}
}
与
相同
const Comp1 = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
print: () => {
console.log('comp1')
}
}), []);
return <div>comp1</div>
});
您在评论中提问:
So after moving to hooks (and still avoid using classes), the only way to use ref is to use useImperativeHandle
(and actually use a "fake" ref)? Is this a good practice?
答案: 使用 useImperativeHandle
与在 class 组件中通过引用调用子组件方法是相同的不良做法。 React 文档说你应该避免通过 refs 调用子组件方法,你应该避免使用 useImperativeHandle
。此外,您需要避免使用 refs,因为您可以在没有它们的情况下做事。
1. What's the difference between Comp1 and Comp2 refs?
Comp1
使用 React.forwardRef
并且能够从父级接收给定的 ref
。
Comp2
将不起作用并触发以下错误:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
?
<Child ref={ref1}/>
就像来自父组件的请求:“嘿 Child
,请将您的组件引用或类似内容传递给给定的可变存储框 (ref
),以便我可以调用你的方法或直接操作包含的 DOM 节点。"
问题 - 函数组件没有 instance:
// React internally calls `new ClassComp()`, instance can be stored and passed in a ref
<ClassComp ref={compRef} />
// React just *calls* the function for re-renders, there is no plain function "instance"
<FunctionComp ref={compRef} />
React.forwardRef
解决了上述限制。和
const FunctionComp = React.forwardRef((props, ref) => <div ref={ref}>Hello FnComp</div>
尽管没有实例,、FunctionComp
仍然可以将代表某些内容传递给 Parent
给出的 ref
(如 div
DOM 节点)。 ClassComp
而不是在此处传递它的实例。
2. Why do I have to use forwardRef
along with useImperativeHandle
in order to actually get the ref to Comp1
?
您不必这样做。 useImperativeHandle
是一个扩展以提供更自定义的命令式调用 API。以下三种选择是等价的:
forwardRef
仅:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
return <input ref={ref} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
仅限 useImperativeHandle
,使用自定义 ref 道具(查看 了解更多信息):
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp customRef={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = ({ customRef }) => {
const inputRef = React.useRef();
React.useImperativeHandle(customRef, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle
+ forwardRef
:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
const Comp1 = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
print: () => {
console.log('comp1')
}
}), []);
return <div>comp1</div>
});
const Comp2 = () => <div>comp2</div>;
const App = () => {
const ref1 = useRef(null);
const ref2 = useRef(null);
useEffect(() => {
console.log(ref1); // prints ref1 with the expected current
console.log(ref2); // prints ref2 with current: null
})
return <div><Comp1 ref={ref1}/><Comp2 ref={ref2}/></div>
}
- Comp1 和 Comp2 refs 有什么区别?
- 为什么我必须将 forwardRef 与 useImperativeHandle 一起使用才能真正获得对 Comp1 的引用?
我会尝试从问题 2 开始回答
useImperativeHandle 描述引用调用语法将与 公开给父级的实例值的一些自定义方法。
forwardRef 帮助通过高阶组件转发引用以引用内部 DOM 节点。
当您尝试聚焦时:ref1.current.focus(),子输入将被聚焦,而不是包装子输入的高阶组件。
查看与使用 useImperativeHandle 的 setValue 方法和 ChildInput 中的转发输入相关的答案相关的简单示例: https://codesandbox.io/s/react-hook-useimperativehandle-huktt
React 文档说:
You may not use the ref attribute on function components because they don’t have instances. (more)
这意味着您无法将引用绑定到功能组件。这就是为什么你的 ref2.current
是 null
。如果要将 ref 绑定到组件,则需要使用 class 组件。您的 ref1
不是 Comp1
组件的引用。它实际上包含一个您在 useImperativeHandle
挂钩中传递的对象。即它包含下一个对象:
{
print: () => {
console.log('comp1')
}
}
如果要将 ref 与组件呈现的某些 HTML 元素或 class 组件绑定,则必须将 forwardRef
与功能组件一起使用。或者您可以使用 useImperativeHandle
钩子将您的 ref 与某个对象绑定。
更新
使用 useImperativeHandle
与向 class 组件添加方法相同:
class Comp1 extends React.Component {
print() {
console.log('comp1');
}
render() {
return (<div>comp1</div>)
}
}
与
相同const Comp1 = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
print: () => {
console.log('comp1')
}
}), []);
return <div>comp1</div>
});
您在评论中提问:
So after moving to hooks (and still avoid using classes), the only way to use ref is to use
useImperativeHandle
(and actually use a "fake" ref)? Is this a good practice?
答案: 使用 useImperativeHandle
与在 class 组件中通过引用调用子组件方法是相同的不良做法。 React 文档说你应该避免通过 refs 调用子组件方法,你应该避免使用 useImperativeHandle
。此外,您需要避免使用 refs,因为您可以在没有它们的情况下做事。
1. What's the difference between Comp1 and Comp2 refs?
Comp1
使用 React.forwardRef
并且能够从父级接收给定的 ref
。
Comp2
将不起作用并触发以下错误:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use
React.forwardRef()
?
<Child ref={ref1}/>
就像来自父组件的请求:“嘿 Child
,请将您的组件引用或类似内容传递给给定的可变存储框 (ref
),以便我可以调用你的方法或直接操作包含的 DOM 节点。"
// React internally calls `new ClassComp()`, instance can be stored and passed in a ref
<ClassComp ref={compRef} />
// React just *calls* the function for re-renders, there is no plain function "instance"
<FunctionComp ref={compRef} />
React.forwardRef
解决了上述限制。和
const FunctionComp = React.forwardRef((props, ref) => <div ref={ref}>Hello FnComp</div>
尽管没有实例,、FunctionComp
仍然可以将代表某些内容传递给 Parent
给出的 ref
(如 div
DOM 节点)。 ClassComp
而不是在此处传递它的实例。
2. Why do I have to use
forwardRef
along withuseImperativeHandle
in order to actually get the ref toComp1
?
您不必这样做。 useImperativeHandle
是一个扩展以提供更自定义的命令式调用 API。以下三种选择是等价的:
forwardRef
仅:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
return <input ref={ref} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle
,使用自定义 ref 道具(查看 const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp customRef={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = ({ customRef }) => {
const inputRef = React.useRef();
React.useImperativeHandle(customRef, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
useImperativeHandle
+ forwardRef
:
const App = () => {
const compRef = React.useRef();
return (
<div>
<Comp ref={compRef} />
<button
onClick={() => {
compRef.current.focus();
}}
>
Focus input
</button>
</div>
);
}
const Comp = React.forwardRef((props, ref) => {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>