1、定义
在React 数据流中,props是父组件与子组件交互的唯一方式。需要修改子组件,要使用新的props来重新渲染。
但有些情况下,需要我们强制修改子组件(被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素),这时就可以使用Refs。
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
2、使用场景
下面是几个适合使用 refs 的情况:
1.管理焦点,文本选择或媒体播放。
2.触发强制动画。
3.集成第三方 DOM 库。
4.避免使用 refs 来做任何可以通过声明式实现来完成的事情。
3、Refs API使用
3.1 创建Refs
Refs 使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。(在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们)
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
3.2 访问 Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。
console.log(this.myRef.current);//打印出设置了refs 的节点元素
ref 的值根据节点的类型而有所不同:
一、当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
//使用React.createRef()创建refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.value = "";
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>
);
}
}
二、当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
//为 class 组件添加 Ref
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
//调用class组件的方法
this.textInput.current.focusTextInput();
}
render() {
return <CustomTextInput ref={this.textInput} />;
}
}
你不能在函数组件上使用 ref 属性,因为他们没有实例,可但以在函数组件内部使用 ref 属性。
//使用React.createRef()创建refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点
this.textInput.current.value = "";
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>
);
}
}
4、回调 Refs
React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。
不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
//回调Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = null;
}
componentDidMount() {
this.myRef.innerHTML = "我是回调Refs";
console.log(this.myRef);
}
render() {
return (
<div
ref={element => {
this.myRef = element;
}}
/>
);
}
}
可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。
function MyComponent(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<MyComponent
inputRef={el => this.inputElement = el}
/>
);
}
}
5、String 类型的 Refs(老版本React中的API,已经过时,不建议使用)
//过时的Refs
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.refs.myRef.innerHTML = "我是过时的String 类型的Refs";//通过 this.refs.XXXX来访问 DOM 节点
console.log(this.refs.myRef);
}
render() {
return <div ref="myRef" />;
}
}
6、将 DOM Refs 暴露给父组件
在极少数情况下,你可能希望在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装。在新版本中可以使用Refs 转发,老版本可以将refs作为一个参数,放在props中直接传递(如下例)
function MyComponent(props) {//作为props的一员直接传递
return (
<div>
<input
ref={props.inputRef}
onChange={null}
/>
</div>
);
}
6.1 Refs 转发
Ref 转发是一项将 refs自动地通过组件传递到其一子组件的技巧。对于可重用的组件库是很有用的。
我们需要使用API React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM 。
const MyButton = React.forwardRef((props, ref) => (
<button ref={ref} >
{props.children}
</button>
));
这样,使用 MyButton 的组件可以获取底层 DOM 节点 button 的 ref ,并在必要时访问,就像其直接使用 DOM button 一样。
注意:
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。
class App extends React.Component {
constructor(props) {
super(props);
// 你可以直接获取 DOM button 的 ref:
this.ref = React.createRef();
}
componentDidMount() {
setTimeout(() => {
this.ref.current.innerText = "Refs 转发";
}, 2000);
console.log(this.ref.current);
}
render() {
return (
<div>
<MyButton ref={this.ref}>Click me!</MyButton>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
如图,在父组件打印出了refs内容,并且2秒改了了button上的内容。
6.2 在高阶组件中转发 refs
利用高阶组件,就可以转发 refs 到 class 组件实例中。什么是高阶组件呢?
高阶组件是一个函数,它接受一个组件并返回一个新组件。
class TextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
setInputValue(value){
this.textInput.current.value=value;
}
render() {
return (<div><input ref={this.textInput} /></div>);
}
}
class App extends React.Component {
constructor(props) {
super(props);
// 你可以直接获取 DOM button 的 ref:
this.ref = React.createRef();
this.HOCref = React.createRef();
}
componentDidMount() {
setTimeout(() => {
this.ref.current.innerText = "Refs 转发";
}, 2000);
console.log(this.ref.current);
console.log(this.HOCref.current);
this.HOCref.current.setInputValue('我是父组件设置的值');
}
render() {
return (
<div>
<MyButton ref={this.ref}>Click me!</MyButton>
<NewFancyButton ref={this.HOCref}>高阶函数</NewFancyButton>
</div>
);
}
}
const NewFancyButton = HOCFunc(TextInput);
function HOCFunc(Component) {
class HOCComponent extends React.Component {
componentDidUpdate(prevProps) {
console.log("old props:", prevProps);
console.log("new props:", this.props);
}
render() {
const { forwardedRef, ...rest } = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 HOCComponent,例如 “forwardedRef”
// 然后它就可以被挂载到被 HOCComponent 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <HOCComponent {...props} forwardedRef={ref} />;
});
}
上面的示例有一点需要注意:refs将不会透传下去。这是因为ref不是prop属性。就像key一样,其被React进行了特殊处理。如果你对HOC添加ref,该ref将引用最外层的容器组件,而不是被包裹的组件。
幸运的是,我们可以使用React.forwardRefAPI明确地将refs转发到内部的TextInput组件。React.forwardRef接受一个渲染函数,其接收props和ref参数并返回一个React节点。
上图中打印出了转发后得到refs的Class组件TextInput,并且在父组件中操作了子组件中的方法。