React中ref是一个对象,它有一个current属性,可以对这个属性进行操作,用于获取DOM元素和保存变化的值。什么是保存变化的值?就是在组件中,你想保存与组件渲染无关的值,就是JSX中用不到的或不显示到页面上的值,比如setTimeout的返回的ID,就可以把这个值放到ref中。为什么要放到ref中,因为更改ref的值,不会引起组件的重新渲染,因为值与渲染无关,它也不应该引起组件渲染。怎么获取ref对象呢?调用useRef()函数和createRef()函数,它们返回ref对象。在组件的整个生命周期中,ref对象一直存在。组件创建,更准确地说法是,组件挂载,ref对象创建,组件销毁,ref对象销毁。

  useRef是一个React Hooks,在函数组件中使用,它还可以接受一个参数,用于初始化useRef返回的对象的current属性。

const ref = useRef(initialValue);

  使用useRef获取DOM元素,就是把ref对象赋给react element的ref属性, 每一个react element都有一个ref属性。组件挂载后,ref对象的current属性,就自动指向DOM元素

import React, { useRef } from "react";

const CustomTextInput = () => {
const textInput = useRef(); const focusTextInput = () => textInput.current.focus(); return (
<>
<input type="text" ref={textInput} />
<button onClick={focusTextInput}>Focus the text input</button>
</>
);
}

  组件挂载完成,textInput.current指向input输入框,就可以直接调用输入框的focus方法。

  使用useRef保存变量的值,直接把变量或值,赋值给ref对象的current属性就可以了。

import React, { useRef, useEffect } from "react";

const Timer = () => {
const intervalRef = useRef(); useEffect(() => {
const id = setInterval(() => {
console.log("A second has passed");
}, 1000); intervalRef.current = id; return () => clearInterval(intervalRef.current);
}); const handleCancel = () => clearInterval(intervalRef.current); return (
<>
//...
</>
);
}

  这里要注意的是更新ref对象的值,是一个side effect,因为这个值不参与渲染,更新值是React渲染之外,要做的事情,所以要放到useEffect或useLayoutEffect中,放到事件处理函数中也可以。如果以下有代码

import React, { useRef } from "react";

const RenderCounter = () => {
const counter = useRef(0);
counter.current = counter.current + 1; return (
<h1>{`The component has been re-rendered ${counter} times`}</h1>
);
};

  最好改成   

import React, { useRef } from "react";

const RenderCounter = () => {
const counter = useRef(0); useEffect(() => {
counter.current = counter.current + 1;
}); return (
<h1>{`The component has been re-rendered ${counter} times`}</h1>
);
};

  函数组件中也可以使用createRef, 但当使用createRef时,每一次组件渲染时都会创建全新的ref对象,而不是每一次渲染都共用一个ref对象,性能会有问题,再说useRef就是代替createRef的,所以在函数组件中就没有必要使用createRef了。

  其实,使用useRef,也可以获取到子组件,直接调用子组件中的方法,不过就是用点麻烦,因为ref只能获取到类组件的实例,也只有类才有实例。函数组件是没有实例的,怎么获取到它?使用forwardref, 把一个函数组件包起来,函数组件就多了一个ref属性。子组件中用useImperativeHandle暴露方法。结合forwardRef 和useImperativeHandle。使用create-react-app 创建React项目,在src中创建一个Counter组件

import React from 'react';
import { useState } from "react" const Counter = () => {
const [count, setCount] = useState(0); const clickHandler = () => {
setCount(c => c + 1);
} return (
<p>count is {count} </p>
)
} export default Counter;

  然后在App.js中引入

import React from 'react';
import Counter from "./Counter"; function App() {
return (
<React.Fragment>
<Counter></Counter>
<button>Add</button>
</React.Fragment>
);
} export default App;

  此时,如果想点击父组件App中的button来增加子组件的count,怎么办?首先,子组件Counter,要把clickHandler方法暴露出来。做法,1,export的不是组件了,而是forwardRef(组件); 2,组件要接受参数ref,const Counter = (props, ref); 3,  在组件内部,使用useImperativeHandle,它的第一个参数是ref,第二个参数是回调函数,返回一个对象,对象中的属性和方法,就可以在父组件中使用ref获取到。

import React, { forwardRef, useImperativeHandle } from 'react';
import { useState } from "react" // 组件被forwardRef之后,组件多了一个ref属性
const Counter = (props, ref) => {
const [count, setCount] = useState(0); const clickHandler = () => {
setCount(c => c + 1);
}
// 第一个参数就是ref,暴露出click方法,供父组件使用
useImperativeHandle(ref, () => {
return ({
click: clickHandler
})
}) return (
<p>count is {count} </p>
)
} // export forwardRef(组件)
export default forwardRef(Counter);

  其次,在父组件App中使用ref,引用子组件,并在button的click回调函数中使用ref

function App() {
const counteRef = useRef(); const handleClick = () => {
counteRef.current.click();
} return (
<React.Fragment>
<Counter ref={counteRef}></Counter>
<button onClick={handleClick}>Add</button>
</React.Fragment>
);
}

  如果项目中使用了Redux和React-Redux,子组件export的是connect()(组件),是一个高阶组件,那父组件中怎么引用子组件呢?如果是react-redux 6.0以前的版本,connect函数第四个参数设置为{ withRef: true },父组件getWrappedInstance()就可以获取到包裹的子组件

connect(null, null, null, { withRef: true })(组件);

  如果是react-redux 6.0 以后的版本,使用 forwardRef: true , 代替{ withRef: true },父组件中的ref可以直接获取到包包裹的子组件

connect(null, null, null, { forwardRef: true })(组件);

  

React中的Ref的更多相关文章

  1. react中的ref的3种方式

    2020-03-31 react中的ref的3种方式 react中ref的3种绑定方式 方式1: string类型绑定 类似于vue中的ref绑定方式,可以通过this.refs.绑定的ref的名字获 ...

  2. react中的ref在input中的详解

    当我们在项目中遇见文本输入框的时候,获取时刻输入框中的值 1.受控组件 class NameForm extends React.Component { constructor(props) { su ...

  3. 六、React 键盘事件 表单事件 事件对象以及React中的ref获取dom节点 、React实现类似Vue的双向数据绑定

    接:https://www.cnblogs.com/chenxi188/p/11782349.html 事件对象 .键盘事件. 表单事件 .ref获取dom节点.React实现类似vue双向数据绑定 ...

  4. 【Web技术】401- 在 React 中使用 Shadow DOM

    本文作者:houfeng 1. Shadow DOM 是什么 Shadow DOM 是什么?我们先来打开 Chrome 的 DevTool,并在 'Settings -> Preferences ...

  5. React中ref的使用方法

    React中ref的使用方法 在react典型的数据流中,props传递是父子组件交互的唯一方式:通过传递一个新的props值来使子组件重新re-render,从而达到父子组件通信.当然,就像reac ...

  6. vue中:key 和react 中key={} 的作用,以及ref的特性?

    vue中:key 和react 中key={} 为了给 vue 或者react 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性 一句话概括就是 ...

  7. React中Ref 的使用 React-踩坑记_05

    React中Ref 的使用 React v16.6.3 在典型的React数据流中,props是父组件与其子组件交互的唯一方式.要修改子项,请使用new props 重新呈现它.但是,在某些情况下,需 ...

  8. React 中阻止事件冒泡的问题

    在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达 ...

  9. react中input自动聚焦问题

    input自动聚焦问题 在react中可以使用refs解决这个问题,首先看一下refs的使用场景: (1)处理焦点.文本选择或媒体控制. (2)触发强制动画. (3)集成第三方 DOM 库. 使用re ...

  10. react中直接调用子组件的方法(非props方式)

    我们都知道在 react中,若要在父组件调用子组件的方法,通常我们会采用在父组件定义一个方法,作为props转给子组件,然后执行该方法,可以获取到子组件传回的参数以得到我们的目的. 显而易见,这个执行 ...

随机推荐

  1. 入门Semantic Kernel:OneApi集成与HelloWorld

    引言 从这一章节开始正式进入我们的 Semantic Kernel 的学习之旅了. 什么是Semantic Kernel? Semantic Kernel是一个轻量级的开源框架,通过 Semantic ...

  2. python教程3.1:数据类型:字符串+列表list

    一.字符串 字符串是⼀个有序的字符的集合,⽤于在计算机⾥存储和表示⽂本信息  常用方法--str.fun() 全局函数 二.列表list []内以逗号分隔,按照索引,存放各种数据类型,每个位置代表⼀个 ...

  3. 企业微信群机器人发送消息(三)java端如何控制

    1.先在群里添加机器人,然后获取机器人的webhook地址: 假设webhook是:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693a9 ...

  4. 10分钟了解Flink SQL使用

    Flink 是一个流处理和批处理统一的大数据框架,专门为高吞吐量和低延迟而设计.开发者可以使用SQL进行流批统一处理,大大简化了数据处理的复杂性.本文将介绍Flink SQL的基本原理.使用方法.流批 ...

  5. SpringBoot3集成WebSocket

    标签:WebSocket,Session,Postman. 一.简介 WebSocket通过一个TCP连接在客户端和服务器之间建立一个全双工.双向的通信通道,使得客户端和服务器之间的数据交换变得更加简 ...

  6. 源码分析——MyBatis核心接口SqlSession的实现原理

    在上一篇文章中(<MyBatis动态代理调用过程源码分析>),我们知道了MyBatis动态代理的核心是MapperProxy,在它内部封装了动态代理的调用逻辑,而我们也知道了在使用动态代理 ...

  7. IDEA使用——新建WEB项目及WEB项目的运行

    第一步:新建项目 1.2勾选Web Application 1.3填写项目名 第二步:项目配置 2.1在WEB-INF目录下新建 classes 和 lib 目录(过程省略) 2.2将classes目 ...

  8. Angular等了三年,那个她已经来了

    Angular生态丰富,功能强大,支撑了许多大型项目的开发.而且一直在前方等待着其他框架跟上.但是不得不直面的一个问题就是:"在等待其他框架跟上的这三年",Angular在陆陆续续 ...

  9. 使用EntityFramework Core和Enums作为字符串的ASP.NET Core Razor页面——第三部分

    目录 介绍 使用代码 添加项目和项目状态处理 下载源文件 - 989.1 KB 介绍 这是一篇由多部分组成的文章的第三部分,演示了通过EntityFramework Core 2.1(EF)将C#en ...

  10. 《HelloGitHub》第 98 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...