React学习笔记六-refs

此文章是本人在学习React的时候,写下的学习笔记,在此纪录和分享。此为第六篇,主要介绍react中的refs。

目录

1.refs基本使用

1.1字符串类型ref小案例

2.回调形式的ref

2.1回调形式ref小案例

2.2回调ref中调用次数问题

3.createRef

3.1createRef的使用

 3.2createRef的“专人专用”

4.ref总结


1.refs基本使用

组件内的标签可以定义ref属性来标识自己。

1.1字符串类型ref小案例

首先我们写一个小案例,来了解refs。我们写一个自定义组件,写两个输入框,点击按钮提示第一个输入框的值,当第二个文本框失去焦点的时候,提示第二个文本框的值。

步骤如下:

        1.在render函数构建虚拟dom时,我们使用ref为input框添加标识,第一个input叫做input1,第二个叫input2,比如说

        <input ref='input1' type="text" placeholder="点击按钮提示数据"/>

注意,使用的是ref来添加表示,而不是refs,不要多写一个s。

        2.然后我们为相应的按钮,和第二个input框设置事件,分别是点击事件和失去焦点事件。如下:

        <button onClick={this.showData}>  <input ref='input2' onBlur={this.showData2} type="text"/>

注意在react中的事件与js中不同,比如说点击事件onclick,在react里面on后的首字母大写,与js做区分。基本上在react里面事件都是在on后首字母大写。

        3.下一步我们写事件处理函数,showData和showData2。由于我们给input框设置了ref添加标识,那么所以使用ref添加标识的信息,全部存储在本组件实例的refs属性里面,所以我们在组件内使用的时候,直接this.refs.使用ref设置的标识名即可使用,也比较推荐使用解构赋值来使用。如下,对refs属性解构赋值,拿取我们想要的input1:

        const {input1} = this.refs

       如此这个inpput1就代表了第一个输入框,我们就可以对第一个文本框的内容进行输入。

代码如下:

<script type="text/babel">
        //创建组件
        class Demo extends React.Component {
            showData = ()=>{
                const {input1} = this.refs
                alert(input1.value)
            }
            showData2 = ()=>{
                const {input2} = this.refs
                alert(input2.value)
            }
            render() {
                return (//构建虚拟dom
                    <div>
                        <input ref='input1' type="text" placeholder="点击按钮提示数据"/>&nbsp;
                        <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                        <input ref='input2' onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
                    </div>
                )
            }

        }

        //渲染组件到界面
        ReactDOM.render(<Demo/>, document.getElementById('test1'))
    </script>

2.回调形式的ref

2.1回调形式ref小案例

上面那个案例是字符串类型的refs,虽然非常简单,但在react官方文档内,不推荐使用这种方式,会出现一些效率上的问题,甚至以后会废弃。所以上面那个案例我们只做refs的学习的入门。

所以现在我们来学习回调形式的ref。

        1.我们尝试把 ref='input1' 改为箭头函数类型:ref={(a)=>{console.log(a)}},此函数参数为a,再在函数体内打印a,看看a是什么东西。

        字符串形式:<input ref='input1' type="text" placeholder="点击按钮提示数据"/>

        函数形式:<input ref={(a)=>{console.log(a)}} type="text" placeholder="点击按钮提示数据"/>

打印结果:

        

 可见,参数a就是本dom节点,就是这个input框当作参数传入了ref。因为箭头函数并没有自己的this,它会把外层render函数的this作为自己的this,而reander函数的this指向组件构造的实例,所以本箭头函数就能和实例相关联,此ref所处的dom节点挂载到实例上并且取名为“input1”。

所以这个形参叫做a就不太合适了,适合叫做currentNode(当前节点),见名知意,或简略称为c。结合箭头函数的特性,只有一个参数,那么可以省略参数的小括号,函数体也就一句,也可以省略箭头函数右侧花括号。所以ref精简如下:

        ref={c => this.input1 = c}

2.事件函数会发生改变。原来是 const {input2} = this.refs ,而现在是 const {input2} = this ,因为此ref所处的dom节点挂载到实例上,所以直接写this即可。

代码如下:

 <script type="text/babel">
        //创建组件
        class Demo extends React.Component {
            showData = () => {
                const {input1} = this
                alert(input1.value)
            }
            showData2 = () => {
                const {input2} = this
                alert(input2.value)
            }
            render() {
                return (//构建虚拟dom
                    <div>
                        <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>
                )
            }

        }

        //渲染组件到界面
        ReactDOM.render(<Demo />, document.getElementById('test1'))
    </script>

2.2回调ref中调用次数问题

我们先上代码:

 class Demo extends React.Component {
            showInfo = ()=>{
                const {input1} = this
                console.log(input1);
            }
            render() {
                return (//构建虚拟dom
                    <div>
                        <input ref={c => this.input1 = c} type="text" />
                        <button onClick={this.showInfo}>点我提示输入的数据</button>
                    </div>
                )
            }

        }

        //渲染组件到界面
        ReactDOM.render(<Demo />, document.getElementById('test1'))

再引入react官网关于回调ref的说明:

        如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

        注意,我们上面所写代码,将ref回调函数写在input标签内,这就是以内联函数的方式定义ref回调函数,符合官网的说明。但是,在更新过程中才会被执行两次,一次null一次传入参数dom元素,第一次执行render渲染dom是初始化,并非更新。

        所以我们在案例上面再加上数据更新的案例,让这个案例有更新状态,再来验证这个回调次数的问题。新加入的案例,就是我们之前写过的,炎热与凉爽切换的案例。另外在ref的回调函数内打印一下回调函数的参数c。代码如下:

//创建组件
        class Demo extends React.Component {
            state = { isHot: false }
            showInfo = () => {
                const { input1 } = this
                alert(input1.value)
            }
            changWeather = ()=>{
                const {isHot} = this.state
                this.setState({isHot:!isHot})
            }
            render() {
                const { isHot } = this.state
                return (//构建虚拟dom
                    <div>
                        <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
                        <input ref={c => {this.input1 = c;console.log('@',c)}} type="text" /><br/><br/>
                        <button onClick={this.showInfo}>点我提示输入的数据</button><br/><br/>
                        <button onClick={this.changWeather}>点我切换天气</button>
                    </div>
                )
            }

        }
        //渲染组件到界面
        ReactDOM.render(<Demo />, document.getElementById('test1'))

效果如下,刚打开页面:

 点击切换按钮切换页面显示文字后:

         这正好印证了官网所言。更新时,第一次回调函数参数为null,第二次才把dom节点当参数传进来。但这并不影响功能的正常。

解释:页面刚加载好的时候,是没有这个情况的,是因为调用render函数,在里面发现回调函数形式的ref,接着根据ref函数内所写把相应的dom节点当作参数传入。

        但是在更新的时候,state会发生改变,state会驱动页面显示,重新调用render函数。在重新调用render时,发现虚拟dom中回调函数式的ref,但react不确定之前调用这个ref的时候,接收的参数,函数执行发生了什么,所以直接先清空这个ref,传参为null,然后再调用这个ref传入当前dom节点参数。

        避免这种问题:官网所言:通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

        那什么是class绑定函数的方式呢?我们来修改上面的代码:

原来的代码:

<input ref={c => {this.input1 = c;console.log('@',c)}} type="text" />

这样的将回调函数式ref写在dom节点内,我们称其为内联式ref。

将这段代码进行修改:

<input ref={this.saveInput} type="text" />

并且将saveInput函数放在实例自身上。

 saveInput = (c) => {
                this.input1 = c
                console.log("@", c);
            }

这样就变成了class的绑定函数方式,它不会影响功能的正常运行。这种情况,就不会发生更新后,它被调用两次,第一次是null的情况了。

3.createRef

3.1createRef的使用

        React.createRef是react的一个api,调用后可以返回一个容器,该容器可以存储被ref所标识的节点。所以我们使用它时,可以创建createRef容器赋值给myRef并且挂载到实例上:

                myRef = React.createRef()

        然后直接在render函数相应的标签内使用:

                <input ref={this.myRef} type="text" />

        这样就相当于把input这个dom节点当作ref的参数,将这个ref放进myRef这个容器里面。

来看代码:

 //创建组件
        class Demo extends React.Component {
            /*
                React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
            */
            myRef = React.createRef()//创建容器赋值给myRef并且挂载到实例上
            showInfo = () => {
                console.log(this.myRef);
            }
            render() {
                return (//构建虚拟dom
                    <div>
                        <input ref={this.myRef} type="text" />
                        <button onClick={this.showInfo}>点我提示输入的数据</button>
                    </div>
                )
            }
        }
        //渲染组件到界面
        ReactDOM.render(<Demo />, document.getElementById('test1'))

我们打印this.myRef看看输出结果:如图所示,打印出的是一个对象,里面的current是我们放入容器的input节点。

 然后打印this.myRef.current:如图所示,打印出了input节点。

 最后打印this.myRef.current.value:如图所示打印出了input框内的值。

 

 3.2createRef的“专人专用”

注意,虽然createRef作为一个容器,但它是“专人专用”的,只能往一个容器内放置一个。我们可以尝试写一个案例,往createRef容器里面塞两个ref。

上面的那个案例,我们给button也设置上createRef容器:

  <input ref={this.myRef} type="text" />
  <button ref={this.myRef} onClick={this.showInfo}>点我提示输入的数据</button>

并且打印输出,console.log(this.myRef);

结果如下:

 显而易见,createRef内只能存在一个ref,后面的会顶替掉前面的。

        所以我们得多创建几个createRef,保证每个“人”都有createRef用。我们创建两个createRef,分别赋值给myRef和myRef2。为两个input分别分配myRef和myRef2,在showInfo和showInfo2内对两个容器做不同的处理。

代码如下:

//创建组件
        class Demo extends React.Component {
            /*
                React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
            */
            myRef = React.createRef()//创建容器赋值给myRef并且挂载到实例上
            myRef2 = React.createRef()
            showInfo = () => {
                alert(this.myRef.current.value);
                console.log(this.myRef);
            }
            showInfo2 = () => {
                alert(this.myRef2.current.value)
            }
            render() {
                return (//构建虚拟dom
                    <div>
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示内容" />
                        <button onClick={this.showInfo}>点我提示输入的数据</button>
                        <input ref={this.myRef2} onBlur={this.showInfo2} type="text" placeholder="失去焦点提示内容" />
                    </div>
                )
            }
        }
        //渲染组件到界面
        ReactDOM.render(<Demo />, document.getElementById('test1'))

这样在需要多个createRef容器的情况下,就可以正常的使用。但也有弊端,就是reander函数内用几个createRef,上面实例内就需要创建几个createRef。但这个createRef还是react官方最推荐的ref使用方式。

4.ref总结

在学完三种ref的使用方式后,我们总结一下:

1.尽量避免使用字符串形式的ref,但尽管写了字符串形式的ref,也不会有太大问题。

2.回调形式的ref稍微麻烦一些。不要太纠结回调函数的回调执行次数问题,内联式的回调函数可以写,官网说了:将 ref 的回调函数定义成 class 的绑定函数的方式,但大多数情况无关紧要。

3.createRef是最麻烦的,但目前来说是react官方最推荐的。

猜你喜欢

转载自blog.csdn.net/zhangawei123/article/details/130799946