从打包结果浅析vue2/vue3/react细节(函数式vs面向对象)
打包工具:rollup(对比webpack无额外注入代码)
- 想进一步了解2者对比的话,可以看我另一篇:rollup打包产物解析及原理(对比webpack)
打包前的代码(保证渲染出来的内容是一样的)
vue2
// main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App)
})
// './App.vue'
<template>
<div id="app">
<div>11 + {{a}} + {{count}}</div>
<button @click="count++">Click me</button>
<child></child>
</div>
</template>
<script>
import child from './test.vue'
export default {
components: {
child,
},
data () {
return {
a: 'aaa',
count: 0
}
}
}
</script>
// './test.vue'
<template>
<div>11 + {{a}} + {{name}}</div>
</template>
<script>
export default {
data () {
return {
a: 'aaa',
name: 'child'
}
}
}
</script>
vue3
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
// './App.vue'
<template>
<div id="app">
<div>11 + {{a}} + {{countProxy.count}}</div>
<button @click="countProxy.count++">Click me</button>
<child></child>
</div>
</template>
<script setup>
import child from './child.vue'
import { reactive } from 'vue'
const a = 'aaa'
const countProxy = reactive({
count: 0
})
</script>
// './child.vue'
<template>
<div>11 + {{a}} + {{nameProxy.name}}</div>
</template>
<script setup>
import { reactive } from 'vue'
const a = 'aaa'
const nameProxy = reactive({
name: 'child'
})
</script>
react (17版本"react": "^17.0.0"
)
// main.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// app.jsx
import React, { useState } from 'react';
import ChildTest from './testjsx'
const App = () => {
const a = 'aaa'
const [count, setCount] = useState(0);
return (
<>
<div>11 + {a} + {count}</div>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<ChildTest />
</>
);
};
export default App;
// './testjsx'
import React, { useState } from 'react';
const ChildTest = () => {
const a = 'aaa'
const [name, setName] = useState('child');
return (
<div>11 + {a} + {name}</div>
);
}
export default ChildTest
打包后的结果
(下面的结果是用vite打包,底层是rollup 无额外代码注入。并且vite打包可以把vendor给分离开,让组件的结构更加清晰)
vue2
打包后目录结构
dist
├── assets
│ ├── index.3a1d85d8.js
│ └── vendor.28ac9a6d.js
└── index.html
// index.3a1d85d8.js
import { V as Vue } from "./vendor.28ac9a6d.js";
var render$1 = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.name))]);
};
var staticRenderFns$1 = [];
function normalizeComponent(scriptExports, render2, staticRenderFns2, functionalTemplate, injectStyles, scopeId, moduleIdentifier, shadowMode) {
// ... 此处省略 50行
}
const __vue2_script$1 = {
data() {
return {
a: "aaa",
name: "child"
};
}
};
const __cssModules$1 = {};
var __component__$1 = /* @__PURE__ */ normalizeComponent(__vue2_script$1, render$1, staticRenderFns$1, false, __vue2_injectStyles$1, null, null, null);
function __vue2_injectStyles$1(context) {
for (let o in __cssModules$1) {
this[o] = __cssModules$1[o];
}
}
var child = /* @__PURE__ */ function() {
return __component__$1.exports;
}();
var render = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div", { attrs: { "id": "app" } },
[
_c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.count))]),
_c("button", { on: { "click": function($event) {
_vm.count++;
} } },
[_vm._v("Click me")]),
_c("child")
], 1
);
};
var staticRenderFns = [];
const __vue2_script = {
components: {
child
},
data() {
return {
a: "aaa",
count: 0
};
}
};
const __cssModules = {};
var __component__ = /* @__PURE__ */ normalizeComponent(__vue2_script, render, staticRenderFns, false, __vue2_injectStyles, null, null, null);
function __vue2_injectStyles(context) {
for (let o in __cssModules) {
this[o] = __cssModules[o];
}
}
var App = /* @__PURE__ */ function() {
return __component__.exports;
}();
new Vue({
el: "#app",
render: (h) => h(App)
});
// vendor.28ac9a6d.js
是vue2的源代码
vue3
打包后目录结构
dist
├── assets
│ ├── index.8a6206cf.js
│ └── vendor.be99b281.js
└── index.html
// index.8a6206cf.js
import { r as reactive, o as openBlock, c as createElementBlock, t as toDisplayString, u as unref, a as createBaseVNode, b as createVNode, d as createApp } from "./vendor.be99b281.js";
const _sfc_main$1 = {
setup(__props) {
const a = "aaa";
const nameProxy = reactive({
name: "child"
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(nameProxy).name), 1);
};
}
};
const _hoisted_1 = { id: "app" };
const _sfc_main = {
setup(__props) {
const a = "aaa";
const countProxy = reactive({
count: 0
});
return (_ctx, _cache) => {
return openBlock(),
createElementBlock("div", _hoisted_1, [
createBaseVNode("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(countProxy).count), 1),
createBaseVNode("button", {
onClick: _cache[0] || (_cache[0] = ($event) => unref(countProxy).count++)
}, "Click me"),
createVNode(_sfc_main$1)
]);
};
}
};
const app = createApp(_sfc_main);
app.mount("#app");
// vendor.be99b281.js
是vue3的源代码
react
打包后目录结构
dist
├── assets
│ ├── index.66a13ace.js
│ └── vendor.5719e053.js
└── index.html
// index.66a13ace.js
import { _ as _react_17_0_2_react, R as React, a as ReactDOM } from "./vendor.5719e053.js";
const ChildTest = () => {
const a = "aaa";
const [name, setName] = _react_17_0_2_react.exports.useState("child");
return React.createElement("div", null, "11 + ", a, " + ", name);
};
const App = () => {
const a = "aaa";
const [count, setCount] = _react_17_0_2_react.exports.useState(0);
return React.createElement(
React.Fragment,
null,
React.createElement("div", null, "11 + ", a, " + ", count),
React.createElement("button", {
onClick: () => setCount(count + 1)
}, "Click me"),
React.createElement(ChildTest, null)
);
};
ReactDOM.render( React.createElement(React.StrictMode, null, React.createElement(App, null)), document.getElementById("root"));
// vendor.5719e053.js
是react的源代码
另外提一嘴,vue3和vue2都支持jsx,以下是vue3和react的jsx写法对比
结论:写法几乎已经很接近了,只有一些响应式api用法略不同
(以下举例,渲染出来是同样的内容)
// react ---> app.jsx
import React, { useState } from 'react';
import ChildTest from './testjsx'
const App = () => {
const a = 'aaa'
const [count, setCount] = useState(0);
return (
<>
<div>11 + {a} + {count}</div>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<ChildTest />
</>
);
};
export default App;
// vue3 jsx ---> app.jsx
import { ref } from 'vue'
import ChildTest from './testjsx'
export default {
setup () {
const a = 'aaa'
const count = ref(0)
return () => (
<>
<div>11 + {a} + {count}</div>
<button onClick={() => count.value++}>
Click me
</button>
<ChildTest />
</>
)
}
}
分析对比打包结果
vue2 vs vue3
直接说结论(大家可以对比一下 下面2个app.vue片段,在琢磨一下)
vue2是面向对象编程,vue3是函数式编程。
vue2内的数据,从实例(VueComponent实例,this指向的就是这个)上获得- vue2会把route和vuex之类的函数或对象和data都放到 对应组件的 VueComponent实例(this) 上去
所以我们可以直接用this.xx去访问 route store等。比如
data () {
aaa: 123 // 在编译的时候,会被加到 VueComponent实例 上去
},
methods: {
function someone () {
console.log(this.aaa)
console.log(this.$route.path)
this.$router.push()
console.log(this.$store.state.xx)
// this就是指向当前组件的 VueComponent实例
}
}
vue3直接从函数作用域内获得(所以代码看上去很清晰,无需写this)
但使用 route 和 vuex 都需要额外引入了(按需引到函数作用域内去)。比如
import { useRoute, useRouter } from 'vue-router' // 这样可以按需引入
import { useStore } from 'vuex' // 这样可以按需引入
const route = useRoute()
const router = useRouter()
const store = useStore()
const aaa = 123
function someone () {
console.log(aaa)
console.log(route.path)
router.push()
console.log(store.state.xx)
}
someone()
最直接的好处:
- vue3更好的支持按需引入,利用tree shaking,可以使代码体积变的更小,加载和解析速度更快
- 代码的结构更加清晰,更利于维护 和 写大型项目(这也是vue3能写组合式api的原理)
大家可以对比一下 下面2个app.vue片段,在琢磨一下
// vue2 的app.vue片段
var render = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c(
"div", { attrs: { "id": "app" } },
[
_c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.count))]),
_c("button", { on: { "click": function($event) {
_vm.count++;
} } },
[_vm._v("Click me")]),
_c("child")
], 1
);
};
// vue3 的app.vue片段
const _sfc_main = {
setup(__props) {
const a = "aaa";
const countProxy = reactive({
count: 0
});
return (_ctx, _cache) => {
return openBlock(),
createElementBlock("div", _hoisted_1, [
createBaseVNode("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(countProxy).count), 1),
createBaseVNode("button", {
onClick: _cache[0] || (_cache[0] = ($event) => unref(countProxy).count++)
}, "Click me"),
createVNode(_sfc_main$1)
]);
};
}
};
vue3 vs react
先说结论:
- 2者都是函数式编程,组件都是从自身的所用域内去获取数据
- 思路和结构都比较接近(vue3也有hooks的概念,支持jsx,整体和react已经很接近)
因为比较接近,所以这里就不在举例了,大家可以看上面给出的打包结果对比一下
总结
vue2 vs vue3
- vue2是面向对象编程,vue3是函数式编程。
- 函数式编程的好处:体现在编码上是可以使用组合式api,可以让逻辑更加集中和清晰。(组合式api介绍,官网)
- vue3有更好的按需引入支持,使得代码体积变得更小(可以让vue程序的体积和占的内存都变小,提升加载和渲染速度!)
vue3 vs react
- 2者都是函数式编程,组件都是从自身的所用域内去获取数据,都有很好的按需引入支持
- 打包后的组件的处理的思路 和 组件结构都比较接近(vue3也有hooks的概念,支持jsx,整体和react已经很接近)
还是想更多维度的了解,函数式编程 vs 面向对象编程的同学,可以看:https://zhuanlan.zhihu.com/p/158828668
码字不易,点赞鼓励!