1.Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。
2.使用refs的情况:
(1)处理焦点、文本选择、媒体控制。
(2)自定义动画
(3)通过第三方DOM库,获取DOM节点
3.如果可以,优先通过声明状态实现,进而避免使用refs。
例如,不要在 Dialog 组件上直接暴露 open() 和 close() 方法,最好传递 isOpen 属性。
4.不要过度使用refs,如果想使用refs来更新组件,推荐做法是“状态提升”。(后续完善)
创建 Refs
使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return ;
}
}
访问refs
当一个 ref 属性被传递给一个 render 函数中的元素时,可以使用 ref 中的 current 属性对节点的引用进行访问。
const node = this.myRef.current;
小结:
function MyFunctionalComponent() {
return ;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// 这将 *不会* 工作!
return (
);
}
}
解决:将它转换为类组件。
备注:你可以在函数式组件内部使用 ref,只要它指向一个 DOM 元素或者 class 组件:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 回调才可以引用它
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
{ textInput = input; }} />
);
}
(1)html元素 --》 将底层dom元素作为它的current属性
(2)自定义类组件 --》该组件已挂载的实例
方法如下:
(1)从父节点访问子节点的DOM节点
点评:因为它会破坏组件的封装,但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。(后续完善)
(2)在子组件上添加Ref.
见:
父组件:
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return (
);
}
}
子组件:
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.focus();
}
render() {
// 告诉 React 我们想把 ref 关联到构造器里创建的 `textInput` 上
return (
);
}
}
点评:不够理想,只能获取组件实例而不是DOM节点。并且还在函数式组件(无状态函数)上无效。
(3)v16.3+,采用ref转发。 Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref。(后续完善)
function logProps(Component) {
// 子组件
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// Assign the custom prop "forwardedRef" as a ref
return ;
}
}
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
//父组件
function forwardRef(props, ref) {
return ;
}
// These next lines are not necessary,
// But they do give the component a better display name in DevTools,
// e.g. "ForwardRef(logProps(MyComponent))"
const name = Component.displayName || Component.name;
forwardRef.displayName = `logProps(${name})`;
return React.forwardRef(forwardRef);
}
(4)v16.2-,将ref作为特殊名字的prop属性,直接传递。
function CustomTextInput(props) {
return (
// 3. 接收
);
}
class Parent extends React.Component {
constructor(props) {
super(props);
// 1.React.createRef() 创建 refs,再将它赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
this.inputElement = React.createRef();
}
render() {
return (
//2. 自定义ref,作为属性传递出去
);
}
}
(5)暴露DOM节点(不推荐)
findDOMNode()
语法:ReactDOM.findDOMNode(component)
使用:当你需要从DOM中读取值时,比如表单的值,或者计算DOM元素的尺寸,这个函数会非常有用。
大多数情况下,你可以添加一个指向DOM节点的引用,从而完全避免使用findDOMNode 这个函数.
当 render 返回 null 或者 false 时, findDOMNode 也返回 null.
备注:
findDOMNode 是用于操作底层DOM节点的备用方案。
findDOMNode 只对挂载过的组件有效。
findDOMNode 不能用于函数式的组件中。
使用:不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问。
特点:更加细致地控制何时 ref 被设置和解除。
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 2.将在组件挂载时将 DOM 元素传入ref 回调函数并调用,当卸载时传入 null 并调用它。
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 3.直接使用原生 API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 渲染后文本框自动获得焦点
this.focusTextInput();
}
render() {
// 1.使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
// 实例上(比如 this.textInput)
return (
);
}
}
备注:
(1)旧版本的this.refs.textInput存在问题,未来可能被移除,建议回调函数代替。
(2)通过将 ref 的回调函数定义成类的绑定函数.
(3)注意
如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。
通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。
import React from 'react';
import ReactDOM from 'react-dom';
class FileInput extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this.ref);
}
change(_ref) {
this.ref = _ref;
}
render() {
return (
{/* this.change(_ref) }>111
改良 */}
{/* this.ref = _ref}>111
内联函数 */}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);