在上一篇 Vite + Vue3 初体验 —— Vite 篇 博客中,我感触到了 Vite 带来的运行时效率晋升,这一期再来感触感触 Vue3
带来的新变动 —— 关注点拆散。
Todo List 设计
这次体验 Vue3
,我想做一个能体验(局部) Vue3
新个性的功能模块。
想了想,用一个 Todo List
应该是比拟适合的。
咱们来布局一下它的性能清单吧。
- 输出
Todo
,按下回车即可增加一条新的Todo Item
。 - 以列表的模式显示所有的
Todo Item
。 - 能够将
Todo Item
标记为实现,标记实现后的Todo Item
会置灰,并且排序处于最上面。 - 能够将
Todo Item
删除,删除后在列表中不展现。 - 能够将
Todo Item
置顶,高亮显示,以进步优先级。
OK,接下来,咱们先把根底页面搭建进去吧。
搭建根底 UI 界面
配置 UI 库
目前反对 Vue3
的 UI 框架有上面几种:
- Ant Design Vue
- Element Plus
- Ionic
- Native UI
其中 ant-design
和 elementui
是从 Vue2
一路走来的老 UI 库了,我在体验 Vue3
的时候决定还是应用轻格调的 ant-design
。
先装置反对 Vue3
的 ant-design-vue
吧。
yarn add ant-design-vue@next
而后,再配置一下按需加载,这样的话,只有被应用到的组件才会被打包,可无效减小生产包的体积。
// vite.config.tsimport { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import Components from 'unplugin-vue-components/vite'import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'// https://vitejs.dev/config/export default defineConfig({ plugins: [ vue(), Components({ resolvers: [ AntDesignVueResolver(), ], }), ]});
最初,在 main.ts
中引入款式文件。
// main.tsimport 'ant-design-vue/dist/antd.css';
根底布局
当初,咱们的布局须要一个输入框和一个列表,咱们先在页面把这两个元素画进去吧。
在此之前,在App.vue
中引入了咱们的TodoList
组件。
// TodoList.vue<script setup lang="ts">import { DeleteOutlined, CheckOutlined, CheckCircleFilled } from '@ant-design/icons-vue';import { Input } from "ant-design-vue";</script><template> <section class="todo-list-container"> <section class="todo-wrapper"> <Input class="todo-input" placeholder="请输出待办项" /> <section class="todo-list"> <section class="todo-item"> <span>Todo Item</span> <div class="operator-list"> <DeleteOutlined /> <CheckOutlined /> </div> </section> <section class="todo-item"> <span>Todo Item</span> <div class="operator-list"> <DeleteOutlined /> <CheckOutlined /> </div> </section> <section class="todo-item todo-checked"> <span>Todo Item</span> <div class="operator-list"> <CheckCircleFilled /> </div> </section> </section> </section> </section></template><style scoped lang="less">.todo-list-container { display: flex; justify-content: center; width: 100vw; height: 100vh; box-sizing: border-box; padding-top: 100px; background: linear-gradient(rgba(93, 190, 129, .02), rgba(125, 185, 222, .02)); .todo-wrapper { width: 60vw; .todo-input { width: 100%; height: 50px; font-size: 18px; color: #F05E1C; border: 2px solid rgba(255, 177, 27, 0.5); border-radius: 5px; } .todo-input::placeholder { color: #F05E1C; opacity: .4; } .ant-input:hover, .ant-input:focus { border-color: #FFB11B; box-shadow: 0 0 0 2px rgb(255 177 27 / 20%); } .todo-list { margin-top: 20px; .todo-item { box-sizing: border-box; padding: 15px 10px; cursor: pointer; border-bottom: 2px solid rgba(255, 177, 27, 0.3); color: #F05E1C; margin-bottom: 5px; font-size: 16px; transition: all .5s; display: flex; justify-content: space-between; align-items: center; padding-right: 10px; .operator-list { display: flex; justify-content: flex-start; align-items: center; :first-child { margin-right: 10px; } } } .todo-checked { color: rgba(199, 199, 199, 1); border-bottom-color: rgba(199, 199, 199, .4); transition: all .5s; } .todo-item:hover { box-shadow: 0 0 5px 8px rgb(255 177 27 / 20%); border-bottom: 2px solid transparent; } .todo-checked:hover { box-shadow: none; border-bottom-color: rgba(199, 199, 199, .4); } } }}</style>
这次我选了一套黄橙配色,咱们来看看界面的成果吧。
解决业务逻辑
解决输出
当初,咱们来解决一下咱们的输出逻辑,在按下回车键时,将输出的后果收集起来增加到 Todo
数组中,并且将输入框清空。
这里须要用到双向绑定,定义一个 援用
变量,与输入框进行绑定。
<script setup lang="ts">import { ref } from "vue";// 创立一个援用变量,用于绑定 Todo List 数据const todoList = ref<{ title: string, is_completed: boolean}[]>([]);// 创立一个援用变量,用于绑定输入框const todoText = ref('');const onTodoInputEnter = () => { // 将 todo item 增加到 todoList 中 todoList.value.unshift({ title: todoText.value, is_completed: false }); // 增加到 todoList 后,清空 todoText 的值 todoText.value = '';}</script><template> //... <!-- v-model:value 语法是 vue3 的新个性,代表组件外部进行双向绑定是值 key 是 value --> <Input v-model:value="todoText" @keyup.enter="onTodoInputEnter" class="todo-input" placeholder="请输出待办项" /></template>
当初关上本地开发界面,输出一个值,而后按下回车,输入框的值就被清空了 —— 将这一项增加到了 todoList
数组中!
渲染列表
在解决好了输出之后,当初须要将列表渲染进去。
这里还是用经典的 v-for
语法,同时须要加上一些状态的判断。
<section class="todo-list"> <section v-for="item in todoList" class="todo-item" :class="{'todo-completed': item.is_completed}"> <span>{{item.title}}</span> <div class="operator-list"> <CheckCircleFilled v-show="item.is_completed" /> <DeleteOutlined v-show="!item.is_completed" /> <CheckOutlined v-show="!item.is_completed" /> </div> </section></section>
这个语法置信用过 vue2
的都分明,就不做过多介绍了。
有一说一,vscode
+volar
对vue3 + ts
的反对是真不错,代码提醒和谬误提醒都十分欠缺了。在开发过程中,几乎是事倍功半。
解决删除和实现逻辑
最初,咱们来解决一下删除和实现的逻辑吧。
<script setup lang="ts">// 创立一个援用变量,用于绑定 Todo List 数据const todoList = ref<{ title: string, is_completed: boolean}[]>([]);// 删除和实现的逻辑都与 todoList 放在同一个中央,这样对于逻辑关注点就更加聚焦了const onDeleteItem = (index: number) => { todoList.value.splice(index, 1);}const onCompleteItem = (index: number) => { todoList.value[index].is_completed = true; // 从新排序,将曾经实现的我的项目往后排列 todoList.value = todoList.value.sort(item => item.is_completed ? 0 : -1);}</script><template> //... <DeleteOutlined v-show="!item.is_completed" @click="onDeleteItem(index)" /> <CheckOutlined v-show="!item.is_completed" @click="onCompleteItem(index)" /></template>
最初,来看看咱们界面的成果吧。(如下图)
退出置顶逻辑
咱们须要先给数组元素增加一个字段 is_top
,用于判断该节点是否置顶。
而后,再退出置顶函数的逻辑解决以及款式显示。(如下)
<script setup lang="ts">// 创立一个援用变量,用于绑定 Todo List 数据const todoList = ref<{ title: string, is_completed: boolean, is_top: boolean}[]>([]);const onTopItem = (index: number) => { todoList.value[index].is_top = true; // 从新排序,将曾经实现的我的项目往前排列 const todoItem = todoList.value.splice(index, 1); todoList.value.unshift(todoItem[0]);}</script><template> //... <section class="todo-list"> <section v-for="(item, index) in todoList" class="todo-item" :class="{'todo-completed': item.is_completed, 'todo-top': item.is_top}"> <span>{{item.title}}</span> <div class="operator-list"> <CheckCircleFilled v-show="item.is_completed" /> <DeleteOutlined v-show="!item.is_completed" @click="onDeleteItem(index)" /> <ToTopOutlined v-show="!item.is_completed" @click="onTopItem(index)" /> <CheckOutlined v-show="!item.is_completed" @click="onCompleteItem(index)" /> </div> </section> </section></template>
而后,咱们来看看咱们的界面成果吧!(如下图)
这样一来,咱们的 Todo List
就实现了!
当初再来看看咱们的代码,次要是有两块逻辑关注点:
todoList
相干逻辑,负责列表的渲染以及列表的相干操作(删除、置顶、实现)。todoText
相干逻辑,负责解决输入框的输出。
在拆散了逻辑关注点后带来的益处时,如果我想要批改列表相干的解决逻辑,我只须要关注和调整 todoList
相干的代码即可;如果我想要调整输出相干的逻辑,我只须要关注和调整 todoText
相干的逻辑即可。
如果这两块的逻辑前面随着业务倒退而变得越来越简单了,我能够抉择将其拆分成更小块的业务逻辑来进行保护,还能够将这些逻辑都拆分到单文件中进行保护治理,这样对于后续的保护和降级都可能有更好的把控。
解决前后端交互逻辑
咱们之前所有的逻辑都是在本地做的解决,当初咱们来接入服务端的逻辑,将咱们的所有数据及变更进行长久化。同时,咱们也来看看在 Vue3
中,如何解决有前后端交互逻辑的场景。
假如咱们有上面这么几组接口(如下图)
那么,基于这几组接口的后端交互逻辑,咱们还是用经典的 axios
来做吧。
应用 yarn add axios
增加依赖。
这里,咱们先在 src
目录下新建一个 service
,用于初始化咱们用于网络申请的 service
。(如下)
// src/service/index.tsimport axios from "axios";const service = axios.create({ // 设置 baseURL,这个地址是我部署的后端服务 baseURL: "https://hacker.jt-gmall.com"});export default service;
用户身份信息
咱们设计的 Todo List
是一个在线网页,咱们心愿每个用户进来看到的都是本人的 Todo List
。
咱们来看看后盾的接口设计,他应用 key
来给 Todo Item
做分组,所以咱们须要在进入页面时,为每一个用户生成一个举世无双的 user key
。
咱们先设计一个用来获取 key
的函数吧。
这里应用uuid
来生成惟一的user key
。
// service/auth.tsimport { v4 as uuid } from "uuid";const getUserKey = () => { if (localStorage.getItem('user_key')) return localStorage.getItem('user_key'); const userKey = uuid(); localStorage.setItem('user_key', userKey); return userKey;}export { getUserKey}
获取 Todo List
而后,咱们回到咱们的 TodoList.vue
文件,咱们先写一个获取远端 Todo
列表的逻辑。(如下)
// TodoList.vueimport service from "@/service";import { getUserKey } from '@/service/auth';// 创立一个援用变量,用于绑定 Todo List 数据const todoList = ref<{ title: string, is_completed: boolean, is_top: boolean}[]>([]);// 初始化 todo listconst getTodoList = async () => { const reply = await service.get('/todo/get-todo-list', { params: { key: getUserKey() } }); todoList.value = reply.data.data;}getTodoList();
这里加上网络申请后,页面也是不会有什么变动的,因为这个用户目前是没有数据的。
接下来,咱们把剩下的几个逻辑都补全。
留神:这里应用到了alias
别名性能,须要在vite.config.ts
和tsconfig.json
中进行配置。
import path from 'path';// vite.config.tsexport default defineConfig({ resolve: { alias: { "@": path.resolve(__dirname, "src"), } }, // ...})
// tsconfig.json{ "compilerOptions": { // ... "baseUrl": "./", "paths": { "@/*": ["./src/*"] } }}
新增、置顶、实现、删除 Todo
因为用户进入 Todo List
查看的都是本人的数据,并且该数据只有本人可操作。
所以,也是为了能有更好的用户体验,在咱们所有的操作逻辑实现后,回显数据还是用原有的逻辑。
当然,新增数据时,还是须要从新获取列表数据,因为咱们操作数据时须要用到每一项的 id
。
综上所述,咱们重构后的四个函数长这样。
// 删除、实现、置顶的逻辑都与 todoList 放在同一个中央,这样对于逻辑关注点就更加聚焦了const onDeleteItem = async (index: number) => { const id = todoList.value[index].id; await service.post('/todo/delete', { id }); todoList.value.splice(index, 1);}const onCompleteItem = async (index: number) => { const id = todoList.value[index].id; await service.post('/todo/complete', { id }); todoList.value[index].is_completed = true; // 从新排序,将曾经实现的我的项目往后排列 const todoItem = todoList.value.splice(index, 1); todoList.value.push(todoItem[0]);}const onTopItem = async (index: number) => { const id = todoList.value[index].id; await service.post('/todo/top', { id }); todoList.value[index].is_top = true; // 从新排序,将曾经实现的我的项目往前排列 const todoItem = todoList.value.splice(index, 1); todoList.value.unshift(todoItem[0]);}// 新增 Todo Item 的逻辑都放在一处// 创立一个援用变量,用于绑定输入框const todoText = ref('');const addTodoItem = () => { // 新增一个 TodoItem,申请新增接口 const todoItem = { key: getUserKey(), title: todoText.value } return service.post('/todo/add', todoItem);}const onTodoInputEnter = async () => { if (todoText.value === '') return; await addTodoItem(); await getTodoList(); // 增加胜利后,清空 todoText 的值 todoText.value = '';}
逻辑批改实现后,咱们回到页面查看一下成果吧!咱们做一些操作后,刷新页面查看一下。(如下图)
刷新页面后,咱们的数据仍然是能够展现进去的,阐明数据曾经胜利做了服务端长久化啦!
小结
这次,咱们用 Vue3
来实现了一个简略的 Todo List
零碎。
能够看出,Vue3
对 ts
的反对变得更敌对了,而新的 vue
单文件语法和 组合式 API
给我的体验也有点靠近 React
+ JSX
。 —— 我的意思是,给开发者的体验更好了。
咱们再来看看咱们用 组合式 API
实现的逻辑局部(如下图)。
从上图能够看出,咱们的逻辑关注点被分成了两大块,别离是列表相干逻辑(渲染、操作)和新增 Todo Item。
这种清晰的职责划分使得咱们须要保护某一部分的性能时,与之相干的内容都被圈在了一个比拟小的范畴,可能让人更加聚焦到须要调整的性能上。
如果当初让我给 Vue3
和 Vue2
的(开发)体验打个分的话,我会别离给出 8分
和 6分
。
好啦,咱们这次的 Vue3
体验就到此为止了,Vue3
给我的体验还是十分不错的!
最初附上本次体验的 Demo 地址。
最初一件事
如果您曾经看到这里了,心愿您还是点个赞再走吧~
您的点赞是对作者的最大激励,也能够让更多人看到本篇文章!
如果感觉本文对您有帮忙,请帮忙在 github 上点亮 star
激励一下吧!