在常规的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改子元素,你需要用新的 props 去重新渲染子元素。然而,在少数情况下,你需要在常规数据流外(冲出 React 虚拟 DOM 的限制)强制修改子元素(被修改的子元素可以是 React 组件实例,或者是一个 DOM 元素),在这种情况下,React 提供了Refs来解决。
简单说就是:
在从 render 方法中返回 UI 结构之后,你可能想冲出 React 虚拟 DOM 的限制,在 render 返回的组件实例上调用某些方法,此时用Refs就好。
下面有一些正好使用 refs 的场景:
如果可以通过声明式实现,就尽量避免使用 refs 。
注意:不要过度使用 Refs。应用中可以用state更新组件,就不要用Refs.
React 支持给任何组件添加特殊属性(ref)。ref 属性接受回调函数,并且当组件 装载(mounted) 或者 卸载(unmounted) 之后,回调函数会立即执行。
使用:ref={()=>{}}
例:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.state = {text:''};
this.handler = this.handler.bind(this);
}
handler(e){
this.setState({text:e.target.value});
}
render() {
return (
<div>
"text"
value={this.state.text}
onChange={this.handler}
ref={() => { alert(this.state.text)}} />
div>
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
执行过程:
(1)首先页面什么都没有,只有一个alert框,框的内容还是空的。
说明:ref在componentDidMount前会调用一次。
(2)点击确定后出现text框。
说明ref调用完后进行了渲染。
(3)当在text框里输入个’s’后,’s’并没有直接渲染出来,而是先执行了ref回调函数,alert弹出输入的值’s’。
说明:ref在componentDidUpdate前会调用一次
。因为输入s后会使组件再次更新,在组件更新前首先会进行卸载(unmounted)。
(4)点击alert的确定后,再次弹出了一样的alert
说明:ref在componentDidMount前会调用一次。
(5)再次点击alert的确定后,界面才渲染出’s’.
说明ref调用完后进行了再次渲染。
当给 HTML 元素添加 ref 属性时,ref 回调函数收到的参数是 DOM 元素的引用。
例:用ref 回调来存储 DOM 节点的引用。
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
// 通过使用原生API,显式地聚焦text输入框
this.textInput.focus();
console.log(this.textInput);//
}
render() {
// 用`ref`回调函数来存储 输入框的DOM节点的引用(例如:this.textInput)
return (
<div>
"text"
ref={(input) => { this.textInput = input; }} />
"button"
value="Focus the text input"
onClick={this.focus}
/>
div>
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
React 组件在加载时将 DOM 元素传入 ref 的回调函数的形参里
,在卸载时则会传入 null
。在componentDidMount 或 componentDidUpdate 这些生命周期回调之前执行 ref 回调。
执行结果:
点击button后,控制台输出this.textInput的值,即ref参数的值:
< input type=’text’/>
当给 类组件 添加 ref 属性时, ref 回调函数收到的参数是装载(mounted)的组件实例。
例:
class CustomTextInput extends React.Component {
render() {
return (
<div>div>
);
}
}
class AutoFocusTextInput extends React.Component {
componentDidMount() {
console.log('AutoFocusTextInput',this.textInput);
}
render() {
return (
{ this.textInput = input; }} />
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
你不能在函数式组件上使用 ref 属性
,因为它们没有实例:
function MyFunctionalComponent() {
return ;
}
class Parent extends React.Component {
render() {
// 这里 *不会* 执行!
return (
{ this.textInput = input; }} />
);
}
}
如果你需要使用 ref ,你需要将组件转化成 类(class)组件,就像需要 生命周期方法 或者 state 一样。
然而你可以 在函数式组件内部使用 ref 来引用一个 DOM 元素或者 类(class)组件:
function CustomTextInput(props) {
// textInput必须在这里声明,所以 ref 回调可以引用它
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
type="text"
ref={(input) => { textInput = input; }} />
type="button"
value="Focus the text input"
onClick={handleClick}
/>
);
}
在极少数情况下,你可能希望从父组件访问子节点的 DOM 节点。通常不建议这样做,因为它会破坏组件的封装,但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然你可以向子组件添加 ref,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数式组件上无效。
其实,我们可以在子组件的DOM节点上添加 ref 属性,然后让父组件通过props传递一个回调函数 给子组件的DOM节点
,就OK了.
例:
function CustomTextInput(props) {
return (
);
//此时相当于
// this.inputElement = el} />
}
class Parent extends React.Component {
render() {
return (
this.inputElement = el}
/>
);
}
}
在上面的例子中,Parent 将它的 ref 回调作为一个特殊的 inputRef 传递给 CustomTextInput,然后 CustomTextInput 通过 ref 属性将其传递给 < input>。最终,Parent 中的 this.inputElement 将被设置为与 CustomTextInput 中的 < input> 元素相对应的 DOM 节点。
请注意,上述示例中的 inputRef 属性没有特殊的含义,它只是一般的组件属性。
这种模式的另一个好处是它能作用很深。假如有个 Parent 组件不需要 DOM 节点 A,但是某个渲染 Parent 的组件(我们称之为 Grandparent)需要通过它访问。这时我们可以让 Grandparent 传递 inputRef 给 Parent 组件,然后让 Parent 组件将其转发给 CustomTextInput:
function CustomTextInput(props) {
return (
);
//此时相当于
// this.inputElement = el} />
}
function Parent(props) {
return (
My input:
);
}
class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
/>
);
}
}
上面的例子中,Grandparent 首先指定了 ref 回调函数。它通过一个常规的 inputRef 属性被传递到 Parent,Parent 也同样把它传递给了 CustomTextInput。最后 CustomTextInput 读取了 inputRef 属性并将传递的函数作为 ref 属性附加到 < input>。最终,Grandparent 中的 this.inputElement 被设置为 CustomTextInput 的 input 对应的 DOM 节点。
总而言之,我们建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。请注意,此方法要求您向子组件添加一些代码,如果你无法完全控制子组件,最后的办法是使用 findDOMNode(),但是不推荐这样做。