使用Vue3 composition-api重写一个抽象可复用的增删改查页面
2020/5/13 11:55:30
本文主要是介绍使用Vue3 composition-api重写一个抽象可复用的增删改查页面,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
vue3.0 beta版本已经发布一段时间了,尝试着用composition-api来重写一个简单的后台管理系统中的增删改查。
对于常用的增删改查的后台管理页面,通常的为表格+详情页的模式,主要包含如下几个功能:
- 表格用于展示数据内容
- 点击表格中的一项,能够弹出详情页进行编辑。
- 保存或取消更新表格数据。
这种常用的模式在vue2下,这次试着用vue3的api来改写,让我们抛弃被吐槽了很多的mixin,拥抱hooks。
使用@vue/cli创建项目
首先确认@vue/cli为最新版本4.3.1,否则升级的时候可能会出现错误,执行:
vue create vue3-admin-demo cd vue3-admin-demo vue add vue-next 复制代码
安装后检查package.json
中的vue版本为3.0.0就成功了,注意创建的时候,如果有vuex及vue-router,需要先手动勾选后,再执行vue add vue-next
,vuex及vue-router便会自动升级到4.0版本。
写一个简单的管理页面
初始化
main.js中初始化vue的方式也有了区别,vue不再通过export default 方式暴露,而是使用对应的api,这里使用vuex及vue-router的use引入也是用类似链式调用的方式,如下:
import { createApp } from 'vue'; import App from './App.vue' import router from './router' import store from './store' createApp(App).use(router).use(store).mount('#app') 复制代码
引入vue-router后,App.vue便可以直接使用router-link,我们在默认的模板里增加一条路由信息:
<template> <div id="app"> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/staff">Staff</router-link> </div> <router-view/> </div> </template> 复制代码
创建路由
在views
目录创建staff.vue
的页面,然后去router/index.js
更新一下路由:
const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/staff', name: 'Staff', component: () => import(/* webpackChunkName: "about" */ '../views/Staff.vue') } ] 复制代码
在Staff.vue中随便写点什么,预览一下:
OK,路由准备完成,下面开始编写内部表格编写页面模板
这里我们简单定义数据为一个人员列表,结构如下,这里跟vue2没有太大区别。
<div class="home"> <button>新增</button> <table> <thead> <th>姓名</th> <th>部门</th> <th>职位</th> <th>入职日期</th> <th>操作</th> </thead> <tbody> <tr> <td>name</td> <td>department</td> <td>position</td> <td>date</td> <td> <button>编辑</button> <button>删除</button> </td> </tr> </tbody> </table> <div class="dialog-detail" v-show="isShowDetail"> <div class="form-item"> <span class="label">姓名:</span> <input type="text" v-model="state.form.name"> </div> <div class="form-item"> <span class="label">部门:</span> <input type="text" v-model="state.form.department"> </div> <div class="form-item"> <span class="label">职位:</span> <input type="text" v-model="state.form.position"> </div> <div class="form-item"> <span class="label">入职日期:</span> <input type="text" v-model="state.form.date"> </div> <div class="btn-group"> <button @click="confirmItem(state.form)">确认</button> <button @click="cancelEdit">取消</button> </div> </div> </html> 复制代码
js部分采用setupAPI进行改写,Vue3中使用ref
和reactive
来定义响应式对象,其中ref
可以定义简单的数字,字符串等变量,例如
import { ref } from 'vue' const count = ref(0); const isCancel = ref(true); 复制代码
如果需要对复杂类型的变量如object
之类的,就需要用reactive
方法来进行定义,我们先定义一个form对象,用于绑定详情页的数据,用于后续编辑和新增。
import { reactive } from 'vue'; import { usePageData } from '../components/PageData'; import { fetchStaff } from '../api'; export default { name: 'Staff', setup() { // form对象用于v-model绑定 const state = reactive({ form: { name: '', department: '', position: '', date: '' } }) const pageData = usePageData(fetchStaff, state.form); return { state, ...pageData, } } } 复制代码
这里看到了使用了一个usePageData
和fetchStaff
,是实现的关键,
复用逻辑抽象
可以看到,staff.vue的完整代码比较简洁,我们考虑到:未来需要拓展的话,不同表格,只要定义标准API返回的数据格式,对于获取数据,查看详情, 编辑,确认,取消,之类的逻辑几乎是一模一样的,对于不同页面来说,只有api地址不同,其他逻辑都可以复用和抽象。
在以前,可能会mixin方式来进行混入,不同页面引入这个mixin,调整一下data里面的api地址即可。但当Mixin变的多的时候,就会存在很多问题,如配置项过于分散,变量,方法难以追踪,不知道是哪个mix进来的,重名的时候没有很好的办法处理等。
Vue3最终都是setup,如果我们使用函数式的方法,把相关的逻辑都封装在一个函数内,最终暴露给需要使用setup即可。在React hooks中的也是这样类似的思想。于是我们有了usePageData
。
usePageData
这个方法其实类似vue2中的mixin,但是更加灵活,函数式的编程思路也让逻辑上更加统一。我们先添加一些操作数据的方法:
export const usePageData = (fetchApi, form) => { // 表格操作方法 const addItem = () => {}; const editItem = index => {}; const deleteItem = index => {}; // 详情操作方法 const confirmItem = item => {}; const cancelEdit = () => {}; return { addItem, editItem, deleteItem, confirmItem, cancelEdit } }) 复制代码
return出去的值,可以直接给页面模板中使用,对应的修改一下staff.vue中的按钮事件:
<button @click="addItem">新增</button> <button @click="editItem(index)">编辑</button> <button @click="deleteItem(index)">删除</button> ... <div class="btn-group"> <button @click="confirmItem(state.form)">确认</button> <button @click="cancelEdit">取消</button> </div> 复制代码
继续回到pagedata.js:
初始化的时候需要获取接口数据,这里不同对应页面的api不同,所以usePageData增加一个fetchApi参数,在mounted中完成,vue3需要引入onMounted方法
,添加代码:
import { onMounted } from 'vue'; export const usePageData = (fetchApi, form) => { ... onMounted(async () => { let resList = await fetchApi(); console.log(resList) }); return { ... } 复制代码
fetchApi:
回到staff.vue,调用的时候是这样:
import { fetchStaff } from '../api'; const pageData = usePageData(fetchStaff, state.form); 复制代码
先看看fetchStaff
,主要是对api的封装,用于请求接口:
api.js中fetchStaff 对底层接口封装,这里使用本地数据来模拟
import request from '../utils/request'; export const fetchStaff = query => { return request({ url: './staff.json', method: 'get', params: query }); }; 复制代码
request.js 使用axios作为ajax的简单封装
import axios from 'axios'; const service = axios.create({ timeout: 5000 }); service.interceptors.request.use( config => { return config; }, error => { console.log(error); return Promise.reject(); } ); service.interceptors.response.use( response => { if (response.status === 200) { return response.data; } else { Promise.reject(); } }, error => { console.log(error); return Promise.reject(); } ); export default service; 复制代码
到现在可以测试console.log(resList),可以验证数据是否正确返回了。
引入Store
在store/index.js中添加代码,vue3中store通过createStore
方式创建,完整代码如下:
import { createStore } from "vuex"; export default createStore({ state: { pageData: {}, // 保存当前页面列表数据和总条目 activeIndex: null //当前激活的项(即正在编辑的) }, getters: { // 当前正在编辑的项,根据activeIndex寻找 activeItem: state => { const {list} = state.pageData; if(state.activeIndex !== null && state.activeIndex !== undefined && state.activeIndex > -1 && list && list.length ) { return list[state.activeIndex]; } return null; }, }, mutations: { // 设置正在编辑的下标 SET_ACTIVE_ITEM(state, index) { state.activeIndex = index; }, // 获取整体页面数据 GET_PAGE_DATA(state, payload) { state.pageData = payload; }, // 详情页中的“确定”操作 // 需要判断是否存在Item参数,用于区分是编辑还是新增的情况 CONFIRM_EDIT_ITEM(state, item) { const {activeIndex, pageData} = state; const {list} = pageData; if (!list) { return; } if (activeIndex) { Object.assign(list[activeIndex], item); } else { list.push(Object.assign({}, item)); } // 确定完成后清空activeIndex state.activeIndex = null; }, // 详情页中的“取消”操作,清空当前正在编辑的下标 CLEAR_ACTIVE_ITEM(state) { state.activeIndex = null; }, // 删除数据中的一项 DELETE_ITEM(state, index) { const {list} = state.pageData; list.splice(index, 1); } }, actions: { GET_PAGE_DATA({ commit }, payload) { commit('GET_PAGE_DATA', payload) }, SET_ACTIVE_ITEM({ commit }, index) { commit('SET_ACTIVE_ITEM', index) }, CONFIRM_EDIT_ITEM({ commit }, item) { commit('CONFIRM_EDIT_ITEM', item) }, CLEAR_ACTIVE_ITEM({ commit }) { commit('CLEAR_ACTIVE_ITEM') }, DELETE_ITEM({commit}, index) { commit('DELETE_ITEM', index) } } }); 复制代码
在pageData中引入store
对按钮操作进行修改,pageData完整代码如下:
import { onMounted, computed, watch, ref } from 'vue'; // 通过useStore引入 import { useStore } from 'vuex'; export const usePageData = (fetchApi, form) => { // 对form进行拷贝一份原始值,用于保存后对数据的清空。 const initForm = Object.assign({}, form); const store = useStore(); // 这里store中的页面数据,用于显示 const pageData = computed(() => store.state.pageData); const activeIndex = computed(() => store.state.activeIndex); // activeItem即存在编辑中的变量,这里需要把form内的值更新成当前激活的数据 const activeItem = computed(() => store.getters.activeItem); watch(activeItem, () => { if (!activeItem.value) return; for (let key in form) { form[key] = activeItem.value[key]; } }); let isShowDetail = ref(false); const editItem = index => { isShowDetail.value = true; store.dispatch('SET_ACTIVE_ITEM', index); }; const addItem = () => { isShowDetail.value = true; store.dispatch('SET_ACTIVE_ITEM', null); }; const deleteItem = index => { store.dispatch('DELETE_ITEM', index); }; const confirmItem = item => { isShowDetail.value = false; store.dispatch('CONFIRM_EDIT_ITEM', item); Object.assign(form, initForm); }; const cancelEdit = () => { isShowDetail.value = false; store.dispatch('CLEAR_ACTIVE_ITEM'); Object.assign(form, initForm); }; onMounted(async () => { let resList = await fetchApi(); store.dispatch('GET_PAGE_DATA', resList); }); return { isShowDetail, pageData, activeIndex, editItem, addItem, confirmItem, cancelEdit, deleteItem } } 复制代码
相关的数据和变量都处理好了,更新一下页面模板:
<template> <div class="home"> <button @click="addItem">新增</button> <table> <thead> <th>姓名</th> <th>部门</th> <th>职位</th> <th>入职日期</th> <th>操作</th> </thead> <tbody> <tr v-for="(staff, index) in pageData.list" :key="staff.id"> <td>{{staff.name}}</td> <td>{{staff.department}}</td> <td>{{staff.position}}</td> <td>{{staff.date}}</td> <td> <button @click="editItem(index)">编辑</button> <button @click="deleteItem(index)">删除</button> </td> </tr> </tbody> </table> <div>总数:{{pageData.total}}</div> <div class="dialog-detail" v-show="isShowDetail"> <div class="form-item"> <span class="label">姓名:</span> <input type="text" v-model="state.form.name"> </div> <div class="form-item"> <span class="label">部门:</span> <input type="text" v-model="state.form.department"> </div> <div class="form-item"> <span class="label">职位:</span> <input type="text" v-model="state.form.position"> </div> <div class="form-item"> <span class="label">入职日期:</span> <input type="text" v-model="state.form.date"> </div> <div class="btn-group"> <button @click="confirmItem(state.form)">确认</button> <button @click="cancelEdit">取消</button> </div> </div> </div> </template> 复制代码
效果
最后看一下结果,页面初始化加载默认数据:
新增:编辑:
删除:
总结
通过composition-api方式来组织代码,带来了新的编程体验,但是也对编程者的要求变得更高,如何在可维护,可复用,可理解中找到平衡点,也是对我们的一个挑战。
本文案例完整代码见: github.com/ccxryan/vue…
一些参考链接:
- vue-composition-api-rfc.netlify.app/#summary
- zhuanlan.zhihu.com/p/68477600
这篇关于使用Vue3 composition-api重写一个抽象可复用的增删改查页面的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南