Refs and the DOM
Refs 提供了一种方式,允许我们访问DOM节点火灾render 方法中创建的React 元素。
在典型的 React 数据流中,props 是父组件于子组件交互的唯一方式。要修改一个子组件,你需要使用新的props来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。
什么时候使用 Refs
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
避免使用 refs 来做任何可以通过声明式实现来完成的事情。
举个例子,避免在 Dialog 组件里暴露 open()和 close()方法,最好传递 isOpen 属性。
不要过度使用 Refs
你可能首先会想到使用 refs 在你的 app 中 “让事情发生” 如果是这种情况,考虑一下 state 属性应该被安排在那个组件层。通常情况下,让更高的组件层级拥有这个 state ,是更恰当的。
创建 Refs
Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(porps);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
访问 Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同:
- 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的ref 接受底层 DOM 元素作为 current 属性。
- 当 ref属性用于自定义 class 组件时,ref 对象接受组件的挂载实例作为其 current 属性。
- 你不能在函数组件上使用 ref 属性,因为他们没有实例。
为 DOM 元素添加 ref
使用 ref 去存储 DOM 节点的引用:
class CustomTextInput extends React.Component {
constructor(porps){
super(props);
//创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
//直接使用原生 API 使 text 输入框获得焦点
//注意:我们通过 “current” 来访问 DOM 节点
this.textInput.current.focus();
}
render() {
//告诉 React 我们想吧 <input> ref 关联到
//构造器里创建的 ‘textInput’ 上
return(
<div>
<input
type="text"
ref={this.textInput}
/>
<input
type="button"
value="Focus the text input"
onclick={this.focusTextInput}
/>
</div>
);
}
}
通过current可以直接访问 DOM 元素。
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
为class组件添加 Ref
如果我们想包装上面的 CustomTextInput ,来模拟它挂载之后立即被点击的操作,我们可以使用 ref 来获取这个自定义的 input 组件并手动调用它的 focusTextInput 方法:
class AutoFocusTextInput extends React.Component {
constructor(porps){
super(props);
this.textInput = React.createRef();
}
componentDidMount(){
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
仅在 CustomTextInput 声明为 class 时才有效:
class CustomTextInput extends React.Component {
// ...
}
Refs 与函数组件
你不能在函数组件上使用 ref 属性,因为它们没有实例:
function MyFunctionComponent () {
return <input />;
}
class Parent extends React.Component {
constryctor(props) {
super(porps);
this.textInput = React.createRef();
}
render() {
// This will "not" work!
return (
<MyFinctionComponent ref={this.textInput} />
);
}
}
如果你需要使用 ref ,你应该将组件转化为一个 class ,就像当你需要使用生命周期钩子或 state 时一样。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
function CustomTextInput (porps){
// 这里必须声明 textInput ,这样 ref 才可以引用它
let textInput = React。createRef();
function handleClick() {
textInput.current.focus();
}
return(
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onclick={handleClick}
/>
</div>
);
}
回调 Refs
React 也支持另一种设置 refs 的方式,称为”回调 refs“。他能助你更细的控制何时 refs 被设计和解除。
不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM元素作为参数,以时它们能在其他地方被存储和访问。
class CustomTextInput extends React.Component {
constructor(porps){
super(props);
this.textInput = null;
this.setTextInputRef = element =>{
this.textInput = element;
};
this.focusTextInput= () =>{
//使用原生 DOM API 使 text 输入框获得焦点
if(this.textInput) this.textInput.focus();
};
}
componentDidMount() {
//组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}
render(){
// 使用 ‘ref’ 的回调函数将 text 输入框 DOM 节点的引用存储到 React
return(
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
可以在组件间传递回调形式的 refs。
function CustomTextInput(porps) {
return(
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return(
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}