手写react5-ref

60 阅读1分钟

原生组件的ref指向DOM对象

从虚拟DOM属性上提取ref

在createDOM的最后,添加ref指针即可

export function createDOM(vdom) {
    if (!vdom) return null;
    let { type, props, ref } = vdom;
    let dom;//真实DOM
  	...
    vdom.dom = dom;
  	if (ref) ref.current = dom
    return dom;
}

createRef方法:

function createRef() {
    return { current: null };
}

类组件的ref指向类的实例化对象

可以在挂载的时候添加ref引用

function mountClassComponent(vdom) {
    let { type: ClassComponent, props, ref } = vdom;
    let classInstance = new ClassComponent(props);
    //如果类组件的虚拟DOM有ref属性,那么就把类的实例赋给ref.current属性
    if (ref) ref.current = classInstance;
    let renderVdom = classInstance.render();
    classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}

函数组件没有实例不能添加ref,需要添加forwardRef

用法:

function TextInput(props, forwardRef) {
  return <input ref={forwardRef} />
}
const ForwardedTextInput = React.forwardRef(TextInput);
  
class Form extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  getFocus = (event) => {
    //如果给一个函数组件添加了ref属性,那么ref会向指向DOM对象
    this.inputRef.current.focus();
  }
  render() {
    return (
      <div>
        <ForwardedTextInput ref={this.inputRef} />
        <button onClick={this.getFocus}>输入框获得焦点</button>
      </div>
    )
  }
}
ReactDOM.render(
  <Form />, document.getElementById('root')
);

实现:

function forwardRef(render) {//TODO
    return {
        $$typeof: REACT_FORWARD_REF,
        render //函数组件 TextInput(props, forwardRef)
    }
}
export function createDOM(vdom) {
    if (!vdom) return null;
    let { type, props, ref } = vdom;
    let dom;//真实DOM
    if (type && type.$$typeof === REACT_FORWARD_REF) {//说明它是一个转发过的函数组件
        return mountForwardComponent(vdom);
function mountForwardComponent(vdom) {
    let { type, props, ref } = vdom;
    let renderVdom = type.render(props, ref);
    vdom.oldRenderVdom = renderVdom;
    return createDOM(renderVdom);
}

函数组件ref的渲染过程:

经过babel转义后,变成:

React.createElement(ForwardedTextInput, {
  ref: inputRef
});

执行createElement之后,返回了

{
  	$$typeof: REACT_ELEMENT,
    type: ForwardedTextInput,
    ref: inputRef
}

也就是:

{
  	$$typeof: REACT_ELEMENT,
    type: React.forwardRef(TextInput),
    ref: inputRef
}

这里面的type,再调用React.forwardRef(TextInput)之后,得到ForwardedTextInput,其结构变成了:

{
  	$$typeof: REACT_ELEMENT,
    type: {
        $$typeof: REACT_FORWARD_REF,
        render: TextInput(props, forwardRef) {
          return <input ref={forwardRef} />
        }
    },
    ref: inputRef
}

type.render里面的 再经过babel的解析就成为

{
  	$$typeof: REACT_ELEMENT,
    type: {
        $$typeof: REACT_FORWARD_REF,
        render: TextInput(props, forwardRef) {
          return {
              $$typeof: REACT_ELEMENT,
              type: 'input',
              ref: forwardRef
          }
        }
    },
    ref: inputRef
}

这个结构满足type.$$typeof === REACT_FORWARD_REF这一条件

所以会优先进入createDOM的if (type && type.$$typeof === REACT_FORWARD_REF)这一分支,而不会走到其他分值判断中去,最终会走到mountForwardComponent方法里面,这个方法中就可以拿到type.render去执行了