踩坑日记(以ts为基础,详解react|踩坑日记:以ts为基础,详解react ref以及其类型约束下的坑)
文章图片
react.jpg 什么是ref
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素上面是官网对
ref
的介绍,简单概括一下ref的作用为用来获取组件的实例或Dom,并且无论是你使用Vue框架还是React框架,都不建议过度使用ref
,能用组件通信来解决的问题,一般不推荐使用ref
,一般是作为“逃生舱”来使用,但有一些情况,你不得不使用ref
获取组件的实例或者DOM,来打破典型的数据流形式组件通信。比如,我们做了某些比较死的表单封装,想直接通过父组件调用其提交方法,比如,你想让你封装的“轮播图”组件直接执行其下一步的操作,等等等,程序员可能遇到很多种奇怪的需求,也可能需要你用到ref
,这里发表一下个人观点,相对于Vue这种渐进式框架而言,React从一开始就对开发者提出的比较严格规范的要求,所以React的ref并不像Vue中那样出现的平凡,多数情况下,还是依照经典的数据流来完成一些操作,虽然两个两个框架都不建议过度使用ref
,比较低的出场率也就注定在使用到它的时候难免遇到一些坑,今天,我将通过这篇文章,来尽可能详细的介绍ref的相关内容,并记录我使用React + ts 访问ref
时遇到的一些坑(本人,react老玩家,ts实属新手)。环境准备
- create-react-app
- typescript
- React.createRef()
- useRef(只在函数组件中使用的hooks)
- 回调函数
- 字符串(已废弃,不要再使用了!)
- 当
ref
属性用于 HTML 元素时,ref
为其底层 DOM 元素。 - 当
ref
属性用于自定义 class 组件时,ref 对象为其接收组件的挂载实例。 - 你不能在函数组件上使用 ref 属性,因为他们没有实例。如果你想要在函数组件中使用
ref
,可以使用forwardRef
,但你可以在函数组件内部使用ref属性,只要他是指向DOM元素或者class组件。
React.createRef() app.tsx
class App extends React.PureComponent {
childRef: any
constructor (props:any) {
super(props)
this.childRef = React.createRef()
console.log(this.childRef.current)
}
render () {
return (这是一个类组件
);
}
componentDidMount () {
console.log(this.childRef.current)
}
}
export default App;
child.tsx
import React from 'react'
class Child extends React.PureComponent {
render () {
return 这是子组件
}
}
export default Child
上面,我们使用
React.createRef()
,在类组件App中访问了类组件Child
的ref。我们通过React.createRef()
创建refs
,并通过ref
属性,传给对应的子组件,因为子组件是一个类组件,那么就会将其实例,挂载到ref对象的current
属性上,于是我们的打印结果是这样的。文章图片
refchild.png
结果比较明显了,这里有一个细节是,我写在
constructor
中的打印结果为null,而写在
componentDidMount
生命周期里才能正常打印,所以,这里有一点需要主要的是
ref是组件或者DOM挂载后才可以访问到的,这一点需要注意,
你在访问组件ref时,必须确保其已经挂载。
我们上面的代码中,APP也是一个类组件,那么如果APP是函数组件呢?我们也是可以正常使用
React.createRef()
,只不过纯函数组件没有生命周期,我们可以通过事件来访问(这时候组件一定是挂载完成的),通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
const App: React.FC = () => {
const childRef: any = React.createRef()
const getRef = () => {
console.log(childRef.current)
}
return (这是一个函数组件
);
}
export default App
我们也是可以正常获取结果的。其实对于纯函数组件,我更推荐使用
useRef
这个hooks来完成,因为useRef
的优势还是比传统的获取ref的形式要多很多,因为useRef不仅仅可以存ref,还可以存任何值,你可以用它来存变量等,当然,这是这个hooks本身的优势,今天我们主要说ref,还是说说怎么使用useRef
来访问ref吧useRef react推出hooks可谓是让react变得更受欢迎了,也更加的舒服了,其中的
refRef
就可以帮助我们在纯函数组件中访问refs对象,于是,对于上面,我们使用react.createRef()
在纯函数中访问refs的代码,可以做下面的更改import React, { useRef } from 'react';
const App: React.FC = () => {
const childRef: any = useRef(null)
const getRef = () => {
console.log(childRef.current)
}
return (这是一个函数组件
);
}
export default App
其结果也是一样的,
useRef
同React.createRef()
类似,都会将ref放在其.current
属性中,不过,useRef
的作用要远远大于后者,这里推荐,再次推荐。具体可看官网对其的介绍。回调函数 【踩坑日记(以ts为基础,详解react|踩坑日记:以ts为基础,详解react ref以及其类型约束下的坑)】React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。不同于
react.createRef
和useRef
返回一个对象,如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调ref来实现。我们还是使用上面的代码(实际开发中,我已经很少写类组件了~),换成回调函数的形式。
import React, { useState } from 'react';
import Child from './components/child'const App: React.FC = () => {
const [isMount, setIsMount] = useState(true)
let childRef: any
const getRef = () => {
console.log(childRef)
}
const setRef = (node: any) => {
console.log('我挂载/卸载了')
childRef = node
}
const unMountChild = () => {
setIsMount(false)
}
return (这是一个函数组件
{isMount && }
);
}
export default App
在上面的代码中,我们通过传入回调的方式,将refs对象赋给了childRef变量,并可以在某一事件中获取它,这一点,和其他两种方式没有差别,差别在于,我们可以在Child组件挂载或者卸载的时候执行一些方法,我们通过一个状态控制了子组件的挂载状态,来做这个demo,最终的实际效果如下
文章图片
refChild2.png
我们在刚开始挂载时执行了方法,这时候我们通过事件获取其refs对象,当修改状态使组件卸载,可以看到再次执行了方法,并且这时候也获取不到refs对象了。这就是refs回调的作用。
小结:上面我们介绍了几种访问refs对象和创建refs对象的方法,这样的文章在网上也是层出不穷,不新鲜,如果你只是想简单学习怎么去创建和访问refs那么你可以看到这里就好了,秉承着我一贯的爱踩坑风格,我决定让自己走一些弯路,再去探索一下更“奇葩”的用法,并且其极有可能在你的业务中用到。
访问DOM的ref对象 上面,我们的Child是一个类组件,其存在实例,于是我们通过三种方式,访问到了这个实例,那么我们思考,如果我们访问的不是一个类组件,而是一个普通DOM节点,会是什么结果呢?我们试一下。
import React, { useRef } from 'react';
const App: React.FC = () => {
const childRef: any = useRef(null)
const getRef = () => {
console.log(childRef.current)
}
return (这是一个函数组件
这是一个普通DOM节点我也是
);
}
export default App
结果是这样的
文章图片
DOMref.png
所以说:
当 ref 属性用于 HTML 元素时,创建的 ref 接收底层 DOM 元素作为其 current 属性。其效果和
document.getElementById
一样。但通常,我们很少在react框架中使用document.getElementById这样的语法,那么你就用ref吧!访问纯函数组件的ref对象(ref转发) 在文章的上面,我们说过,“你只能访问class组件的ref,因为纯函数组件没有实例,但如果你非要获取纯函数组件的ref,你可以使用
React.forwardRef
”,我们先来试一下,正常访问纯函数组件的Ref会出现什么情况。我们先将Child组件改成纯函数的形式import React from 'react'
const Child: React.FC = (props: any) => {
return (这是一个子组件)
}
export default Child
我们将Child变成了一个常见的,但是当我们直接去访问其Ref时,就会报这样的错误(强大的ts),当然,如果你使用的是js,也会在执行的过程中报错一些明显的错误。
文章图片
refError.png
上面的意思是,函数组件并不能访问ref,如果我们非要访问怎么办?这个时候就会用到
forwardRef
了,如果你在js中使用过
forwardRef
,你会知道,使用
forwardRef
包装后的纯函数组件第二个参数为
ref
就像这样
const Child = (props, ref) => ...
export default React.forwardRef(Child)
但,我们在ts中使用时,我就踩到了第一个坑,ts包出这样的类型错误
文章图片
refserror.png
研究后发现,我们不能将React.FC类型传给
forwardRef
,他需要的是一个
ForwardRefRenderFunction
类型,查看
ForwardRefRenderFunction
类型用法后,我们做出修改如下。
import React from 'react'
const Child: React.ForwardRefRenderFunction = (props: any, ref: any) => {
return (这是一个子组件)
}
export default React.forwardRef(Child)
import React, { useRef } from 'react';
import Child from './components/child'const App: React.FC = () => {
const childRef: any = useRef(null)
const getRef = () => {
console.log(childRef.current)
}
return (这是一个函数组件
);
}
export default App
这样我们就可以找到正常访问了
文章图片
refResult.png
上面我们做了这样的操作
- 在父组件中创建了refs对象,并向下传递至Child组件
- 子组件通过
forwardRef
的第二个参数接收ref - 子组件将接收的
ref
传至对应的DOM节点或者类组件上,甚至也可以是函数组件上,就重复上面的操作 - 在父组件中可以访问到子组件的DOM节点或者其某个组件的实例。
使用ref回调将DOM节点暴露给父组件 看标题可能比较懵逼,但其实原理很简单,那就是react的父子组件通信。下面用代码来演示一下
import React, { useRef } from 'react';
import Child from './components/child'const App: React.FC = () => {
let childRef: any
const getRef = () => {
console.log(childRef.current)
}
const setRef = (node: any) => {
childRef = node
}
return (这是一个函数组件
);
}
export default App
我们先传一个回调props给子组件,这时候遇到了一坑
文章图片
refcberror3.png
在js中,这一套操作肯定是行云流水的一套基操,但在ts有了类型约束后,我们不能随便往子组件里面传一些属性了,需要在子组件中定义props的类型。所以,这里插播一条内容
在ts中定义子组件props类型 子组件可能是函数组件也可能是class组件,我们分别来演示一下如果定义其props类型
import React from 'react'
interface childProps {
childRef?: (node:any) => void
}
const Child: React.ForwardRefRenderFunction = (props: any, ref: any) => {
console.log(props)
return (这是一个子组件)
}
export default React.forwardRef(Child)
这样我们在父组件中传递props时也不会报错了,也能顺利传递自定义props了。
文章图片
childprops.png
当然了,我们也可以在
React.FC
泛型中定义props类型。
import React from 'react'
interface childProps {
childRef?: (node:any) => void
}
const Child: React.FC = (props: any) => {
console.log(props)
return (这是一个子组件)
}
export default Child
结果也是一样的,我们还可以在class组件中定义。
import React from 'react'interface childProps {
childRef?: T
}class Child extends React.PureComponent> {render () {
console.log(this.props)
return 这是子组件
}
}
export default Child
或者
import React from 'react'interface childProps {
childRef?: (node: any) => void
}class Child extends React.PureComponent {render () {
console.log(this.props)
return 这是子组件
}
}
export default Child
后者更精确类型。
了解了上面的内容后,我们就可以自由的向子组件中传递props了。然后回到主题上来,接着研究我们的使用ref回调的形式将DOM暴露给父组件。这时就轻车熟路了。我习惯将组件尽量使用精简的纯函数形式,下面来写纯函数
import React from 'react'
interface childProps {
childRef?: (node:any) => void
}
const Child: React.FC = (props: any) => {
return (这是一个子组件)
}
export default Child
这时候,你父组件中的就可以这样获取子组件暴露的DOM了。
import React from 'react';
import Child from './components/child'const App: React.FC = () => {
let childRef: any
const getRef = () => {
console.log(childRef) // 在这里获取,注意不是在.current属性中了。因为我们用的是回调。这里容易手滑
}
const setRef = (node: any) => {
childRef = node
}
return (这是一个函数组件
);
}
export default App
结果也是一如既往哦
文章图片
refResult.png 写在后面 本文到这里就结束了,大部分代码比较相似,但是也是全部贴了出来,为了做更完成的记录,和尽可能详细的讲解。本文以react+ts为基础,探索react的ref,详细的介绍了Ref的各种使用场景,和在ts类型约束下,可能遇到的坑。
推荐阅读
- 爱就是希望你好好活着
- 2018年11月19日|2018年11月19日 星期一 亲子日记第144篇
- 叙述作文
- 2019年12月24日
- 2019.4.18感恩日记
- 亲子日记第186篇,2018、7、26、星期四、晴
- 2019.4.2咖啡冥想日记
- 以读攻“毒”唤新活动曹彦斌打卡第二天
- 亲子日记(287)2019.4.27.
- 拉黑家人一整年之后,以为会快乐,最后却抑郁症!!