ref实现原理

64 阅读1分钟

ref实现原理

ref分有原生标签类组件函数组件

原生标签的ref:如果给一个原生组件添加了一个ref属性,那么原生组件虚拟DOM变成真实DOM之后,会把真实DOM元素赋值给ref.current

原理

./constants

export const REACT_FORWARD_REF = Symbol('react.forward_ref');

./react

+ import { REACT_ELEMENT, REACT_FORWARD_REF } from './constants'
+ function createRef () {
+   return { current: null }
+ }

+ function forwardRef (render) {
+   return {
+     $$typeof: REACT_FORWARD_REF,
+     render, // 渲染函数
+   }
+ }

const React = {
  createElement,
  Component,
+   createRef,
+   forwardRef
}
export default React;

./react-dom

export function createDOM (vdom) {
  if (!vdom) return null;
  let { type, props, ref } = vdom;
  let dom; // 真实DOM
  + if (ref && type.$$typeof === REACT_FORWARD_REF) { // 函数组件
  +   return mountForwardComponent(vdom);
  + } else if (type === REACT_TEXT) {
    dom = document.createTextNode(props.content)
  } else if (typeof type === 'function') {
    if (type.isReactComponent) {
      return mountClassComponent(vdom);
    }
    return mountFunctionComponent(vdom);

  } else {
    dom = document.createElement(type);
  }
  // 处理属性
  if (props) {
    updateProps(dom, {}, props);
    if (props.children) {
      let children = props.children;
      if (typeof children === 'object' && children.type) {
        mount(children, dom)
      } else if (Array.isArray(children)) {
        reconcileChildren(props.children, dom)

      }
    }
  }
  vdom.dom = dom;
  + if (ref) ref.current = dom; // 类组件 把真实DOM赋值给ref.current
  return dom;
}

+ function mountForwardComponent (vdom) {
+   let { type, props, ref } = vdom;
+   let renderVdom = type.render(props, ref)
+   vdom.oldRenderVdom = renderVdom;
+   return createDOM(renderVdom);
+ }

function mountClassComponent (vdom) {
  let { type: ClassComponent, props, ref } = vdom;
  let classInstance = new ClassComponent(props);
+   if (ref) ref.current = classInstance;
  let renderVdom = classInstance.render(props);
  classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
  return createDOM(renderVdom);
}

原生标签使用

import React from '../react'
import ReactDOM from '../react-dom'

class Calculate extends React.Component {
  constructor(props) {
    super(props)
    this.aRef = React.createRef()
    this.bRef = React.createRef()
    this.resuleRef = React.createRef()
  }
  handleClick = () => {
    const a = this.aRef.current.value
    const b = this.bRef.current.value
    this.resuleRef.current.value = parseInt(a) + parseInt(b)
  }
  render () {
    return (
      <div>
        <input ref={this.aRef} type="text" />+
        <input ref={this.bRef} type="text" />
        <button onClick={this.handleClick}>=</button>
        <input ref={this.resuleRef} type="text" />
      </div>
    )
  }
}
ReactDOM.render(<Calculate />, document.getElementById('root'))

类组件使用

import React from '../react'
import ReactDOM from '../react-dom'

class TextInput extends React.Component {
  constructor(props) {
    super(props)
    this.textInput = React.createRef()
  }
  getFocus = () => {
    this.textInput.current.focus()
  }

  render () {
    return (
      <div>
        <input type="text" ref={this.textInput} />
      </div>
    )
  }
}

class App extends React.Component {
  constructor(props) {
    super(props)
    this.textInput = React.createRef()
  }

  handleClick = () => {
    this.textInput.current.getFocus()
  }

  render () {
    return (
      <div>
        <TextInput ref={this.textInput} />
        <button onClick={this.handleClick}>聚焦</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

函数组件使用

import React from '../react'
import ReactDOM from '../react-dom'

function InputText (props, forwardRef) {
  return <input type="text" ref={forwardRef} />
}
const ForwardInputText = React.forwardRef(InputText)

class App extends React.Component {
  constructor(props) {
    super(props)
    this.textInput = React.createRef()
  }
  getFocus = () => {
    this.textInput.current.focus()
  }
  /* Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? */
  render () {
    return <div>
      <ForwardInputText ref={this.textInput} />
      <button onClick={this.getFocus}>聚焦</button>
    </div>
  }
}
ReactDOM.render(<App />, document.getElementById('root'))