接下来我们要学习最后一个属性ref
了,官网是这样描述的:
React
支持一个特殊的、可以附加到任何组件上的ref
属性。此属性可以是一个由React.createRef()
函数创建的对象、或者一个回调函数、或者一个字符串(遗留API
)。当ref
属性是一个回调函数时,此函数会(根据元素的类型)接收底层DOM
元素或class
实例作为其参数。这能够让你直接访问DOM
元素或组件实例。谨慎使用
ref
。如果你发现自己经常使用ref
来在应用中“实现想要的功能”,你可以考虑去了解一下自上而下的数据流。
我们写一个获取输入框内容的案例,来说明ref的使用。
<!-- 准备好员工“容器” -->
<div id="app"></div>
<!-- 引入ReactJS核心库 -->
<script type="text/javascript" src="../JS/react.development.js"></script>
<!-- 引入React-DOM核心库,用于操作DOM -->
<script type="text/javascript" src="../JS/react-dom.development.js"></script>
<!-- 引入Babel,用于编译jsx为js -->
<script type="text/javascript" src="../JS/babel.min.js"></script>
<!-- 此处类型为babel -->
<script type="text/babel">
class Demo extends React.Component {
render () {
return (
<div>
<input type="text" placeholder="点击按钮获取值" />
<button >click获取值</button>
<input type="text" placeholder="失去焦点获取值" />
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
</script>
我们要对以上组件实现2个功能:
id
获取元素节点,拿到其值class Demo extends React.Component {
// 获取第一个输入框的值
getInput1 = () => {
const input1 = document.getElementById('input1')
console.log(input1.value)
}
// 获取第二个输入框的值
getInput2 = () => {
const input2 = document.getElementById('input2')
console.log(input2.value)
}
render () {
return (
<div>
<input id="input1" type="text" placeholder="点击按钮获取值" />
<button onClick={this.getInput1}>click获取值</button>
<input id="input2" onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,我们是通过传统的获取元素id
来拿到值,功能可以实现,但是在react
中我们就要使用react
的方法。
class Demo extends React.Component {
// 获取第一个输入框的值
getInput1 = () => {
const input1 = this.refs.input1
console.log(input1.value)
}
// 获取第二个输入框的值
getInput2 = () => {
const input2 = this.refs.input2
console.log(input2.value)
}
render () {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮获取值" />
<button onClick={this.getInput1}>click获取值</button>
<input ref="input2" onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,我们是通过ref
标识元素节点来获取输入框的值,功能可以实现,且代码比传统方法简洁。
注意:在React
中,字符串形式的ref
已经被废弃,不再推荐使用。字符串形式的ref
是一种在React
早期版本中使用的方式
class Demo extends React.Component {
// 获取第一个输入框的值
getInput1 = () => {
const {input1} = this
console.log(input1.value)
}
// 获取第二个输入框的值
getInput2 = () => {
const {input2} = this
console.log(input2.value)
}
render () {
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮获取值" />
<button onClick={this.getInput1}>click获取值</button>
<input ref={c => this.input2 = c} onBlur={this.getInput2} type="text" placeholder="失去焦点获取值" />
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,我们使用的是箭头函数作为回调函数赋值给ref
,箭头函数本身没有this
,这里的this
其实是组件实例本身,我们这里回调函数获取到的参数其实就是ref
标识的这个节点元素,我们将当前节点赋值给这个组件实例。功能同样可以实现。
关于回调 refs 的说明
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
以上是react
官方对内联函数的说明。
我们以代码来重现问题:
class Demo extends React.Component {
// 获取第一个输入框的值
getInput1 = () => {
const {input1} = this
console.log(input1.value)
}
// 初始化状态
state = {isHot:true}
// 切换天气
changeWeather = () => {
const {isHot} = this.state
this.setState({isHot:!isHot})
}
render () {
const {isHot} = this.state
return (
<div>
<h1>今天天气很{isHot?'炎热':'凉爽'}</h1>
<input ref={(c)=>{this.input1 = c; console.log('@',c);}} type="text" />
<button onClick={this.getInput1}>click获取值</button>
<button onClick={this.changeWeather}>修改状态</button>
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,我们在内联函数中加了一个打印日志语句,初始化组件时我们的内联函数会执行一次,但是当我们切换天气更新组件状态时,内联函数会执行两次:
@ null
@ <input type="text" />
这是为什么呢?
这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
根据官方的解释,是因为我们更新组件状态时,react发现我们这个是一个内联函数,调用第一次的时候清空旧的实例,所以我们第一次打印的是null,清空以后设置了一个新的实例,所以我们第二次拿到的就是新的实例。
使用class
绑定的回调函数给ref
class Demo extends React.Component {
// 获取第一个输入框的值
getInput1 = () => {
const {input1} = this
console.log(input1.value)
}
// 初始化状态
state = {isHot:true}
// 切换天气
changeWeather = () => {
const {isHot} = this.state
this.setState({isHot:!isHot})
}
// 绑定ref的回调函数
inputRef = (c) => {
this.input1 = c;
console.log('@',c)
}
render () {
const {isHot} = this.state
return (
<div>
<h1>今天天气很{isHot?'炎热':'凉爽'}</h1>
{/*{this.input1 = c; console.log('@',c);}} type="text" />*/}
<input ref={this.inputRef} type="text"/>
<button onClick={this.getInput1}>click获取值</button>
<button onClick={this.changeWeather}>修改状态</button>
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,我们使用class
绑定的函数给ref
进行回调,当我们进行状态更新的时候,其打印语句只执行一次。
Refs 是使用
React.createRef()
创建的,并通过ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
createRef
是React 16.3
引入的一个API
,用于创建一个ref
对象,可以将其赋值给组件的ref
属性,从而可以在组件外部访问组件内部的DOM
节点或组件实例。
class Demo extends React.Component {
myRef1 = React.createRef()
myRef2 = React.createRef()
getData = () => {
const val1 = this.myRef1.current.value
console.log(val1)
}
blurData = () => {
const val2 = this.myRef2.current.value
console.log(val2)
}
render () {
return (
<div>
<input ref={this.myRef1} type="text"/>
<button onClick={this.getData}>click</button>
<input ref={this.myRef2} onBlur={this.blurData} type="text"/>
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Demo />,document.getElementById('app'))
以上代码,ref
赋值的是由createRef
创建出来的ref对象。用于接收底层 DOM
元素作为其 current
属性。
在React
中,ref
是一个特殊的属性,用于引用组件内部的 DOM
节点或组件实例。ref
属性可以是一个字符串,也可以是一个回调函数,还可以是一个 React.createRef()
创建的 ref
对象。
使用字符串作为 ref
属性的值已经被废弃,不推荐使用。推荐的做法是使用回调函数或 React.createRef()
。
注意:在函数组件中不能使用ref属性,因为它们没有实例。