如何在React中利用ref以及ref转发,回调ref和HOC
React Ref是有用的功能,可作为从父组件中引用DOM元素或类组件的手段。然后,这为我们提供了一种读取和修改该元素的方法。
描述引用的最佳方法也许是作为桥梁。 允许组件访问或修改引用所附加元素的桥。使用ref为我们提供了一种在绕过状态更新和重新渲染的同时访问元素的方法;这在某些用例中可能很有用,但不应用作props和state的替代方法(如官方的React文档所指出的)。但是,在我们将在本文中访问的某些情况下,refs可以很好地工作。
引用还以引用转发的形式为从父组件引用子组件中的元素提供了一些灵活性-我们还将在这里探索如何做到这一点,包括如何将HOC的引用注入到其包装组件中。
Refs 高级用法
ref通常在类组件的构造函数中定义,或者在function组件定义为变量,然后附加到render()函数中的元素。这是一个简单的示例,我们在其中创建引用并将其附加到<input>元素:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef();
}
...
render() {
return (
<>
<input
name="email"
onChange={this.onChange}
ref={this.myRef}
type="text"
</>
)
}
}
使用React.createRef()
创建ref
,并将其分配给类属性。在上面的示例中,ref名为myRef,然后将其附加到<input>DOM元素。
将ref附加到元素后,即可通过ref访问和修改该元素。
让我们在示例中添加一个按钮。然后,我们可以附加一个onClick处理程序,该处理程序将myRef用于集中<input>其附加到的元素:
...
handleClick = () => {
this.myRef.current.focus();
}
render() {
return (
...
<button onClick={this.handleClick}>
Focus Email Input
</button>
</>
)
}
通过这样做,我们实际上是在更改输入元素的状态而没有任何React状态更新。在集中<input>注意力的情况下,这是有道理的-我们不想在每次集中/模糊输入时重新渲染元素。还有更多情况下,引用很有意义;我们将在本文的后续部分进行访问。
您可能已经注意到的current属性myRef;current指的是ref当前附加到的元素,并广泛用于访问和修改我们附加的元素。实际上,如果通过登录myRef控制台进一步扩展示例,我们将看到该current属性确实是唯一可用的属性:
componentDidMount = () => {
// myRef only has a current property
console.log(this.myRef);
// myRef.current is what we are interested in
console.log(this.myRef.current);
// focus our input automatically when component mounts
this.myRef.current.focus();
}
在componentDidMount生命周期阶段,myRef.current将按预期分配给我们的<input>元素;componentDidMount通常是使用ref处理某些初始设置的安全场所,例如,将第一个输入字段安装在舞台上时以某种形式聚焦。
如果我们更改componentDidMount为该componentWillMount怎么办?好吧,myRef将null处于组件生命周期的这个阶段。引用将在组件安装后初始化,从而需要将要附加引用的元素安装(或渲染)在舞台上。因此,componentWillMount不适合访问引用。
ref 能被绑定在哪里
引用相当可扩展,因为它们可以附加到DOM元素和React类组件,但不能附加到功能组件。我们可以访问的功能和数据取决于您将引用附加到何处:
- 引用DOM元素可让我们访问其属性
- 引用类组件使我们可以访问该组件的属性,状态,方法以及整个原型。
目前,我们无法将ref附加到React的function组件中。在以下示例中,myRef 将不会被识别并将导致错误:
// this will fail and throw an error
function FunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return (
<FunctionComponent ref={this.myRef} />
);
}
}
如果发生这种情况,您的应用将无法编译,确保该错误不会泄漏到最终版本中。
但是,我们可以在function组件中定义引用
但是,我们可以对function组件执行的操作是在其内部定义ref,然后将其附加到DOM元素或类组件。下面的示例是完全有效的:
//类组件将接受引用
class MyInput extends React.Component {
...
}
// ref is defined within functional component
function FunctionComponent() {
let myRef = React.createRef();
function handleClick() {
...
}
return (
<MyInput
handleClick={this.handlClick}
ref={myRef}
/>
);
}
如果您看到将ref传递到function组件的机会,则可能值得将其转换为类组件以支持它们。
有条件地将引用与回调引用一起使用
我们还可以将函数传递到元素的ref属性中,而不是传递给ref对象-这些类型的ref称为回调ref。这样做的想法是给我们更多与何时设置和取消设置引用相关的自由。
所引用的元素或组件将作为我们要嵌入ref
属性中的函数的参数提供。从这里我们可以更好地控制ref的设置方式。考虑以下:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myInput = null;
}
focusMyInput = () => {
if (this.myInput) this.myInput.focus();
};
setMyInputRef = element => {
this.myInput = element;
};
componentDidMount() {
this.focusMyInput();
}
render() {
return (
<input
name="email"
onChange={this.onChange}
ref={this.setMyInputRef}
type="text"
)
}
}
现在,我们没有定义构造函数中的ref,而是定义了一个空myInput属性。myInput在setMyInputRef()被调用之前,它不会引用DOM元素,这是在呈现组件时完成的。具体来说:
- 组件在元素setMyInputRef()的ref属性内进行渲染并调用<input>。
- setMyInputRef() 现在使用对元素的完整引用定义myInput类属性
- focusMyInput() 现在可以集中讨论有问题的元素。
这被视为初始化ref的更有利的方法,其中ref本身仅在相关元素存在于舞台上时才存在。
更多用例
因此,除了专注于输入之外,还能在哪里使用ref?在继续进行引用转发之前,让我们简要介绍一下一些用例:
- 增/递减 输入值: <input>以与上面的示例类似的方式将ref附加到字段,并创建一个onClick递增或递减输入值的处理程序。我们可以使用以下命令访问输入引用的值:
incrementValue = () => {
this.myRef.current.value++;
}
render() {
<input
type="text"
ref={this.myRef}
value="0"
/>
<button onClick={this.incremenetInput}>
Increment Input Value
</button>
获取输入值:也许需要引用输入值并将其包含在字符串或标签中-引用提供了从输入元素中获取当前值的桥梁。同样,裁判也可以读取是否已选中复选框或已选中哪个单选按钮;然后可以根据这些值修改舞台上的其他元素,而无需依赖状态。
- 选择表单值或在表单值之间循环: 具有有效表单值的数组,然后单击“下一个”或“上一个”按钮以更新所选值。这样的更新不保证状态更新。选择文本也是参考的一个好用例,参考将管理当前选定或突出显示的值。
- 媒体播放: 基于React的音乐或视频播放器可以利用ref来管理其当前状态-播放/暂停或浏览播放时间线。同样,这些更新不需要状态管理。
- 过渡和关键帧动画: 如果要触发元素上的动画或过渡,可以使用refs来实现。当一个元素需要触发一个单独元素的样式更新时,这特别有用,它也可以嵌套在另一个组件中。
在某些情况下,需要将引用从另一个组件中附加到元素上-这是在转发引用起作用时。让我们看看接下来如何集成引用转发。
转发ref
引用转发是一种将引用自动传递给子组件的技术,允许父组件访问该子组件的元素并以某种方式读取或修改它。
React为我们提供了额外的样板,专门用于ref转发,因此我们使用来包装组件React.forwardRef()
。让我们看一下它的用法:
//处理ref转发到MyInput组件
const MyInput = React.forwardRef((props, ref) => {
return(<input name={props.name} ref={ref} />);
});
//现在我们可以从父组件将ref向下传递到MyInput
const MyComponent = () => {
let ref = React.createRef();
return (
<MyInput
name="email"
ref={ref}
/>
);
}
在上面的示例中,我们使用React.forwardRef(),它为我们提供了2个参数-我们包装的组件的props,以及一个ref专门用于转发的ref的附加对象。
如果我们不使用来包装组件React.forwardRef(),则组件本身就是我们要引用的东西。这可能确实是您打算执行的操作,但是为了将ref转发下来,React.forwardRef()是必需的。
注意:之所以需要这个额外的样板,是因为refJSX属性没有被当作道具。像key,ref在React中有特定用途。这些可以看作是特定React功能的保留关键字。
我们也可以使用React.forwardRef()类组件。这样做不仅仅只是包装我们的组件,更像是HOC设置。看下面的示例,在该示例中,我们将转发的ref作为innerRefprop传递给包装的类组件:
//我们将ref传递给该组件,以用于我们的输入元素
class WrappedComponent extends Component {
render() {
return (
<input
type="text"
name={this.props.name}
ref={this.props.innerRef}
/>
)
}
}
//我们将包装的组件与forwardRef包装在一起,提供ref
const MyInput = React.forwardRef((props, ref) => {
return (<WrappedComponent innerRef={ref} {...props} />);
});
export default MyInput;
这比以前稍微多了一些样板;我们选择innerRef道具将ref传递给<WrappedComponent />。从这里我们可以继续进行操作,并将ref从父组件传递到<MyInput />:
import { MyInput } from './MyInput';
const MyComponent = () => {
let ref = React.createRef();
return (
<MyInput
name="email"
ref={ref}
/>
);
}
这使我们进入了定义HOC的用例,专门用于将转发的ref注入到组件DOM中。
在HOC中使用转发ref
为了进一步扩展HOC概念,让我们探索如何使用HOC包装组件,该组件会自动将ref转发到最初不具有ref支持的包装组件。具体而言,我们期望的是:
- 为了能够通过ref将ref传递到包装在HOC中的组件中forwardedRef。我们将命名为HOC
withRef
。 - HOC获取引用并将其转发到包装的组件
- 包装的组件,用于将ref附加到子元素
// define the withRef HOC
export function withRef(WrappedComponent) {
class WithRef extends React.Component {
render() {
const {forwardedRef, ...props} = this.props;
return(
<WrappedComponent
ref={forwardedRef}
{...props}
/>);
}
}
return React.forwardRef((props, ref) => {
return <WithRef {...props} forwardedRef={ref} />;
});
}
现在,我们可以简单地包装我们的<MyInput />组件withRef()以支持引用转发,从而提供该组件的非引用和受引用支持的版本:
...
export const MyInput = (props) => (
<input
name={props.name}
type="text"
/>
);
export const MyInputWithRef = withRef(MyInput);
现在,我们可以导入<MyInputWithRef />到任何组件中,这将把ref路由到子元素,而withRef()HOC在幕后:
import { MyInputWithRef } from './MyInput';
const MyComponent = () => {
let ref = React.createRef();
return (
<MyInputWithRef
name="email"
forwardedRef={ref}
/>
);
}
网友评论