React 提供了 refs 用于访问 DOM 节点或在 render 方法中创建的 React 元素。尽管在 React 单向数据流中, props 是父子组件交互的唯一方式。但是,某些特殊情况下仍需要在典型数据流外强制修改子组件或是 DOM 元素。
Refs 不能用在无状态组件中
- 处理焦点、文本选择或媒体控制
- 触发强制动画
- 集成第三方 DOM 库(AMap、Google Map)
**不要过度使用 Refs。**实际上可以使用 Refs 很“方便”的去处理一些问题,例如通过 Refs 更新子组件的状态,但是提升 state 所在的组件层级或许会是更合适的做法。
如可以通过声明式实现,则尽量避免使用 Refs
目前有三种方式能够创建 Refs,通过不同方式创建的 Refs 访问方式也不同。
React.createRefs()
API- 回调 Refs
- 字符串类型的 Refs
React.createRefs 创建的 Ref 在
componentDidMount()
之前的值均是object
只不过该对象的 current 属性此时为null
,其余两种则是undefined
React v16.3.0 新增了React.createRefs()
用于创建 Refs。当一个 ref 属性被传递给一个render
函数中的元素时,可以使用 ref 中的current
属性对节点的引用进行访问。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentWillMount() {
console.log({ ...this.refGenerateByAPI}); // { current: null }
}
componentDidMount() {
this.myRef.current.focus();
}
render() {
return (
<input ref={this.myRef} type="text" />
)
}
}
传入回调函数,并绑定到当前实例上。回调函数传入的值就是子元素的实例,可以直接访问。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this._bindRef = this._bindRef.bind(this);
}
_bindRef(input) {
this.myRef = input;
}
componentWillMount() {
console.log(this.myRef); // undefined
}
componentDidMount() {
this.myRef.focus();
}
render() {
return (
<input ref={this._bindRef} type="text" />
)
}
}
如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。
字符串类型的 Refs 是,最早的使用 refs 的方式,由于 String 类型的 refs 存在某些问题,这种使用方式非常有可能在未来的版本中被废弃。
通过这种方式声明的 Refs 最终能通过组件实例的refs
属性的{string}
属性进行访问,
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.refs.myRef); // undefined
}
componentDidMount() {
this.refs.myRef.focus();
}
render() {
return (
<input ref="myRef" type="text" />
)
}
}
假设我们有一组件 A 经过context.Consumer
之类的高阶组件或是 withRouter
之类的函数封装成了 B,此时又希望能够使用 B 组件的时候,能够操作 A 的实例或对应的 DOM。
比如,现有一组件实现如下,并且希望对 ThemeInput 的实例进行 focus 的操作
function ThemeInput(props) {
return (
<input className="theme-input" />
);
}
这种情况下,可以使用 React v16.3.0 推出的新 API —— React.forwardRefs(fn) 重新实现 ThemeInput
const ThemeInput = React.forwardRef((props, ref) => (
<input ref={ref} className="theme-input" />
));
class XXXForm extends React.Component {
consturctor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
this.myRef.current.focus();
}
render() {
return (
<ThemeInput ref={this.myRef} />
);
}
}
上述示例具体行为:
- 我们通过调用 React.createRef 创建一个 React ref 并将其分配给一个 ref 变量。
- 通过将它指定为 JSX 属性,我们将我们的 ref 传递给
<ThemeInput ref = {ref}>
。 - React 将 ref 传递给 forwardRef 中的
(props,ref) => {}
函数作为第二个参数。 - 我们通过指定它作为一个JSX属性,将这个ref参数转发给
<input ref = {ref}>
。 - 当ref被附加时,ref.current 将指向
<input>
DOM节点。
第二个参数只有在您使用 React.forwardRef 调用定义组件时才存在。 常规的功能或类组件不会收到参数参数,并且参考也不可用于道具中。
引用转发不限于DOM组件。 您也可以将引用转发给类组件实例。
forwardRef 在 HOC 中的应用的 React 官方示例,通过额外的 forwardedRef
属性结合 React.forwardedRef()
的方式,更加轻易的实现传递 refs 的功能
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;
return <Component ref={forwardedRef} {...rest} />;
}
}
return React.forwardRef((props, ref) => (
<LogProps {...props} forwardedRef={ref} />
));
}