简略的治理后盾基本上就是数据的增删改查。次要就是 列表 + form 表单。每个页面的逻辑基本上都雷同。不同的中央就是每个页面须要调用的具体 API 及参数。

以前 vue2 的时候最简略的做法是写进去一个页面的逻辑,而后间接 copy 到各个页面中,批改 API 及参数即可。高级一点的是利用 mixin 函数,将可复用逻辑抽离,每个页面引入 mixin

vue3 之后新增了composition API。本文就是利用composition API,将可复用的逻辑抽离到composition API中,并引入ts,实现一个简略的治理后盾性能。

利用@vue/cli创立我的项目

首先须要将 @vue/cli 降级到最新版。本文用的是4.5.6版本。

vue create admincd adminnpm run serve

create抉择手动抉择Manually select features,会有一些交互性的抉择,是否要装置router、vuex等选项,空格能够切换是否选中。咱们选中TypeScript、Router、Vuex、CSS Pre-processors

咱们利用axios + axios-mock-adapter + mockjs来进行接口申请、接口模仿及假数据生成,接下来再装置这几个包。

npm install axiosnpm install -D axios-mock-adapter mockjs

我的项目整体框架

假如咱们的我的项目蕴含一个 Header,Header 的作用是切换页面。两个页面,别离为 List 和 About,这两个页面都是简略的列表+增删改查的操作。

路由

须要在 router 中减少一个 list 的路由信息。

const routes: Array<RouteRecordRaw> = [  {    path: '/',    name: 'Home',    component: Home,  },  {    path: '/about',    name: 'About',    component: () => { return import(/* webpackChunkName: "about" */ '../views/About.vue'); },  },  {    path: '/list',    name: 'List',    component: () => { return import(/* webpackChunkName: "list" */ '../views/List.vue'); },  },];

列表页

首先把列表页的构造写进去,List 和 About 的构造大体类似。

<template>    <div class='content_page'>        <div class='content_body'>            <div class='content_button'>                <button class='add primary' @click='addItem' title='增加'>增加</button>            </div>            <div class='content_table'>                <table>                    <thead>                        <tr>                            <th v-for='item in thead' :key='item'>{{item}}</th>                        </tr>                    </thead>                    <tbody>                        <tr v-for='(item, index) in list' :key='item.id'>                            <td>                                <span :title='item.id'>{{item.id}}</span>                            </td>                            <td>                                <div v-if='index === currentIndex'>                                    <input                                        v-model='item.name'                                        title='name'                                    />                                </div>                                <span :title='item.name' v-else>{{item.name}}</span>                            </td>                            <td :title='item.sex'>                              <div v-if='index === currentIndex'>                                    <input                                        v-model='item.sex'                                        title='sex'                                    />                                </div>                                <span :title='item.sex' v-else>{{item.sex ? '男' : '女'}}</span>                            </td>                            <td :title='item.birth'>                              <div v-if='index === currentIndex'>                                    <input                                        v-model='item.birth'                                        title='birth'                                    />                                </div>                                <span :title='item.birth' v-else>{{item.birth}}</span></td>                            <td :title='item.address'>                              <div v-if='index === currentIndex'>                                  <input                                      v-model='item.address'                                      title='birth'                                  />                              </div>                              <span :title='item.address' v-else>{{item.address}}</span>                            </td>                            <td>                                <div v-if='index === currentIndex'>                                    <button                                        class='primary confirm'                                        @click='confirm(item)'                                    >确定</button>                                    <button                                        @click='cancel(item)'                                    >勾销</button>                                </div>                                <span v-else>                                    <span @click='editItem(index)'>                                        edit                                    </span>                                    <span @click='deleteItem(index, item)'>delete</span>                                </span>                            </td>                        </tr>                    </tbody>                </table>            </div>        </div>    </div></template>

其中用到了addItem、editItem、deleteItem、confirm、cancel这几个办法,每个列表页的这几个办法性能都是雷同的,惟一的不同就是申请的 API,咱们能够将这几个 API 做为参数,将增删改查的办法提取到setup函数中,做到复用。接下来就来到重点的composition API

composition API具体实现

import { ref, onMounted } from 'vue';import {ItemType, FetchType, DeleteType, AddType, EditType} from '../../types/index';export const compositionApi = (  fetchApi: FetchType,  deleteApi: DeleteType,  confirmAddApi: AddType,  confirmEditApi: EditType,  itemData: ItemType,) => {  const currentIndex = ref<number | null>(null);  const list = ref([{}]);  const getList = () => {    fetchApi().then((res: any) => {      list.value = res.data.list;    });  };  const addItem = () => {    list.value.unshift(itemData);    currentIndex.value = 0;  };  const editItem = (index: number) => {    currentIndex.value = index;  };  const deleteItem = (index: number, item: ItemType) => {    deleteApi(item).then(() => {      list.value.splice(index, 1);    //   getList();    });  };  const cancel = (item: ItemType) => {    currentIndex.value = null;    if (!item.id) {      list.value.splice(0, 1);    }  };  const confirm = (item: ItemType) => {    const api = item.id ? confirmEditApi : confirmAddApi;    api(item).then(() => {      getList();      cancel(item);    });  };  onMounted(() => {    getList();  });  return {    list,    currentIndex,    getList,    addItem,    editItem,    deleteItem,    cancel,    confirm,  };};export default compositionApi;

接下来就是在 List 和 About 页面中的setup办法中引入即可。

<script lang='ts'>import axios from 'axios';import { defineComponent, reactive } from 'vue';import { compositionApi } from '../components/composables/index';import {ItemType} from '../types/index';const ListComponent = defineComponent({  name: 'List',  setup() {    const state = reactive({      itemData: {        id: '',        name: '',        sex: 0,        birth: '',        address: '',      },    });    const fetchApi = () => {      return axios.get('/users');    };    const deleteApi = (item: ItemType) => {      return axios.post('/users/delete', { id: item.id });    };    const confirmAddApi = (item: ItemType) => {      return axios.post('/users/add', {         name: item.name,        birth: item.birth,        address: item.address,      });    };    const confirmEditApi = (item: ItemType) => {      return axios.post('/users/edit', {        id: item.id,        name: item.name,        birth: item.birth,        address: item.address,      });    };    const localPageData = compositionApi(fetchApi, deleteApi, confirmAddApi, confirmEditApi, state.itemData);    return {      state,      ...localPageData,    };  },  data() {    return {      thead: [        'id',        'name',        'sex',        'birth',        'address',        'option',      ],    };  }});

这样 List 页面的逻辑基本上就实现了。同样,About 页面的逻辑也就实现了,不同的就是在 About 页面更改一下接口申请的地址。

最终实现成果

composition API vs Mixin

在vue3之前,代码复用的话个别都是用mixin,然而mixin相比于composition API的劣势,在官网中的解释如下:

  • mixin很容易发生冲突:因为每个个性的属性都被合并到同一个组件中,所以为了防止 property名抵触和调试,你依然须要理解其余每个个性。
  • 可重用性是无限的:咱们不能向mixin传递任何参数来扭转它的逻辑,这升高了它们在形象逻辑方面的灵活性

源代码

我的项目中用到的一些 TS 接口的定义、模仿数据及接口申请本文中没有具体介绍,如果想理解的话能够去看看源码。

戳这里:vue3_ts_admin