React Ref 其实是这样的
大家好,我是Mokou,好久没有冒泡了,最近一直在看研究算法和数据结构方面的东西,但是似乎很多前端不喜欢看这种东西,而且目前本人算法方面也很挫,就不献丑了。
当然了,最近也开始研究React了,这篇文章主要是讲述 Ref 相关的内容,如有错误请指正。
ref 的由来
在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件/元素。
适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
ref 的三种方式
在 React v16.3 之前,ref 通过字符串(string ref)或者回调函数(callback ref)的形式进行获取。
ref 通过字符获取:
// string ref
class MyComponent extends React.Component {
componentDidMount() {
this.refs.myRef.focus();
}
render() {
return <input ref="myRef" />;
}
}
ref 通过回调函数获取:
// callback ref
class MyComponent extends React.Component {
componentDidMount() {
this.myRef.focus();
}
render() {
return <input ref={(ele) => {
this.myRef = ele;
}} />;
}
}
在 v16.3 中,经 0017-new-create-ref 提案引入了新的 API:React.createRef。
ref 通过 React.createRef 获取:
// React.createRef
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
this.myRef.current.focus();
}
render() {
return <input ref={this.myRef} />;
}
}
将被移除的 string ref
首先来具体说说 string ref,string ref 就已被诟病已久,React 官方文档中如此声明:"如果你目前还在使用 this.refs.textInput 这种方式访问 refs ,我们建议用回调函数或 createRef API 的方式代替。",为何如此糟糕?
最初由 React 作者之一的 dan abramov。发布于https://news.ycombinator.com/edit?id=12093234,(该网站需要梯子)。吐槽内容主要有以下几点:
- string ref 不可组合。 例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法在在子组件上添加 ref 了。 另一方面,回调引用没有一个所有者,因此您可以随时编写它们。例如:
/** string ref **/
class Parent extends React.Component {
componentDidMount() {
// 可获取到 this.refs.childRef
console.log(this.refs);
}
render() {
const { children } = this.props;
return React.cloneElement(children, {
ref: 'childRef',
});
}
}
class App extends React.Component {
componentDidMount() {
// this.refs.child 无法获取到
console.log(this.refs);
}
render() {
return (
<Parent>
<Child ref="child" />
</Parent>
);
}
}
- string ref 的所有者由当前执行的组件确定。 这意味着使用通用的“渲染回调”模式(例如react),错误的组件将拥有引用(它将最终在react上而不是您的组件定义renderRow)。
class MyComponent extends Component {
renderRow = (index) => {
// string ref 会挂载在 DataTable this 上
return <input ref={'input-' + index} />;
// callback ref 会挂载在 MyComponent this 上
return <input ref={input => this['input-' + index] = input} />;
}
render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />
}
}
- string ref 不适用于Flow之类的静态分析。 Flow不能猜测框架可以使字符串ref“出现”在react上的神奇效果,以及它的类型(可能有所不同)。 回调引用比静态分析更友好。
- string ref 强制React跟踪当前正在执行的组件。 这是有问题的,因为它使react模块处于有状态,并在捆绑中复制react模块时导致奇怪的错误。在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。
关于这点可以参考 React 源码 coerceRef 的实现:
在调和子节点得过程中,会对 string ref 进行处理,把他转换成一个方法,这个方法主要做的事情就是设置 instance.refs[stringRef] = element,相当于把他转换成了function ref
对于更新得过程中string ref是否变化需要对比得是 current.ref._stringRef,这里记录了上一次渲染得时候如果使用得是string ref他的值是什么
owner是在调用createElement的时候获取的,通过ReactCurrentOwner.current获取,这个值在更新一个组件前会被设置,比如更新ClassComponent的时候,调用render方法之前会设置,然后调用render的时候就可以获取对应的owner了。
坚挺的 callback ref
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
最新的 React.createRef
React.createRef 的优点:
- 相对于 callback ref 而言 React.createRef 显得更加直观,避免了 callback ref 的一些理解问题。
React.createRef 的缺点:
- 性能略低于 callback ref
- 能力上仍逊色于 callback ref,例如上一节提到的组合问题,createRef 也是无能为力的。
ref 的值根据节点的类型而有所不同:
- 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
- 默认情况下,你不能在函数组件上使用 ref 属性(可以在函数组件内部使用),因为它们没有实例:
- 如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用)
- 或者可以将该组件转化为 class 组件。
Refs 转发
是否需要将 DOM Refs 暴露给父组件?
在极少数情况下,你可能希望在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装,但它偶尔可用于触发焦点或测量子 DOM 节点的大小或位置。
如何将 ref 暴露给父组件?
如果你使用 16.3 或更高版本的 React, 这种情况下我们推荐使用 ref 转发。Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref。
什么是 ref 转发?
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
如果在低版本中如何转发?
如果你使用 16.2 或更低版本的 React,或者你需要比 ref 转发更高的灵活性,你可以使用 ref 作为特殊名字的 prop 直接传递。
比如下面这样:
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.inputElement = React.createRef();
}
render() {
return (
<CustomTextInput inputRef={this.inputElement} />
);
}
}
以下是对上述示例发生情况的逐步解释:
- 我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
- 我们通过指定 ref 为 JSX 属性,将其向下传递给
<FancyButton ref={ref}>。 - React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数。
- 我们向下转发该 ref 参数到
<button ref={ref}>,将其指定为 JSX 属性。 - 当 ref 挂载完成,ref.current 将指向
<button>DOM 节点。
最后
欢迎关注公众号「前端进阶课」认真学前端,一起进阶。回复 全栈 或 Vue 有好礼相送哦
React Ref 其实是这样的的更多相关文章
- React Ref 和 React forwardRef
Ref 和Dom,Ref是reference(引用)的简写. 能力:大多数情况下,props前递可以解决一切问题,但是依然有需要触达React实例或者Dom节点的情况,这时候应该使用React Ref ...
- React ref回调函数例子
ref属性也可以是一个回调函数而不是一个名字. 这个函数将要在组件被挂载之后立即执行. 这个参照的组件将会作为该函数的参数,这个函数可以立即使用这个组件参数,当然也可以将其保存供以后使用. 当这个 ...
- react ref获取dom对象
react文档 step = React.createRef(); // init <div ref={this.step}></div> // bind componentD ...
- React ref的用法
React的ref有3种用法: 1. 字符串(已废弃)2. 回调函数3. React.createRef() (React16.3提供) 1. 字符串 最早的ref用法. 1.dom节点上使用,通过t ...
- [React] Use React ref to Get a Reference to Specific Components
When you are using React components you need to be able to access specific references to individual ...
- React源码 React ref
ref 的功能,在 react 当中.我们写了一个组件,在这个时候,我们的 render function 里面我们会渲染一系列的子组件或者 dom 节点,有时候我们会希望有这样的需求,就是我们要获取 ...
- 从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- react学习一篇就够了
webstrom自动格式化代码 命令 js框架 MVC 安装 npm install create-react-app -g 生成项目(项目名npm发包包命名规范 /^[a-z0-9_-]$/) cr ...
- react学习2
props,state与render函数的关系 react,父组件的state中的变量改变,则相应的render函数也会执行,返回新的视图,同时父组件的子组件通过props获取父组件的state的变量 ...
随机推荐
- Java实现简单混合计算器
这个计算器并不是基于逆波兰实现的,而是通过简单的递归,一层一层地计算最终求得结果. 具体的图形化界面可以参考我的另外一个篇博客:基于逆波兰表达式实现图形化混合计算器,这里我只是简单的介绍一下怎样求得算 ...
- python根据密钥得到谷歌两步验证动态验证码
# coding=utf-8 import time import base64 import sys import hashlib import hmac import struct def goo ...
- MYSQL中的where ‘1=1‘ 探讨
在学习MySQL时候,关于MySQL注入的例子 首先针对以下代码,实现的是关于sql注入时,一个普通登录所产生的的问题 package com.java.lesson02; import com.ja ...
- 第1课 - 学习 Lua 的意义
第1课 - 学习 Lua 的意义 1.Lua 简介 (1) 1993年.巴西 (2) 小巧精致的脚本语言,大小只有 200K (3) 用标准C语言写成,能够在所有的平台上编译运行 (4) 发明的目标是 ...
- Git切换分支开发
入职第一家公司做开发的时候使用的项目版本管理工具是svn,公司内部搭建的服务器:在第二.第三家公司做开发的时候,使用的项目版本管理工具是Git,现在大多数公司使用的也是Git.刚进入公司的时候首先做的 ...
- 图灵机器人api的使用方法含微信版本和网页版
访问图灵机器人官网http://www.tuling123.com/ 注册一个新的机器人账号 注册成功后转到主页 点击我的机器人>创建机器人>微信机器人 填写基本信息 点击微信介入> ...
- C语言知识点汇集
int main() {// int num; int value; = int num,value; '''同时定义多个变量的方法 但是切记只能是同种类型的''' 都是int 或double等其他类 ...
- 知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案一
简介 模块化的介绍一共2篇 这一篇我们实现一个功能非常简单的StartupModules模块化. 第二篇我们来实现一个ABP的模块化效果. 思考 其实来简单想一下模块化的实验思路,写个接口=>模 ...
- python条件控制语句要注意什么?本文详解
1.条件判断语句(if语句) 执⾏的流程:if语句在执⾏时,会先对条件表达式进⾏求值判断, 如果为True,则执⾏if后的语句 如果为False,则不执⾏ 语法: if 条件表达式 : 代码块 代码块 ...
- 新版 C# 高效率编程指南
前言 C# 从 7 版本开始一直到如今的 9 版本,加入了非常多的特性,其中不乏改善性能.增加程序健壮性和代码简洁性.可读性的改进,这里我整理一些使用新版 C# 的时候个人推荐的写法,可能不适用于所有 ...