Vue3的基本用法

一、前言

vue3已经发布一年了,今天和大家聊聊vue2和vue3。

在这之前,想问大家几个问题?大家可以带着这两个问题听我的分享

  1. 不言而喻vue3的诞生,肯定是为了解决vue2存在的问题,那么究竟解决了什么问题?
  2. vue3相比vue2性能上有哪些提升?

二、vue2和vue3的区别:

vue2基于Object.defineProperty实现,初始化数据在data中,业务逻辑处理放在(computed、methods、watch)以及各个生命周期函数中。

缺点:

  1. 无法监听到添加和删除的属性
  2. 无法监控到数组和对象的变化
  3. 业务逻辑分布在data、computed、methods、watch以及各个生命周期函数中,代码聚合性弱,不便于阅读

1.jpg

vue3基于Proxy实现,把所有的业务逻辑都放在setup函数中进行处理。

优点:

  1. 解决了vue2存在的问题
  2. 在diff算法上使用了静态标记的方式,大大提升了vue的执行效率
  3. 通过关注点分离可以解决单个文件代码冗余的问题(下面会专门讲这个问题)
  4. 静态提升
  5. 事件侦听器缓存
  6. 更好的支持TS
  7. 高内聚、低耦合
<script>
import { ref,defineComponent } from "vue";
 
export default defineComponent({
    setup() {
        const router = useRouter();
        const serviceName = ref("");
        let serviceData = ref<Array<IRecord>>([]);
        //得到服务商的列表
        const getServiceList = () => {
            let data = {
            name: serviceName?.value,
            };
            serviceList(data).then(res => {
            if (res.code === 0) {
                serviceData.value = res.data.list;
            }
            });
        };
        //添加
        const addEvent = () => {
            router.push({
            path: "service-add"
            });
        };
        //编辑
        const editEvent = (record: IRecord & { id: number }) => {
            router.push(`service-edit/${record.id}`);
        };
        //搜索
        const searchEvent = () => {
            getServiceList();
        };
        onMounted(() => {
            //得到服务商的列表
            getServiceList();
        });
        return {
            serviceData,
            serviceColumns,
            serviceName,
            //事件
            searchEvent,
            addEvent,
            editEvent
        };
    }
})
</script>
复制代码

三、vue3新特性

组合式API(Composition API)

组合式API实就是一个setup函数,把相关的业务逻辑放在一起解决了之前vue2代码分散的问题,最后返回需要用的变量(响应式变量、非响应式变量)和函数。

import { ref,defineComponent } from 'vue'
 
export default defineComponent({
    setup() {
      const count = ref(0)
      const handleClick = () => {
         
      }
      return {
        count,
        handleClick
      }
    }
})
复制代码

最简单的setup由定义的数据和函数组成,最终在return中返回,供模板中使用。接下来具体讲一下setup由哪些内容组成:

1、ref() 响应式函数

import { ref } from 'vue'
const count = ref(0)
复制代码

ref接受一个参数,返回的是一个带有value属性的对象,通过value属性可以修改对应的值。

import { ref,defineComponent } from 'vue'
 
export default defineComponent({
    setup() {
      const count = ref(0)
      console.log(count.value); //0
      //修改value的值
      count.value++;
      console.log(count.value); //1
    }
})
复制代码

问题:

ref接受一个参数之后,为什么返回的是一个对象?

ref就是把传入的值和对象统一转化成Proxy对象,因为Proxy只支持引用对象,所以对于值对象会转化成{ value: "值"} 再转化成Proxy对象,此时就可以监听到value值的变化。

为什么要用ref?

相当vue2中data定义的数据,这样vue底层就知道了它是响应式变量。

2、reactive 返回对象的响应式副本

const person = reactive({ name: "天下" })
复制代码

返回的person是一个响应式对象,和原对象并不相等,因此建议只用proxy对象。

import { reactive,defineComponent } from 'vue'
 
export default defineComponent({
    setup() {
      const obj = {
        name: "天下"
      }
      const person = reactive(obj)
 
      console.log(obj === person) //false
 
      //修改值
      setTimeout(()=>{
        person.name = "晓";
        person.age = 18;
      },1000)
    }
})
复制代码

3、在setup如何用生命周期函数

组合式API和选项式AP声明周期函数基本一致,区别是组合式API加了前缀"on",有如下生命周期函数

Vue2 Vue3
beforeCreate
created
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeMount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTirggered
activated onActivated
deactivated onDeactivated
import { onMounted,defineComponent } from "vue";

export default defineComponent({
    setup() {
      onMounted(()=>{
        console.log("onMounted");
      })
    }
})
复制代码

4、watch 监听

接受三个参数:

  • 想要侦听的响应式引用
  • 一个回调
  • 可选的配置选项
import { ref,watch,defineComponent } from "vue";
 
export default defineComponent({
    setup() {
    const num = ref(0);
    watch(num,(newNum,oldNum)=>{
      console.log("newNum",newNum); //666
      console.log("oldNum",oldNum); //0
    })
    setTimeout(()=>{
      num.value = 666;
    },1000)
  }
})
复制代码

5、computed

import { ref,computed,defineComponent } from "vue";
 
export default defineComponent({
    setup() {
      const str = ref("Hello");
      const splitVal = computed(()=>{
        return str.value.split("");
      })
      console.log("splitVal.value",splitVal.value); //['H', 'e', 'l', 'l', 'o']
    }
})
复制代码

6、setup 函数

参数:

  • props
  • context

props 是响应式的,传入新的prop时会被更新。

//父组件
<test-setup title="北京欢迎你"></test-setup>
复制代码
//子组件
export default defineComponent({
    props: {
        title: String
    },
    setup(props,context) {
        console.log("--- setup ---");
        console.log("props",props); //Proxy {title: '北京欢迎你'}
        console.log("context",context);
    }
})
复制代码

注意:因为props是响应式的,因此不能用ES6解构,它会消除prop的响应式。

如果需要解构可以在setup函数中使用Refs。

//失去了响应式
const { message } = props;
console.log("message",message); //父组件信息
 
const { title } = toRefs(props);
console.log("title",title); //ObjectRefImpl {_object: Proxy, _key: 'title', __v_isRef: true}
复制代码

如果title是可选的prop,则传入的prop可能没有此属性,在这种情况下toRefs不会创建一个ref,需要用toRef代替。

import { toRef,defineComponent } from "vue";
 
export default defineComponent({
  setup() {
    const otherMessage = toRef(props,"otherMessage");
    console.log("otherMessage",otherMessage.value); //undefined
  }
})
复制代码

Context 是一个普通的js对象,它暴露了组件的3个属性

  • attrs 属性
  • slots 插槽
  • emit 触发事件

context对象不是响应式对象,因此可以解构

import { defineComponent } from "vue";
 
export default defineComponent({
  setup(props,{ attrs, slots, emit}) {
    console.log("attrs",attrs);
    console.log("slots",slots);
    console.log("emit",emit);
  }
})
复制代码

7、ref和reactive的区别:

ref和reactive都可以实现变量响应式,ref主要用于单个变量,reactive主要用于表单对象中多一些。

两者在使用上也有差别:ref之后的变量通过.value值的方式访问,reactive直接可以通过[data].[property]的方式访问。

四、关注点分离

<a-form-item class="width200">
  <a-select
            placeholder="请选择小程序"
            v-model:value="formState.applet"
            :options="appletData"
            @change="appletHandle"
            >
  </a-select>
</a-form-item>
复制代码
import useAppletData from "./market-data-helper/useAppletData";
 
export default defineComponent({
  setup() {
    const { appletData } = useAppletData();
    return {
      appletData
    }
  }
})
复制代码

相关业务逻辑代码分离出去

下面useAppletData.ts文件的主要作用是把请求到的下拉数据分离出去,请求到数据之后return出去,然后在上面的列表页引入使用。

//useAppletData.ts
import { ref, onMounted } from "vue";
import { appletList } from "../../../../api/common";
 
interface IOptions {
  label: string;
  value: string;
}
const appletDataOptions = [{ label: "全部小程序", value: "" }];
export default function useAppletData() {
  const appletData = ref<Array<IOptions>>(appletDataOptions);
  const getAppletList = async () => {
    let data = {
      page: 1,
      pageSize: 999999
    };
    let res = await appletList(data);
    if (res.code === 0) {
      appletData.value = res;
    }
  };
  return {
    appletData
  };
}
复制代码

这样可以大大提高阅读的速度,代码也不会冗余。

五、在vue-router中的用法

在vue2中,我们通过this.$router这种方式进行路由跳转的,但是在vue3中没有this这一概念,因此我们是这样使用的:

import { defineComponent } from "vue";
import { useRouter } from "vue-router";
 
export default defineComponent({
  const count = ref(0);
  const router = useRouter();
  const handleClick = () => {
    router.push({ name: "AddApplet" });
  }
  return {
    count,
    handleClick
  }
})
复制代码

六、在vuex中的用法

import { useStore } from 'vuex'
 
export default defineComponent({
    setup() {
    const store = useStore()
    const count = store.state.count;
    return {
        count,
    }
  }
})
复制代码

七、对于TypeScript的支持

从列表最初数据的定义到请求接口再到返回数据,都进行了约束。

列表:

//列表定义的数据
interface Applet {
  id?: number;
  code: string;
  createAt: String,
  logo: string,
  merchantName: string,
  name: string;
  serviceName: string;
  tag: string;
  tagId: string;
}
 
const appletData = ref<Array<Applet>>([]);
复制代码
//返回值 data中的值定义
interface AppletListResponse {
  count: number;
  list: Array<Applet>;
  page: number | string;
  pageSize: number | string;
}
 
//列表返回的值定义
interface AppletListRes {
  code: number;
  msg: string;
  data: AppletListResponse;
}
 
appletList(params).then((res: AppletListRes) => {
  if (res.code === 0) {
    appletData.value = res.data.list;
    total.value = res.data.count;
  }
});
复制代码
//参数
interface AppletParams {
  page: number | string;
  pageSize: number | string;
  name?: string;
  storeName?: string;
  serviceName?: string;
  tag?: string;
}
 
//接口请求
export const appletList = (params: AppletParams): Promise<AppletListRes> => {
  return Http.get("/applet/list", params);
};
复制代码

八、script setup语法糖

大家可能发现了,虽然我们可以进行关注点分离代码,但是setup函数里面的代码依旧看起来很多,那么有没有什么方法可以让setup变的更加简短一些呢?下面我就介绍一下vue3 3.2版本正式发布的script setup语法糖,可真是用起来特别甜。

1、setup基本用法:

使用起来特别简单,只需要在script标签上加上setup关键字即可。

<script setup></script>
复制代码

2、组件自动注册

在script setup中,引入的组件,无需在components注册。

<script setup>
    import HelloWorld from './components/HelloWorld.vue'
</script>
 
<template>
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>
复制代码

3、组件核心API的使用

(1)使用defineProps

通过 defineProps 指定当前props类型,获得上下文的props对象。

<script setup>
import { ref } from 'vue'
const props = defineProps({
  msg: String
})
const count = ref(0)
</script>
复制代码

(2)使用emits

父组件:

<script setup>
import HelloWorld from './components/HelloWorld.vue'
 
const handle = (val) => {
  console.log(val); //天下
}
</script>
 
<template>
  <HelloWorld
    msg="Hello Vue 3 + Vite"
    @handle="handle"
  />
</template>
复制代码
<script setup>
import {
  defineEmits
} from 'vue'
 
const emits = defineEmits("handle");
const handleClick = ()=> {
  emits("handle","天下")
}
</script>
 
<template>
  <button type="button" @click="handleClick">点击</button>
</template>
复制代码

(3)获取slots和attrs

通过useAttrs、useSlots分别获取slots和attrs。

<script setup>
    import { defineProps,useAttrs,useSlots } from 'vue'
    const attrs = useAttrs();
    const slots = useSlots();
</script>
复制代码

(4)属性和方法无需返回,直接使用

之前,setup函数中的变量和函数都要return,才能在模板中使用,现在在script setup中定义无需return,直接可以使用,有没有感觉到意外的惊喜?

<script setup>
  import { ref } from 'vue'
  const count = ref(0)
  const handleClick = ()=> count.value++
</script>
 
<template>
  <button type="button" @click="handleClick">count is: {{ count }}</button>
</template>
复制代码

大家有没有觉得简单多了。

最后我为大家总结一下:

vue3不论在底层还是逻辑上都有很大的优势,采用Proxy代替了Object.defineProperty方法的用法,解决了vue之前在处理数组和对象的缺陷。通过优化diff算法、静态提升、事件侦听器缓存,极大的提升了vue的性能。

从使用层面,vue3使用组合式API,抛弃了选项式(data、methods、watch、mounted)隔离这种方式,组合式API讲究的是代码的高内聚低耦合。当代码过多的时候用了关注点分离的方式大大提高了代码的可读性。最后vue3最近还使用了script setup语法糖使得代码更加简洁精炼。

猜你喜欢

转载自juejin.im/post/7019253626602258469