【源码】vue3 为什么说跨平台能力更强

【公司内部分享】探索Vue.js框架内部原理和实现机制,旨在理解其核心概念和技术细节,以便更好地应用和定制Vue.js。

相关文章

vue3 为什么说跨平台能力更强


一、渲染器 API

Vue 3 引入了渲染器 API,使得可以自定义渲染目标。这意味着 Vue 3 可以将组件渲染到各种不同的目标上,从而支持更多的应用场景和平台。

二、createRenderer

用于创建一个自定义渲染器。它接受一个包含渲染器选项的对象作为参数,并返回一个渲染器实例。渲染器选项对象包含用于控制渲染过程的方法,比如 createElement、patchProp、insert 和 remove 等。

  • createElement(type: string, props?: Object, children?: string | VNode | VNode[]) => VNode:创建一个虚拟节点。

  • patchProp(el: Element, key: string, prevValue: any, nextValue: any, isSVG?: boolean, prevChildren?: VNode[]):更新 DOM 元素的属性。这个方法用于在更新期间对 DOM 元素的属性进行处理。

  • insert(child: Node, parent: Node, anchor?: Node | null):将节点插入到父节点中。用于处理新节点的插入。

  • remove(child: Node):从父节点中移除节点。用于处理节点的移除。

二、应用场景

服务器端渲染(SSR)

渲染器 API 可以用于在服务器端渲染 Vue 组件,生成 HTML,并将其发送给客户端。这对于提高首屏加载性能和 SEO 非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const express = require('express');
const { createRenderer } = require('vue');
const { renderToString } = require('@vue/server-renderer');

const app = express();

// 创建自定义渲染器
const renderer = createRenderer({
// 创建虚拟节点
createElement(type, props, children) {
return {
type,
props,
children
};
},
// 更新 DOM 元素的属性
patchProp(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue);
},
// 将节点插入到父节点中
insert(child, parent, anchor) {
if (anchor) {
parent.insertBefore(child, anchor);
} else {
parent.appendChild(child);
}
},
// 从父节点中移除节点
remove(child) {
child.parentNode.removeChild(child);
}
});

// 定义一个简单的 Vue 组件
const App = {
data() {
return {
message: 'Hello, Vue 3 Server-Side Rendering!'
};
},
// 渲染函数,返回一个 VNode
render() {
return {
type: 'div',
children: this.message
};
}
};

// 处理路由
app.get('/', async (req, res) => {
try {
// 渲染 Vue 组件为字符串
const html = await renderToString(renderer, App);
res.send(html);
} catch (error) {
res.status(500).send('Server Error');
}
});

// 启动服务器
const port = 3000;
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});

静态网站生成器(SSG)

渲染器 API 可以用于在构建时(而不是在运行时)将 Vue 组件渲染为静态 HTML 文件,从而实现静态网站生成器的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const fs = require('fs');
const path = require('path');
const { createRenderer } = require('vue');
const { renderToString } = require('@vue/server-renderer');

// 创建自定义渲染器
const renderer = createRenderer({
// 创建虚拟节点
createElement(type, props, children) {
return {
type,
props,
children
};
},
// 更新 DOM 元素的属性
patchProp(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue);
},
// 将节点插入到父节点中
insert(child, parent, anchor) {
parent.insertBefore(child, anchor);
},
// 从父节点中移除节点
remove(child) {
child.parentNode.removeChild(child);
}
});

// 定义一个简单的 Vue 组件
const App = {
data() {
return {
title: 'Static Site Generator',
content: 'This is a static site generated by Vue and custom renderer.'
};
},
// 渲染函数,返回一个 VNode
render() {
return {
type: 'html',
children: [
{
type: 'head',
children: [
{ type: 'title', children: this.title }
]
},
{
type: 'body',
children: [
{ type: 'h1', children: this.title },
{ type: 'p', children: this.content }
]
}
]
};
}
};

// 渲染 Vue 组件为 HTML 字符串
renderToString(renderer, App).then(html => {
// 保存 HTML 字符串到静态文件
fs.writeFileSync(path.resolve(__dirname, 'index.html'), html);
console.log('Static site generated successfully!');
}).catch(error => {
console.error('Error generating static site:', error);
});

桌面应用程序

渲染器 API 可以用于将 Vue 组件渲染到桌面应用程序的窗口中,从而实现基于 Web 技术的桌面应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const { app, BrowserWindow } = require('electron');
const { createRenderer } = require('vue');

// 创建自定义渲染器
const renderer = createRenderer({
// 创建虚拟节点
createElement(type, props, children) {
return {
type,
props,
children
};
},
// 更新 DOM 元素的属性
patchProp(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue);
},
// 将节点插入到父节点中
insert(child, parent, anchor) {
parent.insertBefore(child, anchor);
},
// 从父节点中移除节点
remove(child) {
child.parentNode.removeChild(child);
}
});

// 创建 Vue 应用
const App = {
data() {
return {
message: 'Hello, Desktop App!'
};
},
// 渲染函数,返回一个 VNode
render() {
return {
type: 'div',
children: this.message
};
}
};

// 等待 Electron 准备就绪
app.whenReady().then(() => {
// 创建浏览器窗口
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});

// 加载 Vue 组件到窗口中
mainWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent('<div id="app"></div>'));

// 渲染 Vue 组件到窗口中
renderer.render(createVNode(App), mainWindow.document.getElementById('app'));
});

移动应用程序

渲染器 API 可以用于将 Vue 组件渲染到移动应用程序的 WebView 中,从而实现基于 Web 技术的移动应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const { createRenderer } = require('vue');
const { createApp } = require('vue');
const { defineComponent, h } = require('vue');
const WebView = require('react-native-webview');

// 创建自定义渲染器
const renderer = createRenderer({
// 创建虚拟节点
createElement(type, props, children) {
return {
type,
props,
children
};
},
// 更新 DOM 元素的属性
patchProp(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue);
},
// 将节点插入到父节点中
insert(child, parent, anchor) {
parent.insertBefore(child, anchor);
},
// 从父节点中移除节点
remove(child) {
child.parentNode.removeChild(child);
}
});

// 定义一个简单的 Vue 组件
const App = defineComponent({
data() {
return {
message: 'Hello, Mobile App!'
};
},
// 渲染函数,返回一个 VNode
render() {
return h('div', this.message);
}
});

// 创建 Vue 应用实例
const app = createApp(App);

// 使用自定义渲染器渲染应用程序
app.use({
install(app) {
app.config.globalProperties.$renderer = renderer;
}
});

// 将应用程序挂载到 WebView 中
WebView.injectJavaScript(`
const root = document.createElement('div');
document.body.appendChild(root);
app.mount(root);
`);

小程序

渲染器 API 可以用于将 Vue 组件渲染到小程序的自定义组件中,从而实现在小程序中使用 Vue 的功能。

  • Vue 组件 MyComponent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const { createApp, h } = require('vue');

    // 定义一个简单的 Vue 组件
    const MyComponent = {
    data() {
    return {
    message: 'Hello, Mini Program!'
    };
    },
    // 渲染函数,返回一个 VNode
    render() {
    return h('view', {}, this.message);
    }
    };

    // 创建 Vue 应用实例
    const app = createApp(MyComponent);

    // 将 Vue 组件挂载到 DOM 中
    const vnode = app.mount();
  • 微信小程序自定义组件 vue-component 的 vue-component.wxml 文件

    1
    <web-view id="web-view" src="{{ src }}"></web-view>
  • 微信小程序页面中引入 vue-component 组件

    1
    2
    <import src="../components/vue-component.vue" />
    <vue-component src="{{ vueSrc }}"></vue-component>
  • 微信小程序页面中的逻辑部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Page({
    data: {
    vueSrc: 'data:text/html;charset=utf-8,' + encodeURIComponent('<div id="app"></div>')
    },
    onReady: function () {
    // 使用微信小程序自定义组件的接口获取 web-view 组件实例
    const webView = this.selectComponent('#web-view');

    // 在 web-view 组件中渲染 Vue 组件
    webView.postMessage({
    type: 'render',
    vnode: vnode
    });
    }
    });

其他自定义渲染目标

渲染器 API 还可以用于将 Vue 组件渲染到其他自定义渲染目标上,例如 Canvas、PDF、SVG 等。

  • Canvas

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    const { createRenderer } = require('vue');

    // 创建自定义渲染器
    const renderer = createRenderer({
    // 创建虚拟节点
    createElement(type, props, children) {
    return {
    type,
    props,
    children
    };
    },
    // 更新 DOM 元素的属性
    patchProp(el, key, prevValue, nextValue) {
    // 不需要更新 Canvas 上的属性
    },
    // 将节点插入到父节点中
    insert(child, parent, anchor) {
    // 不需要将节点插入到 Canvas 中
    },
    // 从父节点中移除节点
    remove(child) {
    // 不需要从 Canvas 中移除节点
    }
    });

    // 定义一个简单的 Vue 组件
    const MyComponent = {
    data() {
    return {
    message: 'Hello, Canvas!'
    };
    },
    // 渲染函数,返回一个 VNode
    render() {
    return {
    type: 'text',
    props: {
    x: 50,
    y: 50,
    text: this.message,
    fillStyle: 'black'
    }
    };
    }
    };

    // 创建 Vue 应用实例
    const { createApp, h } = require('vue');
    const app = createApp(MyComponent);

    // 使用自定义渲染器渲染应用程序
    const vnode = app.mount();

    // 获取 Canvas 元素
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 渲染组件到 Canvas 上
    renderer.render(vnode, ctx);
  • pdf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    import { createApp } from 'vue';
    import jsPDF from 'jspdf';

    // 创建自定义渲染器
    const renderer = {
    // 创建虚拟节点
    createElement(type, props, children) {
    return {
    type,
    props,
    children
    };
    },
    // 更新 DOM 元素的属性
    patchProp(el, key, prevValue, nextValue) {
    // 不需要更新 PDF 上的属性
    },
    // 将节点插入到父节点中
    insert(child, parent, anchor) {
    // 不需要将节点插入到 PDF 中
    },
    // 从父节点中移除节点
    remove(child) {
    // 不需要从 PDF 中移除节点
    }
    };

    // 定义一个简单的 Vue 组件
    const MyComponent = {
    data() {
    return {
    message: 'Hello, PDF!'
    };
    },
    // 渲染函数,返回一个 VNode
    render() {
    return {
    type: 'text',
    props: {
    x: 10,
    y: 10,
    text: this.message,
    fontSize: 12
    }
    };
    }
    };

    // 创建 Vue 应用实例
    const app = createApp(MyComponent);

    // 使用自定义渲染器渲染应用程序
    const vnode = app.mount();

    // 创建 PDF 实例
    const pdf = new jsPDF();

    // 渲染组件到 PDF 上
    renderer.render(vnode, {
    drawText(text, x, y, options) {
    pdf.text(text, x, y, options);
    }
    });

    // 保存 PDF 文件
    pdf.save('example.pdf');
  • svg

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    const { createApp } = require('@vue/runtime-core');

    // 创建自定义渲染器
    const renderer = {
    // 创建虚拟节点
    createElement(type, props, children) {
    return {
    type,
    props,
    children
    };
    },
    // 更新 DOM 元素的属性
    patchProp(el, key, prevValue, nextValue) {
    el.setAttribute(key, nextValue);
    },
    // 将节点插入到父节点中
    insert(child, parent, anchor) {
    parent.appendChild(child);
    },
    // 从父节点中移除节点
    remove(child) {
    child.parentNode.removeChild(child);
    }
    };

    // 定义一个简单的 Vue 组件
    const MyComponent = {
    data() {
    return {
    message: 'Hello, SVG!'
    };
    },
    // 渲染函数,返回一个 VNode
    render() {
    return {
    type: 'svg',
    props: {
    width: '200',
    height: '200'
    },
    children: [
    {
    type: 'text',
    props: {
    x: '10',
    y: '50',
    fill: 'black'
    },
    children: this.message
    }
    ]
    };
    }
    };

    // 创建 Vue 应用实例
    const app = createApp(MyComponent);

    // 使用自定义渲染器渲染应用程序
    const vnode = app.mount();

    // 创建 SVG 元素
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

    // 渲染组件到 SVG 上
    renderer.insert(vnode, svg, null);

    // 将 SVG 添加到 DOM 中
    document.body.appendChild(svg);

【源码】vue3 为什么说跨平台能力更强
https://www.cccccl.com/20240301/源码/vue/vue3 为什么说跨平台能力更强/
作者
Jeffrey
发布于
2024年3月1日
许可协议