<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hello_react</title>
<!-- 引入react,为了使用 React对象 -->
<script src="js/react.development.js"></script>
<!-- 引入 react-dom, 为了使用 ReactDOM 对象 -->
<script src="js/react-dom.development.js"></script>
<!-- 引入 babel, 为了 解析 JSX -->
<script src="js/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<!-- 1.原生方式获取DOM结点并取值 -->
<!-- <script type="text/babel">
// 创建组件
class Demo extends React.Component {
showData = () => {
// 拿到左侧input框的值
// 原生方式: 给input加id
// 不推荐使用原生方式:加id,使用document.getElementById获取结点,再取value值
const input1 = document.getElementById('input1') // 这样获取到的是左侧input的真实结点,真实DOM
alert(input1.value)
}
render() {
return (
<div>
<input id="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script> -->
<!-- 2.字符串形式的 ref --> <!--react官方已经不推荐使用这种字符串形式的ref了-->
<!-- <script type="text/babel">
class Demo extends React.Component {
showData = () => {
// console.log(this.refs.input1) // 这个this.refs.input1 拿到的是什么? ----是真实DOM。
// 一定注意:虽然你写的是 jsx,在虚拟DOM上添加的 ref 属性。但是这些虚拟DOM会转成真实DOM,所以你这里获取的this.refs.input1是真实DOM对应的节点
// debugger // 通过打断点,可以看到 input1上有很多属性。----是真实DOM节点。而虚拟DOM节点上的属性是非常少。
const {input1} = this.refs
alert(input1.value)
}
showData2 = () => {
const {input2} = this.refs
alert(input2.value)
}
render() {
return (
<div>
{/*添加ref="input1"----》组件实例对象this的refs属性中就多了一组key-value
key就是你所指定的input1, value就是ref当前所处的结点
*/}
{/* 为什么叫 refs属性呢?因为它可以收集多组key-value
理解:把ref就当成标识。组件内的标签可以定义ref属性来标识自己。
*/}
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button ref="btn" onClick={this.showData}>点我提示左侧数据</button>
<input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点提示数据"/>
</div>
// 只要你敢在你的结构中把任何一个标签用ref打标识了,react都非常精准的给你收集到这个 refs 属性中去。
// 而且,react这里收集的不是虚拟DOM,而是该虚拟DOM转成真实DOM之后对应的节点。是真正的节点。
)
}
// 需要注意的点:
// 你写在标签上的属性叫 ref,而收集出来的属性叫 refs
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script> -->
<!-- 字符串形式的ref,存在效率上的问题。效率不高。可能会在未来的版本中被移除。
建议使用回调函数、createRef -->
<!-- 3. 回调函数形式的 ref -->
<!-- <script type="text/babel">
class Demo extends React.Component {
showData = () => {
const {input1} = this // 从实例自身取ref当前所处节点
alert(input1.value)
}
showData2 = () => {
const {input2} = this
alert(input2.value)
}
render() {
return (
<div>
{/* 回调函数必须满足: 1. 你定义的函数 2. 你没调用 3.函数最终执行了。 《===》 你定义的函数,被别人调了。叫回调函数。
回调函数能收到什么参数?---取决于谁调的它。函数能收到什么参数取决于函数的调用者。
*/}
{/* <input ref={() => {console.log('@')}} type="text" placeholder="点击按钮提示数据"/> */}
{/* ref中的匿名回调被调用了。因为控制台打印了@ */}
{/* <input ref={(a) => {console.log(a)}} type="text" placeholder="点击按钮提示数据"/> */}
{/*这里的参数,正好是ref所处的DOM节点*/}
{/* <input ref={(a) => {this.input1 = a}} type="text" placeholder="点击按钮提示数据"/> */}
{/* 分析:通常情况下,我们在ref匿名回调函数中写 this.input1 = a 就把代码写完了
react 为了在页面上展示东西,首先肯定会new 一个Demo实例,且通过Demo实例调用render,调用render时会执行返回的JSX,
执行JSX时就直接触发了 ref 中回调函数的执行。由于你写了一个函数形式的ref,react会帮你调用这个回调函数,而且把ref当前所处的节点传递进去。
所以说a就拿到了ref当前所处的DOM节点。
this.input1 = a 的意思是:把这个ref所处的节点放在了实例自身上,然后起了个名字为 input1。
这里的 this是谁?---这里是箭头函数,没有自己的this,往外找就是render。 render中this是组件实例对象。
所以最终:把ref当前所处节点挂在组件实例自身,并且取名input1
*/}
{/* 精简写法:*/}
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
// 注意的点:
// 一定要明白: c => this.input1 = c 这个 函数是一个回调函数。你只管写在这,剩下的交给react去做。因为你写的是 ref = 这个回调函数,所以react会去帮你调用这个函数的。
// 如果你在标签中写一个 haha = {() => {console.log('#')}} ---react就报错。不是说你写什么属性,react都会帮你调。
// 但是你写ref等于一个函数,react会帮你调。
// 它不仅帮你调,还把ref当前所处的节点帮你传进去了。我们的做法就是:往实例自身上挂一下。
// 来分析一个问题:
// c => this.input1 = c 和 c => this.input2 = c 这个回调函数调用次数的问题:
// 首先确实react 帮你调了这个函数,而且确实它也是一个回调函数。现在来研究下它的调用次数的问题。
// --------------------------------------------------》
//
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script> -->
<!-- 4. 回调ref中,回调执行次数的问题 -->
<!-- <script type="text/babel">
class Demo extends React.Component {
state = {
isHot: true
}
showData = () => {
const {input1} = this
alert(input1.value)
}
changeWeather = () => {
const {isHot} = this.state
this.setState({
isHot: !isHot
})
}
saveInput = (c) => {
this.input1 = c;
console.log('@', c)
}
render() {
const {isHot} = this.state
return (
<div>
<h2 onClick={this.changeWeather}>今天天气很 { isHot ? '炎热': '凉爽'}</h2>
{/* ref右侧是匿名回调函数--且是内联函数:直接把函数丢在行内。*/}
{/* <input ref={(c) => { this.input1 = c; console.log('@', c)}} type="text" placeholder="点击按钮提示数据"/> */}
{/* 关于回调ref的说明:
如果 ref 回调函数是以【内联函数】的方式定义的,在【更新过程】中它会被执行两次。第一次传参数是 null,第二次会传入参数DOM元素。
当点击天气标题,更新时,回调ref后面的内联函数调用了两次
为什么要在更新时调用两次呀?这是因为在每次渲染时会创建一个新的函数实例,所以react清空旧的ref并且设置新的。----意思是:为了保证组件能出现在页面上,
需要调用一次render。调用render时执行到了 `ref = {(c) => { this.input1 = c; console.log('@', c)}}`这行代码。发现你写了一个函数式的ref,则帮你调用这个函数。
且把ref所处当前节点传入。
点击了天气标题后,会更改状态。状态驱动页面显示。怎么驱动?--重新调render
则 `ref = {(c) => { this.input1 = c; console.log('@', c)}}` 又会重新执行一次。又发现是函数式的ref。注意,这个函数不再是之前的那个函数,而是一个新函数。
之前的函数被释放了,没了。这是一个新的函数。而react不确定之前的函数做了什么动作。所以react会在更新时作一次清空,且传了一个 null。紧接着又调用第二次的时候,才把
当前的节点传入。
【解决】:通过将ref回调函数定义成 【class绑定函数】的方式。可以避免上述问题。
*/}
{/*ref右侧是类绑定回调函数---回调函数绑定在 this自身。这就是【class绑定函数】*/}
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>
{/* 总结 :
ref={(c) => { this.input1 = c; console.log('@', c)}} 和
ref={this.saveInput}
都叫回调形式的ref。第一种形式是内联回调函数丢在那里给react调用,第二种形式把回调函数放在了实例自身。
使用把回调函数绑定在this自身的方式,叫 class绑定方式。这种方式下,再点击天气标题,【更新】时,都不会再重复的调用 ref后面的回调函数 saveInput 了。
正因为saveInput在实例this自身,它能知道 saveInput不再是一个新的函数了。所以不会重复调用。
*/}
{/*
但是大多数情况下,它是无关紧要的。------以后直接写ref的内联回调。
*/}
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script> -->
<!-- 5. createRef -->
<script type="text/babel">
class Demo extends React.Component {
// 直接调用React的API createRef
// React.createRef是一个函数,调用该函数后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用的”
// 下面代码含义:创建出一个容器,挂在了实例自身上。起名为 myRef
myRef = React.createRef()
myRef2 = React.createRef()
showData = () => {
console.log(this.myRef) // 先输出下容器 --- this.myRef {current: input}
// current属性不能改,人家写好的。
alert(this.myRef.current.value)
}
showData2 = () => {
console.log(this.myRef2)
alert(this.myRef2.current.value)
}
render() {
return (
<div>
{/*这里就可以直接使用 this.myRef:
当react发现这个this.myRef是使用createRef创建出来的一个容器,就会把ref当前所处节点,直接存储到了这个容器里。
就是说,把 input节点,存储到了 this.myRef 中
注意这里的 this.myRef就不再是回调函数。而是用React最新的API --createRef生成的一个容器,这个容器专门用于存储被ref所标识的节点。
所以input就被存在 this.myRef中。
*/}
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点时提示数据"/>
</div>
// 总结 : 用 createRef有一个比较繁琐的地方:用几个ref,就需要在上面创建几个ref容器。稍微繁琐,但是react目前最推荐的写法。
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
<!-- 6.整体总结ref
在条件允许的情况下,尽可能避免字符串形式的ref。
就开发来讲,用内联回调函数的ref比较多。----调用两次无关紧要。
createRef最麻烦,目前最推荐。
-->
</body>
</html>