什么是Refs
Refs提供了一种方式,运许我们访问DOM节点或在render方法中创建的React元素
在典型的React数据流中,props是父组件与子组件交互的唯一方式。要想修改一个子组件,我们需要使用新的props来重新渲染。但是在某些情况下,我们不得不在典型数据流之外去修改一些子组件。被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素。这种情况下我们就必须要考虑其它方式了。好在React为我们提供了解决办法。
什么时候使用Refs
以下几种情况适合使用refs:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库
refs虽然能够让我们去操作DOM元素,但是在实际开发中不要过度使用refs,也就是说能不用refs的就尽量不要使用
Refs的使用
- 创建Refs
Refs 是用React.createRef()来创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class RefComponent extends React.Component{constructor(props){super(props);this.myRef = React.createRef();}render(){return <div ref={this.myRef} />}}class RefComponent extends React.Component{ constructor(props){ super(props); this.myRef = React.createRef(); } render(){ return <div ref={this.myRef} /> } }class RefComponent extends React.Component{ constructor(props){ super(props); this.myRef = React.createRef(); } render(){ return <div ref={this.myRef} /> } }
- 访问Refs
访问ref的方式也很简单,当ref被传递给render中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。也就是说我们创建的ref会有一个current属性,那么我们所要操作的元素就存在这个current中。
ref的值根据节点的类型有所不同:
- 当ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性。
- 当ref属性用于自定义Class组件时,ref对象接收组件的挂载实例作为其current属性。
- 【注意】:我们不能在函数组件上使用ref属性,因为它们没有实例
下面来看两个案例:
- 为DOM元素添加ref
class CustomTextInput extends React.Component {constructor(props) {super(props);//创建一个ref来存储DOM元素this.textInput = React.createRef();}focusTextInput = ()=>{// 直接使用原生 API 使 text 输入框获得焦点// 注意:我们通过 "current" 来访问 DOM 节点this.textInput.current.focus();}render() {// 告诉 React 我们想把 <input> ref 关联到// 构造器里创建的 `textInput` 上return (<div><inputtype="text"ref={this.textInput} /><inputtype="button"value="Focus the text input"onClick={this.focusTextInput}/></div>);}}class CustomTextInput extends React.Component { constructor(props) { super(props); //创建一个ref来存储DOM元素 this.textInput = React.createRef(); } focusTextInput = ()=>{ // 直接使用原生 API 使 text 输入框获得焦点 // 注意:我们通过 "current" 来访问 DOM 节点 this.textInput.current.focus(); } render() { // 告诉 React 我们想把 <input> ref 关联到 // 构造器里创建的 `textInput` 上 return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }class CustomTextInput extends React.Component { constructor(props) { super(props); //创建一个ref来存储DOM元素 this.textInput = React.createRef(); } focusTextInput = ()=>{ // 直接使用原生 API 使 text 输入框获得焦点 // 注意:我们通过 "current" 来访问 DOM 节点 this.textInput.current.focus(); } render() { // 告诉 React 我们想把 <input> ref 关联到 // 构造器里创建的 `textInput` 上 return ( <div> <input type="text" ref={this.textInput} /> <input type="button" value="Focus the text input" onClick={this.focusTextInput} /> </div> ); } }
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.createRef();}componentDidMount() {this.textInput.current.focusTextInput();}render() {return (<CustomTextInput ref={this.textInput} />);}}class CustomTextInput extends React.Component {// ...}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 CustomTextInput extends React.Component { // ... }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 CustomTextInput extends React.Component { // ... }
需要注意的是:仅在 CustomTextInput 声明为 class 时才有效
Refs 与函数组件
默认情况下,我们不能在函数组件上使用 ref 属性,因为它们没有实例。也就是说我们自己定义的函数组件,当别人引用它并且想使用ref属性,这种是无效的。
如果非要在函数组件中使用 ref,可以使用 forwardRef,或者将该组件转化为 class 组件。
function MyFunctionComponent() {return <input />;}class App extends React.Component{constructor(props){super(porps);this.textInput = React.createRef();}render(){//ref是无效的return <MyFunctionComponent ref={this.textInput} />}}function MyFunctionComponent() { return <input />; } class App extends React.Component{ constructor(props){ super(porps); this.textInput = React.createRef(); } render(){ //ref是无效的 return <MyFunctionComponent ref={this.textInput} /> } }function MyFunctionComponent() { return <input />; } class App extends React.Component{ constructor(props){ super(porps); this.textInput = React.createRef(); } render(){ //ref是无效的 return <MyFunctionComponent ref={this.textInput} /> } }
虽然不能在函数组件上使用ref,但是我们可以在函数组件内部使用ref,需要借助useRef() 钩子函数
function CustomTextInput(props) {// 这里必须声明 textInput,这样 ref 才可以引用它const textInput = useRef(null);function handleClick() {textInput.current.focus();}return (<div><inputtype="text"ref={textInput} /><inputtype="button"value="Focus the text input"onClick={handleClick}/></div>);}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> ); }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> ); }
-
Refs转发
Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。
下面的示例中,MyButton使用React.forwardRef来获取传递给它的ref,然后转发到它渲染的DOM button上。这样使用MyButton的组件可以获取到底层DOM节点button的ref,并在必要时访问,就像直接使用DOM button一样。
const MyButton = React.forwardRef((props, ref) =>{(<button ref={ref} className="my-button">{props.children}</button>)});//可以直接获取DOM button的refconst ref = React.createRef();<MyButton ref={ref}>click me</MyButton>const MyButton = React.forwardRef((props, ref) =>{ ( <button ref={ref} className="my-button">{props.children}</button> ) }); //可以直接获取DOM button的ref const ref = React.createRef(); <MyButton ref={ref}>click me</MyButton>const MyButton = React.forwardRef((props, ref) =>{ ( <button ref={ref} className="my-button">{props.children}</button> ) }); //可以直接获取DOM button的ref const ref = React.createRef(); <MyButton ref={ref}>click me</MyButton>
-上面说过,在函数组件上是不能使用ref的,那么这里却使用了ref那么是不是自相矛盾了呢?其实并不矛盾,细想:当我们在类组件或原生DOM元素上使用ref属性时,那么这个ref是直接与类组件或DOM元素绑定的,也就是说我们可以通过ref就能直接操作组件或者DOM元素。
而在这个示例中我们在函数组件上使用了ref,但不同的是:这个ref绑定的并不是当前函数组件本身,而是函数组件的子元素button
下面简单简单描述一下上面案例的执行步骤:
- 1、我们通过调用React.createRef()创建了一个ref并将其赋值给变量ref
- 2、指定ref为JSX属性,通过MyButton组件将其向下传递
- 3、React 将ref作为参数传递给forwardRef函数,再通过该函数将其向下传递
- 4、然后向下转发该ref参数到button上,同时将其指定为button的JSX属性
- 5、当 ref 挂载完成,ref.current 将指向 < button> DOM 节点。
- 6、最后我们就可以在外部直接访问到组件内部的元素了