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

1,020 阅读3分钟

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

打包工具: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片段,在琢磨一下)

  1. 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()
      
  2. 最直接的好处:

    1. vue3更好的支持按需引入,利用tree shaking,可以使代码体积变的更小,加载和解析速度更快
    2. 代码的结构更加清晰,更利于维护 和 写大型项目(这也是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

先说结论:

  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 面向对象编程的同学,可以看:zhuanlan.zhihu.com/p/158828668

码字不易,点赞鼓励!