ref 概述
- 拿一个场景举例开始 - 比如,在react当中写一个组件,类型是 class Component
- 在 render function 里面渲染一系列的子组件或者 dom节点
- 会有这样的需求,就是获取某个dom节点或某个子组件的实例来做一些手动的操作
- 不局限于 props 更新这种方式来更新这个节点,获取节点后,可以绑定某些事件做一些操作
- 如果没有其他好方法,可能要自己去写 document.querySelector之类的选择器来获取这个 dom 节点
 
- 在 react 当中就为我们提供了这种方式 - 通过 ref 这个特殊的 attribute 可以非常方便的在一个组件内部
- 获取到它的一个子节点的实例对象, 这就是 ref 的一个核心功能
 
- 在 react 当中有三种使用ref的方式 - string ref
- function
- createRef
 
ref 应用示例
1 ) 演示代码
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 中可见 import {createRef} from './ReactCreateRef';
-  定位到 ReactCreateRef.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, 先看一个示例 import 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 中
/*** 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' objectrender.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 对象 return {$$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