ref 概述
- 拿一个场景举例开始
- 比如,在react当中写一个组件,类型是 class Component
- 在 render function 里面渲染一系列的子组件或者 dom节点
- 会有这样的需求,就是获取某个dom节点或某个子组件的实例来做一些手动的操作
- 不局限于 props 更新这种方式来更新这个节点,获取节点后,可以绑定某些事件做一些操作
- 如果没有其他好方法,可能要自己去写
document.querySelector
之类的选择器来获取这个 dom 节点
- 在 react 当中就为我们提供了这种方式
- 通过 ref 这个特殊的 attribute 可以非常方便的在一个组件内部
- 获取到它的一个子节点的实例对象, 这就是 ref 的一个核心功能
- 在 react 当中有三种使用ref的方式
- string ref
- function
- createRef
ref 应用示例
1 ) 演示代码
js
import React from 'react'
export default class RefDemo extends React.Component {
constructor() {
super()
this.objRef = React.createRef()
}
componentDidMount() {
setTimeout(() => {
this.refs.stringRef.textContent = 'string ref'
this.methodRef.textContent = 'method ref'
this.objRef.current.textContent = 'obj ref'
}, 1000)
}
render() {
return (
<>
<p ref="stringRef">xxxx</p>
<p ref={ele => (this.methodRef = ele)}>yyyy</p>
<p ref={this.objRef}>zzzz</p>
</>
)
}
}
2 )代码说明
- 第一种使用了
stringRef
- 也就是我们在想要获取的那个节点的 props上面
- 使用了一个 ref 属性, 然后传入一个字符串, react在完成这一个节点的渲染之后
- 它会在
this.refs
这个对象上面挂载这个string对应的一个key - 这个key所指向的就是我们这个节点实例的对象
- 如果是dom节点, 它就会对应dom的实例
- 如果是子组件,就是子组件的实例,即:class Component
- 如果是一个 function Component,正常来讲它是会失败的
- 拿到的会是一个 undefined,因为 function Component 没有实例
- 如果不让其出错,需要使用
forwardRef
,后续来说
* 此种方式不被推荐
- 第二种使用了
function
- 在 ref 属性上传入一个 method 方法,它接受一个参数
- 这个参数就是一个节点对应的实例
- 如果是dom节点对应的是dom实例
- 如果是组件对应的是组件的实例
- 然后在this上面去挂载某一个属性,比如上面的
methodRef
, 基于其textContent
属性来重新赋值
- 第三种是通过
React.createRef
这个API- 在 class Component 内部,我们使用
this.objRef = React.createRef()
去创建了一个对象- 这个对象相当于
{ current: null}
, 默认值是null
- 这个对象相当于
- 把这个对象传给某一个节点的 ref 属性上,在组件渲染完成之后
- 会把这个节点对应的实例挂载到这个对象的
current
这个属性上面 - 调用它就是通过
this.objRef.current
操作它就可以了
- 在 class Component 内部,我们使用
- 这个 demo 效果是不用跑也知道是怎样的一个变化
- 以上三种情况的前两种没有在源码中有过多的体现,我们看下第三种情况
createRef
3 )源码探究
-
从入口文件 React.js 中可见
jsimport {createRef} from './ReactCreateRef';
-
定位到 ReactCreateRef.js 中
js/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * @flow */ import type {RefObject} from 'shared/ReactTypes'; // an immutable object with a single mutable value export function createRef(): RefObject { const refObject = { current: null, }; if (__DEV__) { Object.seal(refObject); } return refObject; }
-
这个代码看上去,非常简单,它返回的就是一个简单的对象
-
DEV 相关的判断不是核心,忽略即可
-
它里面的属性 current, 默认值是 null
forward ref
1 )示例演示
-
关于 forward ref, 先看一个示例
jsimport React from 'react' const TargetComponent = React.forwardRef((props, ref) => ( <input type="text" ref={ref} /> )) export default class Comp extends React.Component { constructor() { super() this.ref = React.createRef() } componentDidMount() { this.ref.current.value = 'ref input' } render() { return <TargetComponent ref={this.ref} /> } }
- 在这个文件下,定义了两个组件
Comp
,TargetComponent
- Comp 组件
- 里面创建了一个ref, 并传递给 TargetComponent 组件
- ref 是为了去获取某一个节点的实例的,通常会获取dom节点的实例
- 有时,也会获取一下class Component 的实例
- 如果组件是一个 纯的 function Component, 没有实例,则会出错
- 如果我是一个组件的提供者,就是开源了一些组件
- 用户(开发者) 一开始并不知道是否是有实例的组件
- 还有就是,比如一些包, 如 Redux,提供的 connect 方法里的 HOC (高阶组件)
- 作为用户想要获取自己写的组件的实例的时候,传入ref,获得的是被包装过的组件
- 解决之道是外面套上
React.forwardRef
,如上代码所示- forwardRef 可以帮助我们实现一个 ref 的传递
- TargetComponent 组件
- 就是我们基于
React.forwardRef
实现的 function Component - 如果要把它改造成HOC, 也可以基于此函数的回调将 ref 进行传递挂载
- 这样就不会违背开发者的意图
- 就是我们基于
- 在这个文件下,定义了两个组件
2 )源码分析
定位到 forwardRef.js 中
js
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
import warningWithoutStack from 'shared/warningWithoutStack';
export default function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
if (__DEV__) {
if (typeof render !== 'function') {
warningWithoutStack(
false,
'forwardRef requires a render function but was given %s.',
render === null ? 'null' : typeof render,
);
} else {
warningWithoutStack(
// Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
render.length === 0 || render.length === 2,
'forwardRef render functions accept exactly two parameters: props and ref. %s',
render.length === 1
? 'Did you forget to use the ref parameter?'
: 'Any additional parameter will be undefined.',
);
}
if (render != null) {
warningWithoutStack(
render.defaultProps == null && render.propTypes == null,
'forwardRef render functions do not support propTypes or defaultProps. ' +
'Did you accidentally pass a React component?',
);
}
}
return {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
}
-
忽略 DEV 判断代码,其本质上就只有一个 return 对象
jsreturn { $$typeof: REACT_FORWARD_REF_TYPE, render, };
$$typeoff
,是REACT_FORWARD_REF_TYPE
- 注意
- 使用 forwordRef 时, 用这个API最终创建出来的 ReactElement
- 比如上面示例的
TargetComponent
组件最终会被创建成一个 ReactElement,比如,别名叫 A TargetComponent
组件 就是 forwordRef 返回的对象,这个不难理解,因为代码如此TargetComponent
对应的 组件A 的 type 是TargetComponent
TargetComponent
对应的 组件A的$$typeof
是REACT_ELEMENT_TYPE
- A的
$$typeof
不是REACT_FORWARD_REF_TYPE
- 因为,所有通过React.createElement创建的节点的
$$typeof
都是REACT_ELEMENT_TYPE
TargetComponent
组件最终会被React.createElement创建出来- 这一点不要搞混了
- A的
- 注意
render
- 就是我们传进来的 function component