Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

  • 应用框架

    • UI 框架

      • Angular

      • React

        • React Hooks 实现原理
        • React 组件销毁需要注意的异步更新问题
        • React项目结构规范
        • React Fiber 实现原理
        • React Fiber 本质
        • React「 Refs 转发 」初探
          • 前言
          • Demo: 父组件触发子组件的input元素获取焦点
            • InputChild 创建 ref 绑定 input
            • Refs 转发
            • 函数组件使用
            • Class 组件使用
            • 自定义 prop 属性
          • 高阶组件上使用 refs 转发
            • Child 组件使用高阶组件,并且父组件的 ref 要绑定 Child下的 input元素
          • 参考
          • React.forwardRef 源码解析
        • React之低版本chrome setState loading2次状态出现的bug
        • react学习
        • 使用 immer 修改复杂对象
        • input 原生控件,React 是如何实现受控输入的?
        • useEffect 如何处理 async
        • 用组件替换文本中emoji字符
        • 谈谈 React 组件设计原则
      • Solid

      • Svelte

      • Vue

      • 框架本质

    • 开发框架

    • 组件库

  • 工程能力

  • 应用基础

  • 专业领域

  • 业务场景

  • 大前端
  • 应用框架
  • UI 框架
  • React
gahing
2019-06-25
目录

React「 Refs 转发 」初探草稿

# 前言

Refs 转发:将父组件创建的 ref 传递给子组件的某个dom元素(或组件)。让父组件可以直接操作该dom元素(或组件)

一开始使用该技术的时候,分不清传 自定义 ref prop 和 转发ref 有什么区别,本文稍微探讨下

# Demo: 父组件触发子组件的input元素获取焦点

InputChild 为子组件,App 为父组件

const InputChild = (props) => (
  <input></input>
));
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  render () {
    <InputChild ref={this.icRef}>Click me!</InputChild>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

按上面的操作,icRef 拿到的是 InputChild 组件,拿不到 InputChild 下的 input 元素。

比如想在父组件上让 InputChild 的 input 获取焦点,是不行的。

this.icRef.current.focus() //报错
1

# InputChild 创建 ref 绑定 input

在 InputChild 上,利用inputRef = React.createRef() 绑定 input,

然后父组件通过 ref.current.inputRef.current.focus() 获取焦点

class InputChild extends React.Component{
  constructor(){
    super()
    this.inputRef = React.createRef()
  }
  render(){
    return (
      <input ref={this.inputRef}></input>
    )
  } 
}
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  render () {
    <InputChild ref={this.icRef}>Click me!</InputChild>;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
this.ref.current.inputRef.current.focus() // input 获取焦点
1

# Refs 转发

# 函数组件使用

const InputChild = React.forwardRef((props, ref) => (
  <input ref={ref}>
  </input>
));
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  handleClick = () => {
    this.icRef.current.focus()
  }
  render () {
     <>
      <button onClick={this.handleClick}>Learn React</button>
      <InputChild ref={this.icRef}>Click me!</InputChild>;
     </>
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

点击 button 后,input 可以获取到焦点

# Class 组件使用

function refProps(Component) {
  return React.forwardRef((props, ref) => {
    return <Component {...props} forwardedRef={ref} />;
  });
}

@refProps
class InputChild extends React.Component{
  render(){
    const { forwardedRef } = this.props;
    return (
      <input ref={forwardedRef}></input>
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

效果同上,但是这里是 将 ref 绑定到新的prop上

尚未知道还有没有更好的做法

# 自定义 prop 属性

其实也可以直接定义一个 非ref 的prop,如下

class InputChild extends React.Component{
  render(){
    const { forwardedRef } = this.props;
    return (
      <input ref={forwardedRef}></input>
    )
  }
}
// 父组件使用
<InputChild forwardRef={this.ref} />
1
2
3
4
5
6
7
8
9
10

**注意:**函数式组件不能提供ref,否则对ref的访问将会失败,只能用React.forwardRef()传递 ref

效果是一样,但是对于组件使用者(不知道子组件代码的)来说, ref是透明的 第一想法是用 ref 绑定子组件而不是其他额外prop (forwardRef),作为高阶组件封装时,这样做更加友好.

参考 浅谈 React Refs (opens new window)

# 高阶组件上使用 refs 转发

需求:我们有一个 LogProps 高阶组件用于记录 props log,但是对 上层用户是不可见的

用户使用 <Child ref={this.ref}/> 。 ref 拿到的应该是 Child 而不是 高阶组件

错误使用

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}

@logProps
class Child extends React.Component{
  render(){
    <div/>
  }
}
<Child ref={this.ref}>Click me!</Child>;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

this.ref 拿到的是 LogProps 组件,ref prop被消化,不会被传递到 Child

正确使用

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Assign the custom prop "forwardedRef" as a ref
      return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }
  
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
@logProps
class Child extends React.Component{
  render(){
    <div/>
  }
}
<Child ref={this.ref}>Click me!</Child>;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

this.ref 拿到的是 Child 组件

# Child 组件使用高阶组件,并且父组件的 ref 要绑定 Child下的 input元素

在以上的基础上,再转发一次

const InputChild = React.forwardRef((props, ref) => (
  <input ref={ref}>
  </input>
));

// 使用高阶组件对其进行封装
export default logProps(InputChild);
// 父组件中
<Child ref={this.ref}>Click me!</Child>;
1
2
3
4
5
6
7
8
9

父组件 this.ref.current.focus() 即可让 input 获取焦点

# 参考

  1. 转发 Refs (opens new window)
  2. React 中的转发ref (opens new window)

# React.forwardRef 源码解析

export default function forwardRef<Props, ElementType: React$ElementType>(
  render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
  // ... 一些检测代码
  return {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
}
1
2
3
4
5
6
7
8
9

举个demo

const InputChild = React.forwardRef((props, ref) => (
  <input ref={ref}>
  </input>
));
<InputChild ref={this.icRef}>Click me!</InputChild>;
1
2
3
4
5

那 InputChild 组件上的 icRef 是怎么传到 forwardRef 里面的呢?

InputChild 组件其实就是 ReactElement, 我们先看下 src/ReactElement.js 的代码

实际上我们写的 jsx ,调用的是 createElement 方法,该方法最后又实例化一个 ReactElement 对象

var InputChild = React.forwardRef(function (props, ref) {
  return React.createElement("input", {
    ref: ref
  });
});
=>
var InputChild = React.forwardRef(function (props, ref) {
  return ReactElement(
    "input",//ref
    "",//key
    ref,
    null,//self
    null,//source
    ReactCurrentOwner.current,
    {},//props
  );
});
=>
var InputChild = React.forwardRef(function (props, ref) {
  return {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: "input",
    key: "",
    ref: ref,
    props: {},

    // Record the component responsible for creating this element.
    _owner: ReactCurrentOwner.current,
  };
});
=>
var InputChild = (function render(props, ref){
    return {
      $$typeof: REACT_ELEMENT_TYPE,
      type: "input",
      key: "",
      ref: ref,
      props: {},
      _owner: ReactCurrentOwner.current,
    };
  })=>({
  $$typeof: REACT_FORWARD_REF_TYPE,
  render
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

至于组件怎么渲染的,在 react\packages\react-dom\src\server\ReactPartialRenderer.js中定义,

case REACT_FORWARD_REF_TYPE: {
            const element: ReactElement = ((nextChild: any): ReactElement);
            let nextChildren;
            const componentIdentity = {};
            prepareToUseHooks(componentIdentity);
            nextChildren = elementType.render(element.props, element.ref);
            nextChildren = finishHooks(
              elementType.render,
              element.props,
              nextChildren,
              element.ref,
            );
            nextChildren = toArray(nextChildren);
            const frame: Frame = {
              type: null,
              domNamespace: parentNamespace,
              children: nextChildren,
              childIndex: 0,
              context: context,
              footer: '',
            };
            if (__DEV__) {
              ((frame: any): FrameDev).debugElementStack = [];
            }
            this.stack.push(frame);
            return '';
          }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

创建了一个frame,children就是 InputChild的render

createElement 是怎么和dom联系在一起的?

分析的有点乱。。未完待续。。

编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
React Fiber 本质
React之低版本chrome setState loading2次状态出现的bug

← React Fiber 本质 React之低版本chrome setState loading2次状态出现的bug→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式