Vue3使用经验总结

Kevin2li大约 9 分钟Box

第三方库

ant-design-vue

# 安装
pnpm i --save ant-design-vue
// main.ts
import { createApp } from "vue";
import Antd from "ant-design-vue";
import App from "./App.vue";
import "ant-design-vue/dist/antd.css";

const app = createApp(App);

app.use(Antd);
app.mount("#app");

ant-design-vue + pinia

// main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import Antd from "ant-design-vue";
import App from "./App.vue";
import "ant-design-vue/dist/antd.css";

const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(Antd);
app.mount("#app");

ant-design-vue + pinia + windicss

ant-design-vue + pinia + windicss + alova

代码组织

简单小组件

<template>
  <div>
    <h1>This is Main</h1>
    <div>count: {{ count }}</div>
    <div><button @click="add">add</button></div>
  </div>
</template>

<script>
import { ref, defineComponent } from "vue";
export default defineComponent({
  components: {},
  props: {},
  setup(props) {
    const msg = ref("Hello, vue3");
    const count = ref(1);
    const add = () => {
      count.value++;
    };
    return {
      add,
      count,
      msg,
    };
  },
});
</script>

表单

文本框,密码框,数字输入,单选框,多选框,下拉框,checkbox,文件上传等

<template>
  <a-form
    :model="formState"
    name="validate_other"
    v-bind="formItemLayout"
    @finishFailed="onFinishFailed"
    @finish="onFinish"
  >
    <a-form-item label="Plain Text">
      <span class="ant-form-text">China</span>
    </a-form-item>
    <a-form-item
      label="Username"
      name="username"
      :rules="[{ required: true, message: 'Please input your username!' }]"
    >
      <a-input v-model:value="formState.username" />
    </a-form-item>

    <a-form-item
      label="Password"
      name="password"
      :rules="[{ required: true, message: 'Please input your password!' }]"
    >
      <a-input-password v-model:value="formState.password" />
    </a-form-item>
    <a-form-item
      name="select"
      label="Select"
      has-feedback
      :rules="[{ required: true, message: 'Please select your country!' }]"
    >
      <a-select v-model:value="formState.select" placeholder="Please select a country">
        <a-select-option value="china">China</a-select-option>
        <a-select-option value="usa">U.S.A</a-select-option>
      </a-select>
    </a-form-item>

    <a-form-item
      name="select-multiple"
      label="Select[multiple]"
      :rules="[{ required: true, message: 'Please select your favourite colors!', type: 'array' }]"
    >
      <a-select
        v-model:value="formState['select-multiple']"
        mode="multiple"
        placeholder="Please select favourite colors"
      >
        <a-select-option value="red">Red</a-select-option>
        <a-select-option value="green">Green</a-select-option>
        <a-select-option value="blue">Blue</a-select-option>
      </a-select>
    </a-form-item>

    <a-form-item label="InputNumber">
      <a-form-item name="input-number" no-style>
        <a-input-number v-model:value="formState['input-number']" :min="1" :max="10" />
      </a-form-item>
      <span class="ant-form-text">machines</span>
    </a-form-item>

    <a-form-item name="switch" label="Switch">
      <a-switch v-model:checked="formState.switch" />
    </a-form-item>

    <a-form-item name="slider" label="Slider">
      <a-slider
        v-model:value="formState.slider"
        :marks="{
          0: 'A',
          20: 'B',
          40: 'C',
          60: 'D',
          80: 'E',
          100: 'F',
        }"
      />
    </a-form-item>

    <a-form-item name="radio-group" label="Radio.Group">
      <a-radio-group v-model:value="formState['radio-group']">
        <a-radio value="a">item 1</a-radio>
        <a-radio value="b">item 2</a-radio>
        <a-radio value="c">item 3</a-radio>
      </a-radio-group>
    </a-form-item>

    <a-form-item
      name="radio-button"
      label="Radio.Button"
      :rules="[{ required: true, message: 'Please pick an item!' }]"
    >
      <a-radio-group v-model:value="formState['radio-button']">
        <a-radio-button value="a">item 1</a-radio-button>
        <a-radio-button value="b">item 2</a-radio-button>
        <a-radio-button value="c">item 3</a-radio-button>
      </a-radio-group>
    </a-form-item>

    <a-form-item name="checkbox-group" label="Checkbox.Group">
      <a-checkbox-group v-model:value="formState['checkbox-group']">
        <a-row>
          <a-col :span="8">
            <a-checkbox value="A" style="line-height: 32px">A</a-checkbox>
          </a-col>
          <a-col :span="8">
            <a-checkbox value="B" style="line-height: 32px" disabled>B</a-checkbox>
          </a-col>
          <a-col :span="8">
            <a-checkbox value="C" style="line-height: 32px">C</a-checkbox>
          </a-col>
          <a-col :span="8">
            <a-checkbox value="D" style="line-height: 32px">D</a-checkbox>
          </a-col>
          <a-col :span="8">
            <a-checkbox value="E" style="line-height: 32px">E</a-checkbox>
          </a-col>
          <a-col :span="8">
            <a-checkbox value="F" style="line-height: 32px">F</a-checkbox>
          </a-col>
        </a-row>
      </a-checkbox-group>
    </a-form-item>

    <a-form-item name="rate" label="Rate">
      <a-rate v-model:value="formState.rate" allow-half />
    </a-form-item>

    <a-form-item name="upload" label="Upload" extra="longgggggggggggggggggggggggggggggggggg">
      <a-upload
        v-model:fileList="formState.upload"
        name="logo"
        action="/upload.do"
        list-type="picture"
      >
        <a-button>
          <template #icon><UploadOutlined /></template>
          Click to upload
        </a-button>
      </a-upload>
    </a-form-item>

    <a-form-item label="Dragger">
      <a-form-item name="dragger" no-style>
        <a-upload-dragger v-model:fileList="formState.dragger" name="files" action="/upload.do">
          <p class="ant-upload-drag-icon">
            <InboxOutlined />
          </p>
          <p class="ant-upload-text">Click or drag file to this area to upload</p>
          <p class="ant-upload-hint">Support for a single or bulk upload.</p>
        </a-upload-dragger>
      </a-form-item>
    </a-form-item>

    <a-form-item :wrapper-col="{ span: 12, offset: 6 }">
      <a-button type="primary" html-type="submit">Submit</a-button>
    </a-form-item>
  </a-form>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import { UploadOutlined, InboxOutlined } from '@ant-design/icons-vue';

export default defineComponent({
  components: {
    UploadOutlined,
    InboxOutlined,
  },
  setup() {
    const formItemLayout = {
      labelCol: { span: 6 },
      wrapperCol: { span: 14 },
    };

    const formState = reactive<Record<string, any>>({
      'username': "",
      'password': "",
      'input-number': 3,
      'checkbox-group': ['A', 'B'],
      rate: 3.5,
    });
    const onFinish = (values: any) => {
      console.log('Success:', values);
    };

    const onFinishFailed = (errorInfo: any) => {
      console.log('Failed:', errorInfo);
    };
    return {
      formState,
      onFinish,
      onFinishFailed,
      formItemLayout,
    };
  },
});
</script>

效果:

表格

主要是定义好column和data.

<template>
  <a-table :columns="columns" :data-source="data">
    <template #headerCell="{ column }">
      <template v-if="column.key === 'name'">
        <span>
          <smile-outlined />
          Name
        </span>
      </template>
    </template>

    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'name'">
        <a>
          {{ record.name }}
        </a>
      </template>
      <template v-else-if="column.key === 'tags'">
        <span>
          <a-tag
            v-for="tag in record.tags"
            :key="tag"
            :color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'"
          >
            {{ tag.toUpperCase() }}
          </a-tag>
        </span>
      </template>
      <template v-else-if="column.key === 'action'">
        <span>
          <a>Invite 一 {{ record.name }}</a>
          <a-divider type="vertical" />
          <a>Delete</a>
          <a-divider type="vertical" />
          <a class="ant-dropdown-link">
            More actions
            <down-outlined />
          </a>
        </span>
      </template>
    </template>
  </a-table>
</template>
<script lang="ts">
import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue';
import { defineComponent } from 'vue';
const columns = [
  {
    name: 'Name',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
  {
    title: 'Tags',
    key: 'tags',
    dataIndex: 'tags',
  },
  {
    title: 'Action',
    key: 'action',
  },
];

const data = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    address: 'New York No. 1 Lake Park',
    tags: ['nice', 'developer'],
  },
  {
    key: '2',
    name: 'Jim Green',
    age: 42,
    address: 'London No. 1 Lake Park',
    tags: ['loser'],
  },
  {
    key: '3',
    name: 'Joe Black',
    age: 32,
    address: 'Sidney No. 1 Lake Park',
    tags: ['cool', 'teacher'],
  },
];

export default defineComponent({
  components: {
    SmileOutlined,
    DownOutlined,
  },
  setup() {
    return {
      data,
      columns,
    };
  },
});
</script>

画图

  • G2Plot
# 安装 
npm install @antv/g2plot

要点:

  1. 导入onMounted
  2. 画图代码放在onMounted

示例:

<template>
  <div>
    <h1>Chart Demo</h1>
    <div style="margin: 30px;" id="container1"></div>
    <div style="margin: 30px;" id="container2"></div>
    <div style="margin: 30px;" id="container3"></div>
  </div>
</template>

<script>
import { ref, defineComponent, onMounted } from "vue";
import { Line, Bar, Pie } from "@antv/g2plot";

export default defineComponent({
  components: {},
  props: {},
  setup(props) {
    onMounted(() => {
      // 折线图
      const data = [
        { year: "1991", value: 3 },
        { year: "1992", value: 4 },
        { year: "1993", value: 3.5 },
        { year: "1994", value: 5 },
        { year: "1995", value: 4.9 },
        { year: "1996", value: 6 },
        { year: "1997", value: 7 },
        { year: "1998", value: 9 },
        { year: "1999", value: 13 },
      ];
      const line = new Line("container1", {
        data: data,
        xField: "year",
        yField: "value",
      });

      line.render();

      // 直方图
      const data2 = [
        { year: "1951 年", value: 38 },
        { year: "1952 年", value: 52 },
        { year: "1956 年", value: 61 },
        { year: "1957 年", value: 145 },
        { year: "1958 年", value: 48 },
      ];

      const bar = new Bar("container2", {
        data: data2,
        xField: "value",
        yField: "year",
        seriesField: "year",
        legend: {
          position: "top-left",
        },
      });

      bar.render();

      // 饼图
      const data3 = [
        { type: "分类一", value: 27 },
        { type: "分类二", value: 25 },
        { type: "分类三", value: 18 },
        { type: "分类四", value: 15 },
        { type: "分类五", value: 10 },
        { type: "其他", value: 5 },
      ];

      const piePlot = new Pie("container3", {
        appendPadding: 10,
        data: data3,
        angleField: "value",
        colorField: "type",
        radius: 0.9,
        label: {
          type: "inner",
          offset: "-30%",
          content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
          style: {
            fontSize: 14,
            textAlign: "center",
          },
        },
        interactions: [{ type: "element-active" }],
      });

      piePlot.render();
    });
    return {};
  },
});
</script>

状态存储

  • Pinia

参考:https://juejin.cn/post/7078281612013764616open in new window

# 安装(vue 3.3.0以上,不然会报错)
npm install pinia
  • main.ts挂载pinia: 见第三方库部分

  • 创建store:

// src/store/demo.ts
import { defineStore } from "pinia";

interface User {
  username: string;
  gender: string;
  age: number;
}

export const demoStore = defineStore("demo", {
    state: (): User =>({
        username: "kevin",
        gender: "male",
        age: 25,
    }),
    getters: {

    },
    actions: {
        resetState(){
            this.username = "";
            this.gender = "male";
            this.age = 0;
        }
    }
})
  • 使用store:
// src/components/demo.vue
<template>
  <div>username: {{ store.username }}</div>
  <div>gender: {{ store.gender }}</div>
  <div>age: {{ store.age }}</div>
</template>

<script>
import { ref, defineComponent } from "vue";
import {demoStore} from "../store/demo.ts"

export default defineComponent({
  components: {},
  props: {},
  setup(props) {
    const store = demoStore();
    return { store };
  },
});
</script>

网络请求

# 安装
npm install alova
<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">{{ error.message }}</div>
  <span v-else>responseData: {{ data }}</span>
</template>

<script setup>
  import { createAlova, useRequest } from 'alova';
  import GlobalFetch from 'alova/GlobalFetch';
  import VueHook from 'alova/vue';

  // 1. 创建alova实例
  const alovaInstance = createAlova({
    // VueHook用于创建ref状态,包括请求状态loading、响应数据data、请求错误对象error等
    statesHook: VueHook,

    // 请求适配器,推荐使用fetch请求适配器
    requestAdapter: GlobalFetch(),

    // 全局的响应拦截器
    responded: response => response.json()
  });

  // 2. 使用alova实例创建method并传给useRequest即可发送请求
  const { loading, data, error } = useRequest(
    alovaInstance.Get('http://localhost:7000', {
      params: {
        // id: 1
      }
    })
  );
</script> 

表格数据导入与导出

  • xlsx

组件概念

refreactivetoRawtoRefsunref作用

在Vue 3中,toRaw和toRefs都是用于将响应式对象转换为普通对象的函数,但它们的作用不同。

  • ref是一个用于创建响应式数据的函数,它可以将一个普通的JavaScript值转换为响应式数据。ref函数返回一个包含了原始值的响应式对象,可以通过.value访问原始值,修改原始值时会触发响应式更新。需要注意的是,ref函数只能用于创建包含一个值的响应式对象。如果需要创建包含多个值的响应式对象,可以使用reactive函数。另外,ref函数也可以用于包装普通的对象或者数组,从而实现对对象或者数组的某个属性或者元素进行响应式更新。
const message = ref('Hello, Vue 3!')

// 修改ref对象的值
message.value = 'Hello, World!' 
  • reactive是一个用于创建响应式对象的函数,它可以将一个普通的JavaScript对象转换为响应式对象。reactive函数返回一个代理对象,可以通过该代理对象的属性访问原始对象的属性,对代理对象的属性进行修改时会触发响应式更新。

使用reactive函数创建的响应式对象可以用于模板中的数据绑定,也可以在JavaScript代码中进行操作。在模板中,会自动对响应式数据进行解包,所以可以直接使用响应式对象的属性。在JavaScript代码中,可以直接访问响应式对象的属性,而不需要使用.value来访问原始值。

const user = reactive({
    name: 'Tom',
    age: 18
})

// 修改响应式对象的值
user.name = 'Jerry' 
  • toRaw函数用于将一个响应式对象转换为原始对象,即去除响应式特性的对象。这个函数返回的对象不再具有响应式特性,因此在修改它时不会触发响应式更新。这个函数通常用于开发插件或者调试工具时,或者在需要对响应式数据进行深拷贝或者序列化时使用。
const user = reactive({
    name: 'Tom',
    age: 18
})

// 将响应式对象转换为原始对象
const userObject = toRaw(user)
  • toRefs函数用于将一个响应式对象的所有属性都转换为普通的属性,并且保留属性的响应式特性。这个函数返回的是一个包含了原始对象中所有属性的响应式对象,其中每个属性都是一个ref对象,可以通过.value访问属性的值。这个函数通常用于需要将响应式对象的属性传递给子组件时使用。
const user = reactive({
    name: 'Tom',
    age: 18
})

// 将响应式对象的所有属性都转换为ref对象
const userRefs = toRefs(user) 
  • unref函数的作用是为了简化代码,避免在处理数据时需要判断数据类型。因为在Vue 3中,模板中的数据绑定会自动对响应式数据进行解包,但在JavaScript代码中,需要手动解包响应式数据或者ref对象,才能获取原始值。使用unref函数可以避免这个问题,无论传入的参数是响应式数据还是ref对象,都可以直接获取原始值,避免了代码中的冗余判断。
const message = ref('Hello, Vue 3!')
// 使用unref函数获取ref对象的原始值
const messageText = unref(message)