42.B站 react三大属性3:refs html源码 - yiqunkeke/react-jianshu-shangguigu-zty GitHub Wiki

<!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="点击按钮提示数据"/>&nbsp;
                        <button onClick={this.showData}>点我提示左侧数据</button>&nbsp;
                        <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="点击按钮提示数据"/>&nbsp;
                        <button ref="btn" onClick={this.showData}>点我提示左侧数据</button>&nbsp;
                        <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="点击按钮提示数据"/>&nbsp; */}
                        {/* ref中的匿名回调被调用了。因为控制台打印了@ */}

                        {/* <input ref={(a) => {console.log(a)}} type="text" placeholder="点击按钮提示数据"/>&nbsp; */}
                        {/*这里的参数,正好是ref所处的DOM节点*/}

                        {/* <input ref={(a) => {this.input1 = a}} type="text" placeholder="点击按钮提示数据"/>&nbsp; */}
                        {/* 分析:通常情况下,我们在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="点击按钮提示数据"/>&nbsp;

                        <button onClick={this.showData}>点我提示左侧数据</button>&nbsp;
                      
                        <input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
                    </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="点击按钮提示数据"/>&nbsp;  */}
                        {/* 关于回调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="点击按钮提示数据"/>&nbsp;   

                        {/* 总结 :
                            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>&nbsp;
                    </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="点击按钮提示数据"/>&nbsp;   
                        <button onClick={this.showData}>点我提示左侧数据</button>&nbsp;
                        <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>
⚠️ **GitHub.com Fallback** ⚠️