原生组件的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去执行了