关于React虚拟dom

JSX

JSX 本质上是 JavaScript 的语法拓展

JSX 能够在 JavaScript 中生效得益于 babel 的加工处理,转化为 React.createElement(type, config, children),从而转化为

React.createElement

// 源码
export function createElement(type, config, children) {
    // 用于后面for...in提取存储元素属性
    let propName;

    // 用来存储config中提取出的属性
    const props = {};

    let key = null;
    let ref = null;
    let self = null;
    let source = null;

    if (config != null) {
        // 对ref,key,__self,__source特殊的属性进行赋值
        if (hasValidRef(config)) {
            ref = config.ref;
        }
        // 字符串化
        if (hasValidKey(config)) {
            key = '' + config.key;
        }
        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source;

        // 除了ref,key,__self,__source之外的属性存储到props中
        for (propName in config) {
            if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
                props[propName] = config[propName];
            }
        }
    }

    // 把children赋值和props.children上
    // 去除type和config,剩下的就是children的个数,可能会有多个children
    const childrenLength = arguments.length - 2;
    if (childrenLength === 1) {
        props.children = children;
    } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
            childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
    }

    // 处理type上的defaultProps
    if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (propName in defaultProps) {
            if (props[propName] === undefined) {
                props[propName] = defaultProps[propName];
            }
        }
    }
    // 最后调用ReactElement
    return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current, // 从源码文件得知这里就是Fiber
        props
    );
}

// 下面是源码中的一些对象和函数
// 保留属性。self 和 source 是开发环境下才会用到的属性,用来修改元素的value属性。
const RESERVED_PROPS = {
    key: true,
    ref: true,
    __self: true,
    __source: true
};
function hasValidRef(config) {
    // ... __DEV__环境下的代码省略
    return config.ref !== undefined;
}
function hasValidKey(config) {
    // ... __DEV__环境下的代码省略
    return config.key !== undefined;
}

从上面的源码中可以看到 key 和 ref 不能通过 props 传递,对这两兄弟做了特殊处理

ReactElement

const ReactElement = function (type, key, ref, self, source, owner, props) {
    const element = {
        // 独特的标识为React元素
        $$typeof: REACT_ELEMENT_TYPE,

        // 元素的内置属性
        type: type,
        key: key,
        ref: ref,
        props: props,

        // 记录负责创建此元素的组件, 其实就是Fiber.
        _owner: owner
    };
    // ... __DEV__环境下的代码省略
    return element;
};

到这里虚拟 DOM 的模样我们已经清晰可见,虚拟 DOM 是怎么转化为真实 DOM 的,那就是 ReactDOM.render 来干的事情。

ReactDOM.render

export function render(element: React$Element<any>, container: Container, callback: ?Function) {
    // 判断要挂在的真实dom是否有效
    invariant(isValidContainer(container), 'Target container is not a DOM element.');
    // ...__DEV__代码省略
    return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

function legacyRenderSubtreeIntoContainer(
    parentComponent: ?React$Component<any, any>,
    children: ReactNodeList,
    container: Container,
    forceHydrate: boolean,
    callback: ?Function
) {
    // ...__DEV__代码省略
    let root: RootType = (container._reactRootContainer: any);
    let fiberRoot;
    if (!root) {
        // 初始挂载
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
        fiberRoot = root._internalRoot;
        if (typeof callback === 'function') {
            const originalCallback = callback;
            callback = function () {
                const instance = getPublicRootInstance(fiberRoot);
                originalCallback.call(instance);
            };
        }
        // 初始化挂载不能是批量的
        unbatchedUpdates(() => {
            updateContainer(children, fiberRoot, parentComponent, callback);
        });
    } else {
        fiberRoot = root._internalRoot;
        if (typeof callback === 'function') {
            const originalCallback = callback;
            callback = function () {
                const instance = getPublicRootInstance(fiberRoot);
                originalCallback.call(instance);
            };
        }
        // 更新
        updateContainer(children, fiberRoot, parentComponent, callback);
    }
    return getPublicRootInstance(fiberRoot);
}

export function isValidContainer(node: mixed): boolean {
    return !!(
        node &&
        (node.nodeType === ELEMENT_NODE ||
            node.nodeType === DOCUMENT_NODE ||
            node.nodeType === DOCUMENT_FRAGMENT_NODE ||
            (node.nodeType === COMMENT_NODE && (node: any).nodeValue === ' react-mount-point-unstable '))
    );
}
export const ELEMENT_NODE = 1;
export const TEXT_NODE = 3;
export const COMMENT_NODE = 8;
export const DOCUMENT_NODE = 9;
export const DOCUMENT_FRAGMENT_NODE = 11;
// 能做挂载的container类型一目了然

为什么需要虚拟 DOM

有些面试官常常会问:虚拟 DOM 的性能是否更高。

这是一个典型的陷阱题。如果你对虚拟 dom 够清楚,就应该明白虚拟 DOM 的优势是在于可以差量更新真实 DOM,相比之下,以前的模板语法本质是拼接字符串,生成真实 HTML 挂载到 innerHtml 上,而我们的虚拟 DOM 需要对新旧树进行 diff,这是一个比拼接字符串更加耗时的过程,这么来看虚拟 DOM 简直完败。

然而,DOM 操作的性能和 js 计算的性能不在一个量级,极少量的 DOM 操作耗费的性能足以支撑大量的 JS 计算。在真实场景中,我们往往是仅仅更新少量的数据,只需要对极少真实 DOM 进行操作,在这种场景下,虚拟 DOM 完胜。

虚拟 DOM 解决的关键问题:

  1. 为数据驱动视图思想提供载体,UI=render(data)
  2. 解决跨平台问题,同一套虚拟 DOM,可以对接不同平台的渲染逻辑
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yokiizx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值