作为一名初学者, 我遇到了一个小问题, 父组件如何获取子组件的属性和方法呢?
如下图所示, div
是input
和button
的父组件, 我想要实现的功能特别简单, 那就是在点击button
按钮的时候, 可以打印出input
输入框内的内容
该过程可以简单归结为下:
- 在
input
内输入内容 - 点击
button
调用父组件div
的方法 div
从input
内取到内容,然后打印出来
第 1,2 步比较简单,我们使用props
向button
传递一个回调函数即可
function App() {
const showMess = () => {
console.log("click button success");
}
return (
<div>
<input placeholder='Please input message' className='input'/>
<br />
<br />
<button onClick={showMess} className='button'>Click to show message</button>
</div>
);
}
export default App;
如图所示, 当我们点击button
后, 控制台成功打印出了消息, 表示我们 1,2 步已经完成. 但是, div
这个父组件要如何获取input
内输入的内容呢? 这就需要用到**ref
**了
1. 什么是Ref
在React中文文档中, 对于ref
是这么解释的:
Refs 提供了一种方式, 允许我们访问DOM节点或在render方法中创建的React元素
而在最新的官方文档中,对于ref
的定义如下:
When you want to a component to “remember” some information, but you don’t want that information to trigger new renders(触发新的渲染), you can use a ref
2. 如何使用Ref
获取属性
最新的React文档中, 对于ref
的使用, 已经全部更新为hook
的形式, 但是, 对类组件中ref
的使用, 还是有必要进行学习的
2.1 类组件中Ref
的使用
在类组件中,使用ref
有三种方式:
- 使用字符串形式,及
ref="string"
, 但是该方式已经被弃用 - 使用一个回调函数
- 使用
createRef
但在这之前, 我们首先将刚才的代码修改为一个类组件
- AppClass.js:
import * as React from "react";
import './App.css'
export default class AppClass extends React.Component {
constructor(props) {
super(props);
this.showMess = this.showMess.bind(this);
}
showMess () {
console.log("click button success");
}
render() {
return (
<div>
<input className='input'/>
<br />
<br />
<button onClick={this.showMess} className="button">
Click to show message
</button>
</div>
);
}
}
2.1.1 使用回调函数
使用回调函数的形式如下:
<input ref={currentNode => this.info = currentNode} className='input'/>
其中, 通过回调函数, 其实相当于将input
这个节点和this.info
进行了绑定, 之后, 就可以使用this.info
来操作当前节点
让我们在button
回调函数中添加一个方法
showMess () {
console.log("click button success");
console.log(this.info.alue);
}
从下图可以看到, 已经成功获取了
2.1.2 使用React.createRef()
使用React.createRef()
的形式如下:
constructor(props) {
super(props);
this.showMess = this.showMess.bind(this);
this.info = React.createRef();
}
showMess () {
console.log("click button success");
console.log(this.info.value);
}
但此时, 我们却发现, 控制台打印的并不是input
内的值, 而是一个undefined
这是为什么呢? 让我们把this.info
打印出来看一下
我们发现, input
被放在了this.info
的current
属性中! 所以, 修改代码如下:
constructor(props) {
super(props);
this.showMess = this.showMess.bind(this);
this.info = React.createRef();
}
showMess () {
console.log("click button success");
// 注意这里修改为了 this.info.current.value
console.log(this.info.current.value);
}
再试一次, 可以成功打印我们需要的信息了(节省篇幅,不再放结果图片了)
2.2 函数组件中Ref
的使用
HOOK为我们提供了useRef()
这个钩子, 但是, 我们也应该知道, 函数组件中同样可以使用类组件中的两个方法(虽然不推荐)
2.2.1 使用回调函数
function App() {
let info;
const showMess = () => {
console.log("click button success");
console.log(info.value);
}
return (
<div>
<input ref={currentNode => info = currentNode} className='input'/>
<br />
<br />
<button onClick={showMess} className='button'>Click to show message</button>
</div>
);
}
export default App;
2.2.2 使用React.createRef()
import React from 'react';
import './App.css';
function App() {
let info = React.createRef(); // 不要忘记 import React
const showMess = () => {
console.log("click button success");
console.log(info.current.value); // 记得这里要从 current 获取数据
}
return (
<div>
<input ref={info} className='input'/>
<br />
<br />
<button onClick={showMess} className='button'>Click to show message</button>
</div>
);
}
export default App;
2.2.3 使用useRef()
useRef()
和React.createRef
一样, 都是将DOM节点存储在current
属性内
import { useRef } from 'react';
import './App.css';
function App() {
let info = useRef(null);
const showMess = () => {
console.log('Clicked');
console.log(info.current.value);
}
return (
<div >
<input ref={info} placeholder='Please input your message' className='input' />
<br />
<br />
<button onClick={showMess} className='button'>Click to print message</button>
</div>
);
}
export default App;
3. 如何使用Ref
调用子组件方法
上面, 我们已经成功地使用ref
来获取子组件的属性了, 但是, 我们要如何调用子组件的属性呢? 其实, 和使用属性的方法差不多. 下面将通过useRef()
这方式来调用子组件的方法. (函数组件不能使用ref
, React.createRef
不能使用, 除此之外, useRef
的性能也更好)
如下图所示, 我们在input
和button
下方, 新建一个Child
组件
Child
组件中, 有一个func
方法, 可以打印一句话
import React from 'react'
export default function Child() {
const func = () => {
console.log("This is Child's function");
}
return (
<div style={{textAlign: 'center'}}>
I am Child.
</div>
)
}
3.1 通过useRef()
调用子组件方法
我们在Child
这个子组件中, 添加ref
, 并在App
中使用child.current.func()
调用其中的方法
function App() {
let info = React.createRef();
let child = useRef(null);
const showMess = () => {
console.log('Clicked');
child.current.func();
}
return (
<div >
<input ref={info} placeholder='Please input your message' className='input' />
<br />
<br />
<button onClick={showMess} className='button'>Click to print message</button>
<br />
<br />
<Child ref={child} />
</div>
);
}
export default App;
但是当我们打开网页和控制台, 却发现如下报错: warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
这个报错在前面提示我们:函数组件不能被赋予refs
, 而在最后提示我们: Did you mean to use React.forwardRef()?, 让我们去搜索一下这个React.forwardRef()
, 在官方文档中, 是这么介绍它的:
forwardRef
lets your component expose a DOM node to parent component with aref
可以知道, 这个React.forwardRef()
的作用, 就是让子组件暴露给父组件, 所以, 我们对子组件的代码进行修改
import React, { forwardRef, useImperativeHandle } from 'react'
const Child = forwardRef(function Child(props, ref) {
const func = () => {
console.log("This is child's function")
}
return (
<div style={{ textAlign: 'center' }} ref={ref}>
I am Child.
</div>
)
})
export default Child;
但是, 当我们在浏览器中打印child.current
后, 可以发现, 此时, 返回的是Child
中div
这个DOM元素
可以知道, 如果我们想要返回子组件中某一个DOM元素, 可以用上面的方法, 但我们现在是想调用子组件的某个方法, 此时这种方式就不行了, 需要用到另一个函数: useImperativeHandle()
import React, { forwardRef, useImperativeHandle } from 'react'
const Child = forwardRef(function Child(props, ref) {
useImperativeHandle(ref, () => {
return {
func() {
console.log("This is Child's function");
}
}
})
return (
<div style={{ textAlign: 'center' }} >
I am Child.
</div>
)
})
export default Child;
再次打开浏览器, 可以看到, 此时child.current
已经返回的是func
这个函数, 而该方法也已经成功执行
useImperativeHandle()
函数的用法如下:
useImperative(ref, () => { // ref 即为子组件传递进的ref
return {} // return 的是一个对象
})