Refs and the DOM
Refs提供了一种方式,允许我们访问DOM节点或者render方法中创建React元素。 在经典的React数据流中,
props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的props来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素。对于这两种情况,React都提供了解决方法。
何时使用Refs
下面几个适合使用refs的情况:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库
避免使用refs来做任何可以可以通过声明式实现来完成的事情。 举个例子,避免在dialog组件里暴露open()和close方法,最好传递isOpen属性。
勿过度使用Refs
你可能首先会想到使用refs在你的app中"让事情发生"。如果是这种情况,请花一点时间,认真在考虑以下state属性应该被那排在那个组件层级中。通常让高的组件层级拥有整个state,是更加恰当的。
创建Refs
Refs是使用React.createRefs创建的,并通过ref属性附加到React元素上。在构造组件时,通常将Refs分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props){
super(props)
this.myRef = React.createRefs();
}
render(){
return <div ref={this.myRef} />
}
}访问Refs
当ref被传递给render中元素时,对该节点的引用可以在ref的current属性中被访问。
const node = this.myRef.current;ref的值根据节点类型而有所不同:
- 当
ref属性用于HTML元素时,构造函数使用React.createRefs创建的ref接受底层DOM元素作为其current属性。 - 当
ref用于自定义class组件时候,ref对象接受组件的挂载实例作为其current属性。 - 你不用在函数组件上使用
ref属性,因为它们没有实例。
React会在组件挂载时给current属性传入DOM元素,并且在组件卸载时传入null值。ref会在componentDidMount和componentDidUpdate生命周期钩子出发前更新。
为class组件增加Ref
如果我们想包装CustomTextInput,来模拟它挂载之后立即被点击的操作。我们可以使用ref来获取整个自定义的input组件并手动调用它的focusTextInput方法:
class AutoFocusTextInput extends React.Component{
constructor(props){
super(props)
this.textInput = React.createRefs()
}
componentDidMount(){
this.textInput.current.focusTextInput()
}
render(){
return <CustomTextInput ref={this.textInput} / >
}
}请注意,这尽在组件声明为class时才有效。
Refs与函数组件
默认情况下。你不能在函数组件上使用ref属性,因为它们没有实例: 如果要在函数组件中使用ref,你可以使用forwardRef(可与useImperativeHandle结合使用),或者可以将改组件转为class组件。 不管怎样,你可以在函数组件内部使用ref,只要它指向一个DOM元素或者class组件。
useRef:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在整个生命周期内保持不变。
forwardRef解决textInput不是dom元素而是组件,转发ref。
const textInput = forwardRef((props,ref)=>{
return <input ref={ref}></input>
})
function TextInputWithFocusButton(){
const inputEl = useRef();
const onButtonClick= () => {
inputEl.current.focus();
}
return (
<>
<TextInput ref={inputEl} />
<button onClick={onButtonClick}>按钮</button>
< />
)
}useImperativeHandle:当我不想把整个子组件暴露给父组件,只是提供父组件需要的方法或者值得时候:
const textInput = forwardRef((props,ref)=>{
const inputEl = useRef();
useImperativeHandle(ref,()=>({
focus:()=>{
inputEl.current.focus()
}
}))
return <input ref="inputEl" />
})- 回调Ref(
useCallback):回调得方式可以把ref得变化通知父组件。
function TextInputWithFocusButton(){
const {value,setValue} = useState("");
const inputEl = useCallback(node=>{
console.log(node)
// 设置state.value
},[])
return (
<>
<div>{value}</div>
子组件:<TextInput ref={inputEl} />
< />
)
}