refs提供了一种方法,用于访问在render中创建的dom节点或者React元素。
在典型的React数据流中,属性(props)
是父组件与子组件交互的唯一方式。要修改子组件,你需要使用新的props重新渲染他。
但是某些情况下,你需要在典型的数据流外,强制修改子组件。要修改的子组件可以是React的实例,也可以是DOM元素,对于这两种情况,React提供了解决办法。
何时使用Refs
①处理焦点,文本选择或者媒体控制
②触发强制动画
③集成第三方的DOM库
如果可以通过声明方式实现,尽量避免使用Refs
例如,不要在 Dialog
组件上直接暴露 open()
和 close()
方法,最好传递 isOpen
属性。
不要过度使用Refs
你可能首先会想到在应用中使用Refs
来更新组件,如果是这种情况,思考一下,state
属性在组件层级中的位置,通常你会想明白,状态提升:提升组件state
所在层级会是更加合适的办法。有关示例,请参考状态提升.
Note
下面的例子已经用 React v16.3 引入的 React.createRef()
API 更新。如果你正在使用 React 更早的发布版,我们推荐使用回调形式的 refs。
创建Refs
①使用React.creatRef()来创建refs,通过ref属性来获得React元素。
②当构造组件时,refs通常被赋值给组件的一个属性。这样可以在组件的任意一处使用他。
class MyComponent extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
render(){
return <div ref={this.myRef}></div>
}
}
访问refs
当一个ref
属性被传递给一个render()
函数中的元素时,可以使用ref
中的current
属性对节点的引用进行访问。
const current = this.myRef.current;
ref
的值取决于节点类型
①当 ref
属性被用于一个普通的 HTML 元素时,React.createRef()
将接收底层 DOM 元素作为它的 current
属性以创建 ref
。
②当 ref
被用于一个自定义的类组件的时候,ref
对象将接收该组件已挂载的实例,作为他的current
。
③我们不能在函数形式的组件上面使用ref
,因为他们是没有实例的。
demo
以下的代码存储了ref
属性对节点的引用
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 我们把 input ref 关联到构造器里面创建的 textInput 上
return(
<div>
<input type="text" ref={this.textInput} />
<input type="text" type="button" value="focus ths text"
onClick={this.focusTextInput} />
</div>
)
}
}
ReactDOM.render(
<CustomTextInput />,document.getElementById('root')
)
React会在组件加载时将DOM元素传入current
属性,在组件卸载的时候回改成null
,ref
的更新会发生在componentDidMounted
和componentDidUpdate
的生命周期钩子之前。
为类组件添加ref
如果我们想要包装上面的CustomTextInput
,来模拟挂载之后立即被点击的话,我们可以使用ref
来访问自定义输入,并且手动调用他们的focusTextInput
方法。
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
需要注意的是,这种方法只对于使用class
声明的组件有效。
Refs与函数式组件
我们不能在函数式组件上面使用ref
属性,因为在函数式组件上面没有他们的实例。
如果你想使用ref
或者state
以及其他声明周期方法的时候,就需要将函数式组件转化成class
声明的组件。
注意:我们可以在函数式组件内部使用ref
,只要他指向一个DOM或者class
组件。
function CustomTextInput(props){
//这里必须声明 textInput,这样ref的回调才能够引用它
let textInput = null;
function handleClick(){
textInput.focus();
}
return (
<div>
<input type="text" ref={(input)=>{textInput=input}}/>
<input type="button" value="hello" onClick={handleClick} />
</div>
)
}
对父组件暴露DOM节点
在极少数的情况下, 你可能希望通过父组件访问子组件的节点。通常是不推荐这样做的,因为这样会破坏组件的封装。但是偶尔也可以用于触发焦点后者测量子节点dom大小或者位置。
虽然你可以向子组件添加 ref,但是这不是一个理想的解决方案,因为只能获取组件实例,而不是dom节点,并且这方法在函数式组件上是无效的。
如果你使用 React 16.3 或更高, 这种情况下我们推荐使用 ref 转发。
Ref转发可以让组件像暴露自己的ref一样暴露子组件的ref,关于怎样对父组件暴露子组件的 DOM 节点,在 ref 转发文档 中有一个详细的例子。
如果你使用 React 16.2 或更低,或者你需要比 ref 转发更高的灵活性,你可以使用 这个替代方案 将 ref 作为特殊名字的 prop 直接传递。
可能的话,我们不建议暴露 DOM 节点,但有时候它会成为救命稻草。注意这些方案需要你在子组件中增加一些代码。如果你对子组件的实现没有控制权的话,你剩下的选择是使用 findDOMNode()
,但是不推荐。
回调refs
React也支持另一种设置ref
的方式,称为回调ref
,这可以更加细致的控制什么时候设置和移除 ref
。
这不同于创建createRef
创建的ref
属性,“回调 ref”会传递一个函数,这个函数接收React组件的实例,或者html中的dom元素作为参数,来存储他们以便于被其他地方来访问。
demo:
使用ref
回调函数,在实例的属性中,存储对dom节点的应用。
class CustomTextInput extends React.Component{
constructor(props){
super(props);
this.textInput = null;
this.setTextInputRef=element=>{
this.textInput=element;
}
this.focusTextInput=()=>{
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 test onClick={this.focusTextInput} />
</div>
)
}
}
React将在组件挂载时,将dom元素传入ref
的回调函数并且调用,在卸载的时候传入null
并且调用他。
ref
的回调函数会在componentDidMount
和componentDidUpdate
的生命周期函数被调用之前。
我们可以在组件之间传递回调形式的refs
,就像你可以传递React.createRef()
创建的refs
对象一样。
function CustomTextInput(props){
return(
<div>
<input type="text" ref={props.inputRef}/>
</div>
)
}
class Parent extends React.Component{
render(){
return(
<CustomTextInput inputRef={el=>this.inputElement=el} />
)
}
}
在上面的例子中,Parent 传递给它的 ref 回调函数作为 inputRef 传递给 CustomTextInput,然后 CustomTextInput 通过 ref属性将其传递给 <input>。最终,Parent 中的 this.inputElement 将被设置为与 CustomTextIput 中的 <input> 元素相对应的 DOM 节点
注意
如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。