一般情况在一个正常的dataflow中,父组件总是将data通过props传给子组件,然后子组件re-render。但是有的时候我们想立即修改一个react子组件或一个DOM,那该怎么办呢?
对的,就是用refs
refs提供了一个DOM节点或react组件实例的引用,这样我们在组件中就能访问到那个DOM节点或react组件实例
什么时候用refs
- 处理focus,text 选择,或者视频回放
- 触发交互式动画
- 整合第三方DOM库
不要使用ref去做可以声名的事情。比方说不要让Dialog组件暴露open()和close()方法,而是使用props.isOpen去管理Dialog
创建refs
用React.createRef()创建refs的,然后通过ref属附加给一个React元素或者DOM。在构造组件时,通常将Refs分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
访问refs
在生命周期方法或者事件处理器中可以使用 current 属性访问ref
const node = this.myRef.current
- 如果 ref 是给HTML元素用的,那么this.myRef.current 就是一个DOM
- 如果ref 是给react组件用的,那么this.myRef.current 就是当前组件的实例
- ref 不能用在函数式组件中,因为函数式组件没有实例
给DOM加ref
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 1. 首先创建ref
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 3. 最后,我们在事件处理器中就能使用原生的DOM方法了
this.textInput.current.focus();
}
render() {
// 2. 然后,告诉react我们希望把构造器里定义的textInput 指向 <input>
return (
<div>
<input
type="text"
ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
- 组件mount的时候current会被赋值为DOM,当然了还没mount的时候是null。
- ref会在
componentDidMountorcomponentDidUpdate之前更新。
给class组件加ref
比方说我们想要一个AutoFocusTextInput组件,这个组件就是在mount之后调一下CustomTextInput的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} />
);
}
}
注意CustomTextInput 必须是class型组件
给function组件加ref
render的时候不能给一个function组件加ref
function MyFunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// This will *not* work!
return (
<MyFunctionComponent ref={this.textInput} />
);
}
}
但是一个function组件内部还是能用ref的,只要这个ref不是attach到function组件的
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
暴露DOM refs给父组件
想不想在父组件里面访问一个子DOM节点呢?
不,你不想。
因为这破坏了组件的封装。
但有些时候还是可以用的,比如说触发一个focus或者测量DOM节点的大小和属性。
上面给class组件加ref的例子,有的时候也不是最理想的,因为这个时候返给我们的是一个react组件,而不是一个DOM。
forward refs能让组件选择暴露哪些子组件的refs供自己用。
建议不要暴露DOM节点,但它可以是一个escape hatch。这个方法要向子组件添加一些代码。然后如果我们完全无法控制子组件实现,最后一个选项是使用findDOMNode(),但在StrictMode中是不能用的。
回调refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// Focus the text input using the raw DOM API
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// autofocus the input on mount
this.focusTextInput();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
ref不仅可以使用React.createRef()创建,还可以使用一种叫回调ref的方法实现。我们给render函数里的dom加上一个回调ref,如上面的this.setTextInputRef。在挂载组件的时候react会调用回调ref,然后这个回调的参数会被react认为是当前的dom。这上面的例子中,回调ref,也就是this.setTextInputRef将这个dom的引用赋给了this.textInput。
组件之间可以传回调refs。
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
Parent给 inputRef prop 赋了一个回调,然后传给CustomTextInput,完了CustomTextInput又把这个回调给了input当做一个回调refs。这样的结果是啥呢?是Parent里头的this.inputElement会赋值为一个DOM,而这个DOM恰恰就是CustomTextInput里头的input
有回调refs的注意事项
ref 回调定义为内联函数的话更新的时候会被调用2次,1次refs是null,1次是DOM。这是因为每次渲染都会创建一个新的函数实例,所以react需要先清除旧的ref然后再设置新的ref。
可以把ref callback给bind到class上,不过大多数情况下这个事影响不大