从打包结果浅析vue2/vue3/react细节(函数式vs面向对象)

打包工具:rollup(对比webpack无额外注入代码)

打包前的代码(保证渲染出来的内容是一样的)

vue2

  1. // main.js
  2. import Vue from 'vue'
  3. import App from './App.vue'
  4. new Vue({
  5. el: '#app',
  6. render: h => h(App)
  7. })
  8. // './App.vue'
  9. <template>
  10. <div id="app">
  11. <div>11 + {{a}} + {{count}}</div>
  12. <button @click="count++">Click me</button>
  13. <child></child>
  14. </div>
  15. </template>
  16. <script>
  17. import child from './test.vue'
  18. export default {
  19. components: {
  20. child,
  21. },
  22. data () {
  23. return {
  24. a: 'aaa',
  25. count: 0
  26. }
  27. }
  28. }
  29. </script>
  30. // './test.vue'
  31. <template>
  32. <div>11 + {{a}} + {{name}}</div>
  33. </template>
  34. <script>
  35. export default {
  36. data () {
  37. return {
  38. a: 'aaa',
  39. name: 'child'
  40. }
  41. }
  42. }
  43. </script>

vue3

  1. // main.js
  2. import { createApp } from 'vue'
  3. import App from './App.vue'
  4. const app = createApp(App)
  5. app.mount('#app')
  6. // './App.vue'
  7. <template>
  8. <div id="app">
  9. <div>11 + {{a}} + {{countProxy.count}}</div>
  10. <button @click="countProxy.count++">Click me</button>
  11. <child></child>
  12. </div>
  13. </template>
  14. <script setup>
  15. import child from './child.vue'
  16. import { reactive } from 'vue'
  17. const a = 'aaa'
  18. const countProxy = reactive({
  19. count: 0
  20. })
  21. </script>
  22. // './child.vue'
  23. <template>
  24. <div>11 + {{a}} + {{nameProxy.name}}</div>
  25. </template>
  26. <script setup>
  27. import { reactive } from 'vue'
  28. const a = 'aaa'
  29. const nameProxy = reactive({
  30. name: 'child'
  31. })
  32. </script>

react (17版本"react": "^17.0.0"

  1. // main.js
  2. import React from 'react'
  3. import ReactDOM from 'react-dom'
  4. import App from './app'
  5. ReactDOM.render(
  6. <React.StrictMode>
  7. <App />
  8. </React.StrictMode>,
  9. document.getElementById('root')
  10. );
  11. // app.jsx
  12. import React, { useState } from 'react';
  13. import ChildTest from './testjsx'
  14. const App = () => {
  15. const a = 'aaa'
  16. const [count, setCount] = useState(0);
  17. return (
  18. <>
  19. <div>11 + {a} + {count}</div>
  20. <button onClick={() => setCount(count + 1)}>
  21. Click me
  22. </button>
  23. <ChildTest />
  24. </>
  25. );
  26. };
  27. export default App;
  28. // './testjsx'
  29. import React, { useState } from 'react';
  30. const ChildTest = () => {
  31. const a = 'aaa'
  32. const [name, setName] = useState('child');
  33. return (
  34. <div>11 + {a} + {name}</div>
  35. );
  36. }
  37. export default ChildTest

打包后的结果

(下面的结果是用vite打包,底层是rollup 无额外代码注入。并且vite打包可以把vendor给分离开,让组件的结构更加清晰)

vue2

  1. 打包后目录结构
  2. dist
  3. ├── assets
  4. ├── index.3a1d85d8.js
  5. └── vendor.28ac9a6d.js
  6. └── index.html
  7. // index.3a1d85d8.js
  8. import { V as Vue } from "./vendor.28ac9a6d.js";
  9. var render$1 = function() {
  10. var _vm = this;
  11. var _h = _vm.$createElement;
  12. var _c = _vm._self._c || _h;
  13. return _c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.name))]);
  14. };
  15. var staticRenderFns$1 = [];
  16. function normalizeComponent(scriptExports, render2, staticRenderFns2, functionalTemplate, injectStyles, scopeId, moduleIdentifier, shadowMode) {
  17. // ... 此处省略 50行
  18. }
  19. const __vue2_script$1 = {
  20. data() {
  21. return {
  22. a: "aaa",
  23. name: "child"
  24. };
  25. }
  26. };
  27. const __cssModules$1 = {};
  28. var __component__$1 = /* @__PURE__ */ normalizeComponent(__vue2_script$1, render$1, staticRenderFns$1, false, __vue2_injectStyles$1, null, null, null);
  29. function __vue2_injectStyles$1(context) {
  30. for (let o in __cssModules$1) {
  31. this[o] = __cssModules$1[o];
  32. }
  33. }
  34. var child = /* @__PURE__ */ function() {
  35. return __component__$1.exports;
  36. }();
  37. var render = function() {
  38. var _vm = this;
  39. var _h = _vm.$createElement;
  40. var _c = _vm._self._c || _h;
  41. return _c(
  42. "div", { attrs: { "id": "app" } },
  43. [
  44. _c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.count))]),
  45. _c("button", { on: { "click": function($event) {
  46. _vm.count++;
  47. } } },
  48. [_vm._v("Click me")]),
  49. _c("child")
  50. ], 1
  51. );
  52. };
  53. var staticRenderFns = [];
  54. const __vue2_script = {
  55. components: {
  56. child
  57. },
  58. data() {
  59. return {
  60. a: "aaa",
  61. count: 0
  62. };
  63. }
  64. };
  65. const __cssModules = {};
  66. var __component__ = /* @__PURE__ */ normalizeComponent(__vue2_script, render, staticRenderFns, false, __vue2_injectStyles, null, null, null);
  67. function __vue2_injectStyles(context) {
  68. for (let o in __cssModules) {
  69. this[o] = __cssModules[o];
  70. }
  71. }
  72. var App = /* @__PURE__ */ function() {
  73. return __component__.exports;
  74. }();
  75. new Vue({
  76. el: "#app",
  77. render: (h) => h(App)
  78. });
  79. // vendor.28ac9a6d.js
  80. vue2的源代码

vue3

  1. 打包后目录结构
  2. dist
  3. ├── assets
  4. ├── index.8a6206cf.js
  5. └── vendor.be99b281.js
  6. └── index.html
  7. // index.8a6206cf.js
  8. 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";
  9. const _sfc_main$1 = {
  10. setup(__props) {
  11. const a = "aaa";
  12. const nameProxy = reactive({
  13. name: "child"
  14. });
  15. return (_ctx, _cache) => {
  16. return openBlock(), createElementBlock("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(nameProxy).name), 1);
  17. };
  18. }
  19. };
  20. const _hoisted_1 = { id: "app" };
  21. const _sfc_main = {
  22. setup(__props) {
  23. const a = "aaa";
  24. const countProxy = reactive({
  25. count: 0
  26. });
  27. return (_ctx, _cache) => {
  28. return openBlock(),
  29. createElementBlock("div", _hoisted_1, [
  30. createBaseVNode("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(countProxy).count), 1),
  31. createBaseVNode("button", {
  32. onClick: _cache[0] || (_cache[0] = ($event) => unref(countProxy).count++)
  33. }, "Click me"),
  34. createVNode(_sfc_main$1)
  35. ]);
  36. };
  37. }
  38. };
  39. const app = createApp(_sfc_main);
  40. app.mount("#app");
  41. // vendor.be99b281.js
  42. vue3的源代码

react

  1. 打包后目录结构
  2. dist
  3. ├── assets
  4. ├── index.66a13ace.js
  5. └── vendor.5719e053.js
  6. └── index.html
  7. // index.66a13ace.js
  8. import { _ as _react_17_0_2_react, R as React, a as ReactDOM } from "./vendor.5719e053.js";
  9. const ChildTest = () => {
  10. const a = "aaa";
  11. const [name, setName] = _react_17_0_2_react.exports.useState("child");
  12. return React.createElement("div", null, "11 + ", a, " + ", name);
  13. };
  14. const App = () => {
  15. const a = "aaa";
  16. const [count, setCount] = _react_17_0_2_react.exports.useState(0);
  17. return React.createElement(
  18. React.Fragment,
  19. null,
  20. React.createElement("div", null, "11 + ", a, " + ", count),
  21. React.createElement("button", {
  22. onClick: () => setCount(count + 1)
  23. }, "Click me"),
  24. React.createElement(ChildTest, null)
  25. );
  26. };
  27. ReactDOM.render( React.createElement(React.StrictMode, null, React.createElement(App, null)), document.getElementById("root"));
  28. // vendor.5719e053.js
  29. react的源代码

另外提一嘴,vue3和vue2都支持jsx,以下是vue3和react的jsx写法对比

结论:写法几乎已经很接近了,只有一些响应式api用法略不同

(以下举例,渲染出来是同样的内容)

  1. // react ---> app.jsx
  2. import React, { useState } from 'react';
  3. import ChildTest from './testjsx'
  4. const App = () => {
  5. const a = 'aaa'
  6. const [count, setCount] = useState(0);
  7. return (
  8. <>
  9. <div>11 + {a} + {count}</div>
  10. <button onClick={() => setCount(count + 1)}>
  11. Click me
  12. </button>
  13. <ChildTest />
  14. </>
  15. );
  16. };
  17. export default App;
  18. // vue3 jsx ---> app.jsx
  19. import { ref } from 'vue'
  20. import ChildTest from './testjsx'
  21. export default {
  22. setup () {
  23. const a = 'aaa'
  24. const count = ref(0)
  25. return () => (
  26. <>
  27. <div>11 + {a} + {count}</div>
  28. <button onClick={() => count.value++}>
  29. Click me
  30. </button>
  31. <ChildTest />
  32. </>
  33. )
  34. }
  35. }

分析对比打包结果

vue2 vs vue3

直接说结论(大家可以对比一下 下面2个app.vue片段,在琢磨一下)

  1. vue2是面向对象编程,vue3是函数式编程
    vue2内的数据,从实例(VueComponent实例,this指向的就是这个)上获得

    • vue2会把route和vuex之类的函数或对象data都放到 对应组件的 VueComponent实例(this) 上去
    • 所以我们可以直接用this.xx去访问 route store等。比如

      1. data () {
      2. aaa: 123 // 在编译的时候,会被加到 VueComponent实例 上去
      3. },
      4. methods: {
      5. function someone () {
      6. console.log(this.aaa)
      7. console.log(this.$route.path)
      8. this.$router.push()
      9. console.log(this.$store.state.xx)
      10. // this就是指向当前组件的 VueComponent实例
      11. }
      12. }

      vue3直接从函数作用域内获得(所以代码看上去很清晰,无需写this)

    • 但使用 route 和 vuex 都需要额外引入了(按需引到函数作用域内去)。比如

      1. import { useRoute, useRouter } from 'vue-router' // 这样可以按需引入
      2. import { useStore } from 'vuex' // 这样可以按需引入
      3. const route = useRoute()
      4. const router = useRouter()
      5. const store = useStore()
      6. const aaa = 123
      7. function someone () {
      8. console.log(aaa)
      9. console.log(route.path)
      10. router.push()
      11. console.log(store.state.xx)
      12. }
      13. someone()
  2. 最直接的好处:

    1. vue3更好的支持按需引入,利用tree shaking,可以使代码体积变的更小,加载和解析速度更快
    2. 代码的结构更加清晰,更利于维护 和 写大型项目(这也是vue3能写组合式api的原理)

大家可以对比一下 下面2个app.vue片段,在琢磨一下

  1. // vue2 的app.vue片段
  2. var render = function() {
  3. var _vm = this;
  4. var _h = _vm.$createElement;
  5. var _c = _vm._self._c || _h;
  6. return _c(
  7. "div", { attrs: { "id": "app" } },
  8. [
  9. _c("div", [_vm._v("11 + " + _vm._s(_vm.a) + " + " + _vm._s(_vm.count))]),
  10. _c("button", { on: { "click": function($event) {
  11. _vm.count++;
  12. } } },
  13. [_vm._v("Click me")]),
  14. _c("child")
  15. ], 1
  16. );
  17. };
  18. // vue3 的app.vue片段
  19. const _sfc_main = {
  20. setup(__props) {
  21. const a = "aaa";
  22. const countProxy = reactive({
  23. count: 0
  24. });
  25. return (_ctx, _cache) => {
  26. return openBlock(),
  27. createElementBlock("div", _hoisted_1, [
  28. createBaseVNode("div", null, "11 + " + toDisplayString(a) + " + " + toDisplayString(unref(countProxy).count), 1),
  29. createBaseVNode("button", {
  30. onClick: _cache[0] || (_cache[0] = ($event) => unref(countProxy).count++)
  31. }, "Click me"),
  32. createVNode(_sfc_main$1)
  33. ]);
  34. };
  35. }
  36. };

vue3 vs react

先说结论:

  1. 2者都是函数式编程,组件都是从自身的所用域内去获取数据
  2. 思路和结构都比较接近(vue3也有hooks的概念,支持jsx,整体和react已经很接近)

因为比较接近,所以这里就不在举例了,大家可以看上面给出的打包结果对比一下

总结

vue2 vs vue3

  1. vue2是面向对象编程,vue3是函数式编程
    • 函数式编程的好处:体现在编码上是可以使用组合式api,可以让逻辑更加集中和清晰。(组合式api介绍,官网
  2. vue3有更好的按需引入支持,使得代码体积变得更小(可以让vue程序的体积和占的内存都变小,提升加载和渲染速度!)

vue3 vs react

  1. 2者都是函数式编程,组件都是从自身的所用域内去获取数据,都有很好的按需引入支持
  2. 打包后的组件的处理的思路组件结构都比较接近(vue3也有hooks的概念,支持jsx,整体和react已经很接近)

还是想更多维度的了解,函数式编程 vs 面向对象编程的同学,可以看:https://zhuanlan.zhihu.com/p/158828668

码字不易,点赞鼓励!