Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素
为 DOM/类组件 添加
可以使用
ref去存储DOM节点的引用当
ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性当
ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性
使用
src/index.js
js
import React from "./lib/react";
import ReactDOM from "./lib/react-dom";
class Form extends React.Component {
constructor(props) {
super(props);
this.userNameRef = React.createRef();
}
getFocus = () => {
this.userNameRef.current.getFocus();
};
render() {
return (
<div>
<ForwardUserName ref={this.userNameRef} />
<button onClick={this.getFocus}>focus</button>
</div>
);
}
}
// 为类组件添加ref
/* class UserName extends React.Component {
constructor(props) {
super(props)
this.inputRef = React.createRef()
}
getFocus = () => {
this.inputRef.current.focus()
}
render() {
return (
<input ref={this.inputRef} />
)
}
} */
class Sum extends React.Component {
constructor(props) {
super(props);
this.a = React.createRef();
this.b = React.createRef();
this.result = React.createRef();
}
handleAdd = () => {
let a = this.a.current.value;
let b = this.b.current.value;
this.result.current.value = Number(a) + Number(b);
};
render() {
return (
<div>
<input ref={this.a} /> + <input ref={this.b} />
<button onClick={this.handleAdd}>=</button>
<input ref={this.result} />
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById("root"))``;实现
lib/react.js
js{31-35,40}
import { REACT_ELEMENT } from "./element-types";
import { wrapToVdom } from "./utils";
import { Component } from "./Component";
// 把babel转换后的结果转成ReactDOM
function createElement(type, config, children) {
// children:这里已经是一个虚拟DOM
let key = config.key;
let ref = config.ref;
if (config) {
delete config.__source;
delete config._owner;
delete config.__self;
delete config.key;
delete config.ref;
}
let props = { ...config };
if (arguments.length > 3) {
// 如果参数大于3,说明子节点是数组
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT, // 表示是一个虚拟DOM,或者说是React元素
key,
ref,
type, // 虚拟DOM的类型,如果是函数/类组件,type就是组件本身
props, // 像事件,id,属性,class,style等都在这里面
};
}
function createRef() {
return {
current: null,
};
}
const React = {
createElement,
Component,
createRef,
};
export default React;lib/react-dom.js
js{37,42,57}
import { addEvent } from "./event";
import { REACT_TEXT } from "./element-types";
/**
* 要把虚拟DOM转换成真实DOM并且挂载到容器中
* @param {*} vdom 虚拟DOM
* @param {*} container 容器
*/
function render(vdom, container) {
mount(vdom, container);
}
function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
function createDOM(vdom) {
const { props, type, ref } = vdom;
let dom; // 真实的DOM节点
if (type === REACT_TEXT) {
dom = document.createTextNode(props); // props是一个字符串
} else if (typeof type === "function") {
if (type.isReactComponent) {
// 类组件
return mountClassComponent(vdom);
}
return mountFunctionComponent(vdom); // 函数组件
} else {
dom = document.createElement(type);
}
if (props) {
// 如果props存在,更新属性
updateProps(dom, {}, props);
if (typeof props.children === "object" && props.children.type) {
// 说明只有一个子节点
mount(props.children, dom); // 递归渲染子节点
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
// 创建真实DOM的时候和虚拟DOM进行关联
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountFunctionComponent(vdom) {
const { props, type: FunctionComponent } = vdom;
let renderVdom = FunctionComponent(props); // renderVdom才是要渲染的虚拟DOM
// 保存老的虚拟DOM
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
const { props, type: ClassComponent, ref } = vdom;
// 先创建类组件实例
let classInstance = new ClassComponent(props);
if(ref) ref.current = classInstance; // 为类组件添加ref
// 调用render方法返回renderVdom
let renderVdom = classInstance.render();
vdom.classInstance = classInstance; // vdom和实例进行关联
// 类的实例上保存老的虚拟DOM,同时给vdom上也添加oldRenderVdom指向老的vdom
classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function reconcileChildren(children, dom) {
children.forEach((child) => {
mount(child, dom);
});
}
function updateProps(dom, oldProps = {}, newProps) {
for (let key in newProps) {
// 先遍历新的属性
if (key === "children") continue;
if (key === "style") {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z]*/.test(key)) {
// 绑定的事件
addEvent(dom, key.toLowerCase(), newProps[key]);
// dom[key.toLowerCase()] = newProps[key]
} else {
// dom.className = 'title'
dom[key] = newProps[key];
}
}
// 再遍历老属性,如果新属性中没有直接删除掉
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
/**
* 根据虚拟DOM找到真实DOM
* @param {*} vdom
* 如果是像div节点,这样是可以通过vdom.dom找到真实DOM
* 如果是类组件或者函数组件有两种情况
* 1、创建函数/类组件的时候在虚拟DOM上通过oldRenderVdom绑定了虚拟DOM
* 2、如果是组件嵌套组件,那就需要进行递归查找了
*/
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
// vdom对应原生的组件
return vdom.dom;
} else {
// 类组件或函数组件
let renderVdom = vdom.classInstance
? vdom.classInstance.oldRenderVdom
: vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
let oldDOM = findDOM(oldVdom); // 老的真实DOM
let newDOM = createDOM(newVdom); // 新的真实DOM
parentDOM.replaceChild(newDOM, oldDOM); // 新的替换老的
}
const ReactDOM = {
render,
};
export default ReactDOM;为函数组件添加
使用
- 不能在函数组件上使用
ref属性,因为他们没有实例 Ref转发是一项将ref自动地通过组件传递到其一子组件的技巧Ref转发允许某些组件接收ref,并将其向下传递给子组件
src/index.js
js
import React from "./lib/react";
import ReactDOM from "./lib/react-dom";
// 函数组件绑定ref
/* function UserName(props, ref) {
return <input ref={ref} />
} */
// 限制行为
function UserName(props, ref) {
let inputRef = React.createRef();
ref.current = {
focus: () => {
inputRef.current.focus();
},
};
return <input ref={inputRef} />;
}
const ForwardUserName = React.forwardRef(UserName);
class Form extends React.Component {
constructor(props) {
super(props);
this.userNameRef = React.createRef();
}
getFocus = () => {
this.userNameRef.current.focus();
};
render() {
return (
<div>
<ForwardUserName ref={this.userNameRef} />
<button onClick={this.getFocus}>focus</button>
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById("root"));实现
lib/react.js
js{37-42,47}
import { REACT_ELEMENT, REACT_FORWARD_REF } from "./element-types";
import { wrapToVdom } from "./utils";
import { Component } from "./Component";
// 把babel转换后的结果转成ReactDOM
function createElement(type, config, children) {
// children:这里已经是一个虚拟DOM
let key = config.key;
let ref = config.ref;
if (config) {
delete config.__source;
delete config._owner;
delete config.__self;
delete config.key;
delete config.ref;
}
let props = { ...config };
if (arguments.length > 3) {
// 如果参数大于3,说明子节点是数组
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT, // 表示是一个虚拟DOM,或者说是React元素
key,
ref,
type, // 虚拟DOM的类型,如果是函数/类组件,type就是组件本身
props, // 像事件,id,属性,class,style等都在这里面
};
}
function createRef() {
return {
current: null,
};
}
function forwardRef(render) {
return {
$$typeof: REACT_FORWARD_REF,
render,
};
}
const React = {
createElement,
Component,
createRef,
forwardRef,
};
export default React;lib/react-dom.js
js{19-21,49-54}
import { addEvent } from "./event";
import { REACT_FORWARD_REF, REACT_TEXT } from "./element-types";
/**
* 要把虚拟DOM转换成真实DOM并且挂载到容器中
* @param {*} vdom 虚拟DOM
* @param {*} container 容器
*/
function render(vdom, container) {
mount(vdom, container);
}
function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
function createDOM(vdom) {
const { props, type, ref } = vdom;
let dom; // 真实的DOM节点
if (type && type.$$typeof === REACT_FORWARD_REF) {
// 挂载forward组件
return mountForwardComponent(vdom);
} else if (type === REACT_TEXT) {
dom = document.createTextNode(props); // props是一个字符串
} else if (typeof type === "function") {
if (type.isReactComponent) {
// 类组件
return mountClassComponent(vdom);
}
return mountFunctionComponent(vdom); // 函数组件
} else {
dom = document.createElement(type);
}
if (props) {
// 如果props存在,更新属性
updateProps(dom, {}, props);
if (typeof props.children === "object" && props.children.type) {
// 说明只有一个子节点
mount(props.children, dom); // 递归渲染子节点
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
// 创建真实DOM的时候和虚拟DOM进行关联
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountForwardComponent(vdom) {
const { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountFunctionComponent(vdom) {
const { props, type: FunctionComponent } = vdom;
let renderVdom = FunctionComponent(props); // renderVdom才是要渲染的虚拟DOM
// 保存老的虚拟DOM
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
const { props, type: ClassComponent, ref } = vdom;
// 先创建类组件实例
let classInstance = new ClassComponent(props);
if (ref) ref.current = classInstance; // 类组件添加ref
// 调用render方法返回renderVdom
let renderVdom = classInstance.render();
vdom.classInstance = classInstance; // vdom和实例进行关联
// 类的实例上保存老的虚拟DOM,同时给vdom上也添加oldRenderVdom指向老的vdom
classInstance.oldRenderVdom = vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function reconcileChildren(children, dom) {
children.forEach((child) => {
mount(child, dom);
});
}
function updateProps(dom, oldProps = {}, newProps) {
for (let key in newProps) {
// 先遍历新的属性
if (key === "children") continue;
if (key === "style") {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z]*/.test(key)) {
// 绑定的事件
addEvent(dom, key.toLowerCase(), newProps[key]);
// dom[key.toLowerCase()] = newProps[key]
} else {
// dom.className = 'title'
dom[key] = newProps[key];
}
}
// 再遍历老属性,如果新属性中没有直接删除掉
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
/**
* 根据虚拟DOM找到真实DOM
* @param {*} vdom
* 如果是像div节点,这样是可以通过vdom.dom找到真实DOM
* 如果是类组件或者函数组件有两种情况
* 1、创建函数/类组件的时候在虚拟DOM上通过oldRenderVdom绑定了虚拟DOM
* 2、如果是组件嵌套组件,那就需要进行递归查找了
*/
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
// vdom对应原生的组件
return vdom.dom;
} else {
// 类组件或函数组件
let renderVdom = vdom.classInstance
? vdom.classInstance.oldRenderVdom
: vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
let oldDOM = findDOM(oldVdom); // 老的真实DOM
let newDOM = createDOM(newVdom); // 新的真实DOM
parentDOM.replaceChild(newDOM, oldDOM); // 新的替换老的
}
const ReactDOM = {
render,
};
export default ReactDOM;