最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue3丨从 5 个维度来讲 Vue3 变化

    正文概述 掘金(前端精)   2020-12-25   342

    一些概念

    Vue 和 React 的逻辑复用手段

    到目前为止,

    Vue:Mixins(混入)、HOC(高阶组件)、作用域插槽、Vue Composition API(VCA/组合式API)。

    React:Mixins、HOC、Render Props、Hook。

    我们可以看到都是一段越来越好的成长史,这里就不再举例赘述,本文重心在 VCA,VCA 更偏向于「组合」的概念。

    5个维度来讲 Vue3

    1. 框架

    一个例子先来了解 VCA

    举个简单的例子:

    我们要实现 3 个逻辑

    1. 根据 id 获取表格的数据
    2. 可对表格数据进行搜索过滤
    3. 弹框新增数据到表格中

    Vue2 options api 的处理

    为了阅读质量,省略了部分代码,但不影响我们了解 VCA

    // 逻辑功能(1)
    const getTableDataApi = id => {
      const mockData = {
        1: [
          { id: 11, name: '张三1' },
          { id: 12, name: '李四1' },
          { id: 13, name: '王五1' }
        ],
        2: [
          { id: 21, name: '张三2' },
          { id: 22, name: '李四2' },
          { id: 23, name: '王五2' }
        ]
      };
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(mockData[id] || []);
        }, 1000);
      });
    };
    
    export default {
      name: 'VCADemo',
      components: { Modal },
      data() {
        return {
          // 逻辑功能(1)
          id: 1,
          table: [],
          // 逻辑功能(2)
          search: '',
          // 逻辑功能(3)
          modalShow: false,
          form: {
            id: '',
            name: ''
          }
        };
      },
      computed: {
        // 逻辑功能(2)
        getTableDataBySearch() {
          return this.table.filter(item => item.name.indexOf(this.search) !== -1);
        }
      },
      watch: {
        // 逻辑功能(1)
        id: 'getTableData'
      },
      mounted() {
        // 逻辑功能(1)
        this.getTableData();
      },
      methods: {
        // 逻辑功能(1)
        async getTableData() {
          const res = await getTableDataApi(this.id);
          this.table = res;
        },
        // 逻辑功能(3)
        handleAdd() {
          this.modalShow = true;
        },
        // 逻辑功能(3)
        handlePost() {
          const { id, name } = this.form;
          this.table.push({ id, name });
          this.modalShow = false;
        }
      }
    };
    

    这里只是举例简单的逻辑。如果项目复杂了,逻辑增多了。涉及到一个逻辑的改动,我们就可能需要修改分布在不同位置的相同功能点,提升了维护成本。

    Vue3 composion api 的处理

    让我们来关注逻辑,抽离逻辑,先看主体的代码结构

    import useTable from './composables/useTable';
    import useSearch from './composables/useSearch';
    import useAdd from './composables/useAdd';
    
    export default defineComponent({
      name: 'VCADemo',
      components: { Modal },
      setup() {
        // 逻辑功能(1)
        const { id, table, getTable } = useTable(id);
        // 逻辑功能(2)
        const { search, getTableBySearch } = useSearch(table);
        // 逻辑功能(3)
        const { modalShow, form, handleAdd, handlePost } = useAdd(table);
        return {
          id,
          table,
          getTable,
    
          search,
          getTableBySearch,
    
          modalShow,
          form,
          handleAdd,
          handlePost
        };
      }
    });
    

    setup 接收两个参数:props,context。可以返回一个对象,对象的各个属性都是被 proxy 的,进行监听追踪,将在模板上进行响应式渲染。

    我们来关注其中一个逻辑,useTable,一般来说我们会用 use 开头进行命名,有那味了~

    // VCADemo/composables/useTable.ts
    // 逻辑功能(1)相关
    import { ref, onMounted, watch, Ref } from 'vue';
    import { ITable } from '../index.type';
    
    const getTableApi = (id: number): Promise<ITable[]> => {
      const mockData: { [key: number]: ITable[] } = {
        1: [
          { id: '11', name: '张三1' },
          { id: '12', name: '李四1' },
          { id: '13', name: '王五1' }
        ],
        2: [
          { id: '21', name: '张三2' },
          { id: '22', name: '李四2' },
          { id: '23', name: '王五2' }
        ]
      };
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(mockData[id] || []);
        }, 1000);
      });
    };
    export default function useTable() {
      const id = ref<number>(1);
      const table = ref<ITable[]>([]);
      const getTable = async () => {
        table.value = await getTableApi(id.value);
      };
      onMounted(getTable);
      watch(id, getTable);
      return {
        id,
        table,
        getTable
      };
    }
    

    我们把相关逻辑独立抽离,并「组合」在一起了,可以看到在 vue 包暴露很多独立函数提供我们使用,已经不再 OO 了,嗅到了一股 FP 的气息~

    上面这个例子先说明了 VCA 的带来的好处,Vue3 的核心当然是 VCA,Vue3 不仅仅是 VCA,让我们带着好奇往下看~

    生命周期,Vue2 vs Vue3

    选项式 API(Vue2)Hook inside setup(Vue3)
    beforeCreateNot needed*createdNot needed*beforeMountonBeforeMountmountedonMountedbeforeUpdateonBeforeUpdateupdatedonUpdatedbeforeUnmountonBeforeUnmountunmountedonUnmountederrorCapturedonErrorCapturedrenderTrackedonRenderTrackedrenderTriggeredonRenderTriggered

    Hook inside setup,顾名思义,VCA 建议在 setup 这个大方法里面写我们的各种逻辑功能点。

    Teleport 组件

    传送,将组件的 DOM 元素挂载在任意指定的一个 DOM 元素,与 React Portals 的概念是一致的。

    一个典型的例子,我们在组件调用了 Modal 弹框组件,我们希望的弹框是这样子的,绝对居中,层级最高,如:

    Vue3丨从 5 个维度来讲 Vue3 变化

    组件的结构是这样子的

    <Home>
      <Modal />
    </Home>
    

    但是如果在父组件 Home 有类似这样的样式,如 transform

    Vue3丨从 5 个维度来讲 Vue3 变化

    就会影响到 Modal 的位置,即使 Modal 用了 position:fixed 来定位,如:

    Vue3丨从 5 个维度来讲 Vue3 变化

    这就是为什么我们需要用 Teleport 组件来帮助我们 “跳出” 容器,避免受到父组件的一些约束控制,把组件的 DOM 元素挂载到 body 下,如:

    <Teleport to="body">
      <div v-if="show">
        ...Modal 组件的 DOM 结构...
      </div>
    </Teleport>
    

    注意:即使 Modal 跳出了容器,也保持 “父子组件关系”,只是 DOM 元素的位置被移动了而已 。

    异步组件(defineAsyncComponent)

    我们都知道在 Vue2 也有异步组件的概念,但整体上来说不算完整~,Vue3 提供了 defineAsyncComponent 方法与 Suspense 内置组件,我们可以用它们来做一个优雅的异步组件加载方案。

    直接看代码:

    HOCLazy/index.tsx

    import { defineAsyncComponent, defineComponent } from 'vue';
    import MySuspense from './MySuspense.vue';
    export default function HOCLazy(chunk: any, isComponent: boolean = false) {
      const wrappedComponent = defineAsyncComponent(chunk);
      return defineComponent({
        name: 'HOCLazy',
        setup() {
          const props = { isComponent, wrappedComponent };
          return () => <MySuspense {...props} />;
        }
      });
    }
    

    解释:HOCLazy 接收了两个参数,chunk 就是我们经常采用的组件异步加载方式如:chunk=()=>import(xxx.vue)isComponent 表示当前的“组件”是一个 组件级 or 页面级,通过判断 isComponent 来分别对应不同的 “loading” 操作。

    HOCLazy/MySuspense.vue

    <template>
      <Suspense>
        <template #default>
          <component :is="wrappedComponent"
                     v-bind="$attrs" />
        </template>
        <template #fallback>
          <div>
            <Teleport to="body"
                      :disabled="isComponent">
              <div v-if="delayShow"
                   class="loading"
                   :class="{component:isComponent}">
                <!-- 组件和页面有两种不一样的loading方式,这里不再详细封装 -->
                <div> {{isComponent?'组件级':'页面级'}}Loading ...</div>
              </div>
            </Teleport>
          </div>
        </template>
      </Suspense>
    </template>
    
    <script lang="ts">
    import { defineComponent, defineAsyncComponent, ref, onMounted } from 'vue';
    export default defineComponent({
      name: 'HOCLazy',
      props: ['isComponent', 'wrappedComponent'],
      setup(props) {
        const delayShow = ref<boolean>(false);
        onMounted(() => {
          setTimeout(() => {
            delayShow.value = true;
            // delay 自己拿捏,也可以以 props 的方式传入
          }, 300);
        });
        return { ...props, delayShow };
      }
    });
    </script>
    
    <style lang="less" scoped>
    .loading {
      // 组件级样式
      &.component {
      }
      // 页面级样式
    }
    </style>
    

    解释:

    1. Suspense 组件有两个插槽,具名插槽 fallback 我们这里可以理解成一个 loading 的占位符,在异步组件还没显示之前的后备内容。
    2. 这里还用了 Vue 的动态组件 component 来灵活的传入一个异步组件,v-bind="$attrs" 来保证我们传递给目标组件的 props 不会消失。
    3. fallback 中我们利用了判断 isComponent 来展示不同的 loading ,因为我们希望页面级的 loading 是“全局”的,组件级是在原来的文档流,这里用了 Teleport :disabled="isComponent" 来控制是否跳出。
    4. 细心的小伙伴会发现这里做了一个延迟显示 delayShow,如果我们没有这个延迟,在网络环境良好的情况下,loading 每次都会一闪而过,会有一种“反优化”的感觉。

    调用 HOCLazy:
    为了更好的看出效果,我们封装了 slow 方法来延迟组件加载:

    utils/slow.ts

    const slow = (comp: any, delay: number = 1000): Promise<any> => {
      return new Promise(resolve => {
        setTimeout(() => resolve(comp), delay);
      });
    };
    export default slow;
    

    调用(组件级)

    <template>
      <LazyComp1 str="hello~" />
    </template>
    const LazyComp1 = HOCLazy(
      () => slow(import('@/components/LazyComp1.vue'), 1000),
      true
    );
    // ...
    components: {
      LazyComp1
    },
    // ...
    

    看个效果:

    Vue3丨从 5 个维度来讲 Vue3 变化

    ref,reactive,toRef,toRefs 的区别使用

    ref(reference)

    ref 和 reactive 的存在都是了追踪值变化(响应式),ref 有个「包装」的概念,它用来包装原始值类型,如 string 和 number ,我们都知道不是引用类型是无法追踪后续的变化的。ref 返回的是一个包含 .value 属性的对象。

    setup(props, context) {
      const count = ref<number>(1);
      // 赋值
      count.value = 2;
      // 读取
      console.log('count.value :>> ', count.value);
      return { count };
    }
    

    在 template 中 ref 包装对象会被自动展开(Ref Unwrapping),也就是我们在模板里不用再 .value

    <template>  
      {{count}}
    </template>
    

    reactive

    与 Vue2 中的 Vue.observable() 是一个概念。
    用来返回一个响应式对象,如:

    const obj = reactive({
      count: 0
    })
    // 改变
    obj.count++
    

    注意:它用来返回一个响应式对象,本身就是对象,所以不需要包装。我们使用它的属性,不需要加 .value 来获取。

    toRefs

    让我们关注 setup 方法的 props 的相关操作:

    <template>
      {{name}}
      <button @click="handleClick">点我</button>
    </template>
    // ...
    props: {
      name: { type: String, default: ' ' }
    },
    setup(props) {
      const { name } = props;
      const handleClick = () => {
        console.log('name :>> ', name);
      };
      return { handleClick };
    }
    // ...
    

    注意:props 无需通过 setup 函数 return,也可以在 template 进行绑定对应的值

    我们都知道解构是 es6 一种便捷的手段,编译成 es5 ,如:

    // es6 syntax
    const { name } = props;
    // to es5 syntax
    var name = props.name;
    

    假设父组件更改了 props.name 值,当我们再点击了 button 输出的 name 就还是之前的值,不会跟着变化,这其实是一个基础的 js 的知识点。

    为了方便我们对它进行包装,toRefs 可以理解成批量包装 props 对象,如:

    const { name } = toRefs(props);
    const handleClick = () => {
      // 因为是包装对象,所以读取的时候要用.value
      console.log('name :>> ', name.value);
    };
    

    可以理解这一切都是因为我们要用解构,toRefs 所采取的解决方案。

    toRef

    toRef 的用法,就是多了一个参数,允许我们针对一个 key 进行包装,如:

    const name = toRef(props,'name');
    console.log('name :>> ', name.value);
    

    watchEffect vs watch

    Vue3 的 watch 方法与 Vue2 的概念类似,watchEffect 会让我们有些疑惑。其实 watchEffect 与 watch 大体类似,区别在于:

    watch 可以做到的

    • 懒执行副作用
    • 更具体地说明什么状态应该触发侦听器重新运行
    • 访问侦听状态变化前后的值

    对于 Vue2 的 watch 方法,Vue3 的 "watch" 多了一个「清除副作用」 的概念,我们着重关注这点。

    这里拿 watchEffect 来举例:

    watchEffect 方法简单结构

    watchEffect(onInvalidate => {
      // 执行副作用
      // do something...
      onInvalidate(() => {
        // 执行/清理失效回调
        // do something...
      })
    })
    

    执行失效回调,有两个时机

    • 副作用即将重新执行时,也就是监听的数据发生改变时
    • 组件卸载时

    一个例子:我们要通过 id 发起请求获取「水果」的详情,我们监听 id,当 id 切换过于频繁(还没等上个异步数据返回成功)。可能会导致最后 id=1 的数据覆盖了id=2 的数据,这并不是我们希望的。

    我们来模拟并解决这个场景:

    模拟接口 getFruitsById

    interface IFruit {
      id: number;
      name: string;
      imgs: string;
    }
    const list: { [key: number]: IFruit } = {
      1: { id: 1, name: '苹果', imgs: 'https://xxx.apple.jpg' },
      2: { id: 2, name: '香蕉', imgs: 'https://xxx.banana.jpg' }
    };
    const getFruitsById = (
      id: number,
      delay: number = 3000
    ): [Promise<IFruit>, () => void] => {
      let _reject: (reason?: any) => void;
      const _promise: Promise<IFruit> = new Promise((resolve, reject) => {
        _reject = reject;
        setTimeout(() => {
          resolve(list[id]);
        }, delay);
      });
      return [
        _promise,
        () =>
          _reject({
            message: 'abort~'
          })
      ];
    };
    

    这里封装了“取消请求”的方法,利用 reject 来完成这一动作。

    在 setup 方法

    setup() {
      const id = ref<number>(1);
      const detail = ref<IFruit | {}>({});
    
      watchEffect(async onInvalidate => {
        onInvalidate(() => {
          cancel && cancel();
        });
        // 模拟id=2的时候请求时间 1s,id=1的时候请求时间 2s
        const [p, cancel] = getFruitsById(id.value, id.value === 2 ? 1000 : 2000);
        const res = await p;
        detail.value = res;
      });
      // 模拟频繁切换id,获取香蕉的时候,获取苹果的结果还没有回来,取消苹果的请求,保证数据不会被覆盖
      id.value = 2;
      // 最后 detail 值为 { "id": 2, "name": "香蕉", "imgs": "https://xxx.banana.jpg" }
    }
    

    如果没有执行 cancel() ,那么 detail 的数据将会是 { "id": 1, "name": "苹果", "imgs": "https://xxx.apple.jpg" },因为 id=1 数据比较“晚接收到”。

    这就是在异步场景下常见的例子,清理失效的回调,保证当前副作用有效,不会被覆盖。感兴趣的小伙伴可以继续深究。

    fragment(片段)

    我们都知道在封装组件的时候,只能有一个 root 。在 Vue3 允许我们有多个 root ,也就是片段,但是在一些操作值得我们注意。

    inheritAttrs=true[默认] 时,组件会自动在 root 继承合并 class ,如:

    子组件

    <template>
      <div class="fragment">
        <div>div1</div>
        <div>div2</div>
      </div>
    </template>
    

    父组件调用,新增了一个 class

    <MyFragment class="extend-class" />
    

    子组件会被渲染成

    <div class="fragment extend-class">
      <div> div1 </div>
      <div> div2 </div>
    </div>
    

    如果我们使用了 片段 ,就需要显式的去指定绑定 attrs ,如子组件:

    <template>
      <div v-bind="$attrs">div1</div>
      <div>div2</div>
    </template>
    

    emits

    在 Vue2 我们会对 props 里的数据进行规定类型,默认值,非空等一些验证,可以理解 emits 做了类似的事情,把 emit 规范起来,如:

    // 也可以直接用数组,不做验证
    // emits: ['on-update', 'on-other'],
    emits: {
      // 赋值 null 不验证
      'on-other': null,
      // 验证
      'on-update'(val: number) {
        if (val === 1) {
          return true;
        }
        // 自定义报错
        console.error('val must be 1');
        return false;
      }
    },
    setup(props, ctx) {
      const handleEmitUpdate = () => {
        // 验证 val 不为 1,控制台报错
        ctx.emit('on-update', 2);
      };
      const handleEmitOther = () => {
        ctx.emit('on-other');
      };
      return { handleEmitUpdate, handleEmitOther };
    }
    

    在 setup 中,emit 已经不再用 this.$emit 了,而是 setup 的第二个参数 context 上下文来获取 emit 。

    v-model

    个人还是挺喜欢 v-model 的更新的,可以提升封装组件的体验感~

    Vue3 把这两个语法糖统一了,所以我们现在可以在一个组件上使用 多个 v-model 语法糖,举个例子:

    先从父组件看

    <VModel v-model="show"
            v-model:model1="check"
            v-model:model2.hello="textVal" />
    

    hello为自定义修饰符

    我们在一个组件上用了 3 个 v-model 语法糖,分别是

    v-model 语法糖对应的 prop对应的 event自定义修饰符对应的 prop
    v-model(default)modelValueupdate:modelValuev-model:model1model1update:model1v-model:model2model2update:model2model2Modifiers

    这样子我们就更清晰的在子组件我们要进行一些什么封装了,如:

    VModel.vue

    // ...
    props: {
      modelValue: { type: Boolean, default: false },
      model1: { type: Boolean, default: false },
      model2: { type: String, default: '' },
      model2Modifiers: {
        type: Object,
        default: () => ({})
      }
    },
    emits: ['update:modelValue', 'update:model1', 'update:model2'],
    // ...
    

    key attribute

    <template>
      <input type="text"
             placeholder="请输入账号"
             v-if="show" />
      <input type="text"
             placeholder="请输入邮箱"
             v-else />
      <button @click="show=!show">Toggle</button>
    </template>
    

    类似这样的 v-if/v-else,在 Vue2 中,会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,所以当我们在第一个 input 中输入,然后切换第二个 input 。第一个 input 的值将会被保留复用。

    有些场景下我们不要复用它们,需要添加一个唯一的 key ,如:

    <template>
      <input type="text"
             placeholder="请输入账号"
             v-if="show"
             key="account" />
      <input type="text"
             placeholder="请输入邮箱"
             v-else
             key="email" />
      <button @click="show=!show">Toggle</button>
    </template>
    

    但是在 Vue3 我们不用显式的去添加 key ,这两个 input 元素也是完全独立的,因为 Vue3 会对 v-if/v-else 自动生成唯一的 key。

    全局 API

    在 Vue2 我们对于一些全局的配置可能是这样子的,例如我们使用了一个插件

    Vue.use({
      /* ... */
    });
    const app1 = new Vue({ el: '#app-1' });
    const app2 = new Vue({ el: '#app-2' });
    

    但是这样子这会影响两个根实例,也就是说,会变得不可控。

    在 Vue3 引入一个新的 API createApp 方法,返回一个实例:

    import { createApp } from 'vue';
    const app = createApp({ /* ... */ });
    

    然后我们就可以在这个实例上挂载全局相关方法,并只对当前实例生效,如:

    app
      .component(/* ... */)
      .directive(/* ... */ )
      .mixin(/* ... */ )
      .use(/* ... */ )
      .mount('#app');
    

    需要注意的是,在 Vue2 我们用了 Vue.prototype.$http=()=>{} 这样的写法,来对 “根Vue” 的 prototype 进行挂载方法,使得我们在子组件,可以通过原型链的方式找到 $http 方法,即 this.$http

    而在 Vue3 我们类似这样的挂载需要用一个新的属性 globalProperties

    app.config.globalProperties.$http = () => {}
    

    在 setup 内部使用 $http

    setup() {
      const {
        ctx: { $http }
      } = getCurrentInstance();
    }
    

    2. 底层优化

    Proxy 代理

    Vue2 响应式的基本原理,就是通过 Object.defineProperty,但这个方式存在缺陷。使得 Vue 不得不通过一些手段来 hack,如:

    • Vue.$set() 动态添加新的响应式属性
    • 无法监听数组变化,Vue 底层需要对数组的一些操作方法,进行再封装。如 pushpop 等方法。

    而在 Vue3 中优先使用了 Proxy 来处理,它代理的是整个对象而不是对象的属性,可对于整个对象进行操作。不仅提升了性能,也没有上面所说的缺陷。

    简单举两个例子:

    1. 动态添加响应式属性
    const targetObj = { id: '1', name: 'zhagnsan' };
    const proxyObj = new Proxy(targetObj, {
      get: function (target, propKey, receiver) {
        console.log(`getting key:${propKey}`);
        return Reflect.get(...arguments);
      },
      set: function (target, propKey, value, receiver) {
        console.log(`setting key:${propKey},value:${value}`);
        return Reflect.set(...arguments);
      }
    });
    proxyObj.age = 18;
    // setting key:age,value:18
    

    如上,用 Proxy 我们对 proxyObj 对象动态添加的属性也会被拦截到。

    Reflect 对象是ES6 为了操作对象而提供的新 API。它有几个内置的方法,就如上面的 get / set,这里可以理解成我们用 Reflect 更加方便,否则我们需要如:

    get: function (target, propKey, receiver) {
      console.log(`getting ${propKey}!`);
      return target[propKey];
    },
    
    1. 对数组的操作进行拦截
    const targetArr = [1, 2];
    const proxyArr = new Proxy(targetArr, {
      set: function (target, propKey, value, receiver) {
        console.log(`setting key:${propKey},value:${value}`);
        return Reflect.set(...arguments);
      }
    });
    proxyArr.push('3');
    // setting key:2,value:3
    // setting key:length,value:3
    

    静态提升(hoistStatic) vdom

    我们都知道 Vue 有虚拟dom的概念,它能为我们在数据改变时高效的渲染页面。

    Vue3 优化了 vdom 的更新性能,简单举个例子

    Template

    <div class="div">
      <div>content</div>
      <div>{{message}}</div>
    </div>
    

    Compiler 后,没有静态提升

    function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", { class: "div" }, [
        _createVNode("div", null, "content"),
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ]))
    }
    

    Compiler 后,有静态提升

    const _hoisted_1 = { class: "div" }
    const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "content", -1 /* HOISTED */)
    
    function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createBlock("div", _hoisted_1, [
        _hoisted_2,
        _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
      ]))
    }
    

    静态提升包含「静态节点」和「静态属性」的提升,也就是说,我们把一些静态的不会变的节点用变量缓存起来,提供下次 re-render 直接调用。
    如果没有做这个动作,当 render 重新执行时,即使标签是静态的,也会被重新创建,这就会产生性能消耗。

    3. 与 TS

    defineComponent

    在 TS 下,我们需要用 Vue 暴露的方法 defineComponent,它单纯为了类型推导而存在的。

    props 推导

    import { defineComponent } from 'vue';
    export default defineComponent({
      props: {
        val1: String,
        val2: { type: String, default: '' },
      },
      setup(props, context) {
        props.val1;
      }
    })
    

    当我们在 setup 方法访问 props 时候,我们可以看到被推导后的类型,

    • val1 我们没有设置默认值,所以它为 string | undefined
    • 而 val2 的值有值,所以是 string,如图:

    Vue3丨从 5 个维度来讲 Vue3 变化

    PropType

    我们关注一下 props 定义的类型,如果是一个复杂对象,我们就要用 PropType 来进行强转声明,如:

    interface IObj {
      id: number;
      name: string;
    }
    
    obj: {
      type: Object as PropType<IObj>,
      default: (): IObj => ({ id: 1, name: '张三' })
    },
    

    或 联合类型

    type: {
      type: String as PropType<'success' | 'error' | 'warning'>,
      default: 'warning'
    },
    

    4. build丨更好的 tree-sharking(摇树优化)

    基于函数的 API 每一个函数都可以用 import { method1,method2 } from "xxx";,这就对 tree-sharking 非常友好,而且函数名同变量名都可以被压缩,对象去不可以。举个例子,我们封装了一个工具,工具提供了两个方法,用 method1method2 来代替。

    我们把它们封装成一个对象,并且暴露出去,如:

    // utils
    const obj = {
      method1() {},
      method2() {}
    };
    export default obj;
    
    // 调用
    import util from '@/utils';
    util.method1();
    

    经过webpack打包压缩之后为:

    a={method1:function(){},method2:function(){}};a.method1();
    

    我们不用对象的形式,而用函数的形式来看看:

    // utils
    export function method1() {}
    export function method2() {}
    
    // 调用
    import { method1 } from '@/utils';
    method1();
    

    经过webpack打包压缩之后为:

    function a(){}a();
    

    用这个例子我们就可以了解 Vue3 为什么能更好的 tree-sharking ,因为它用的是基于函数形式的API,如:

    import {
      defineComponent,
      reactive,
      ref,
      watchEffect,
      watch,
      onMounted,
      toRefs,
      toRef
    } from 'vue';
    

    5. options api 与 composition api 取舍

    我们上面的代码都是在 setup 内部实现,但是目前 Vue3 还保留了 Vue2 的 options api 写法,就是可以“并存”,如:

    // ...
    setup() {
      const val = ref<string>('');
      const fn = () => {};
      return {
        val,
        fn
      };
    },
    mounted() {
      // 在 mounted 生命周期可以访问到 setup return 出来的对象
      console.log(this.val);
      this.fn();
    },
    // ...
    

    结合 react ,我们知道 “函数式”,hook 是未来的一个趋势。

    所以个人建议还是采用都在 setup 内部写逻辑的方式,因为 Vue3 可以完全提供 Vue2 的全部能力。

    总结

    个人觉得不管是 React Hook 还是 Vue3 的 VCA,我们都可以看到现在的前端框架趋势,“更函数式”,让逻辑复用更灵活。hook 的模式新增了 React / Vue 的抽象层级,「组件级 + 函数级」,可以让我们处理逻辑时分的更细,更好维护。

    Vue3 One Piece,nice !

    最后,前端精本精祝您圣诞快乐?~ (听说公众号关注「前端精」会更快乐哦~


    起源地下载网 » Vue3丨从 5 个维度来讲 Vue3 变化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元