In the last article Vite + Vue3 first experience - Vite article blog, I felt the runtime efficiency improvement brought by Vite, and this issue will experience the new changes brought by Vue3
Todo List Design
This time to experience Vue3
, I want to make a functional module that can experience (part of) the new features of Vue3
Todo List
about it, it should be more appropriate to use a 061f4ed0511335.
Let's plan its feature list.
- Enter
Todo
and press Enter to add a newTodo Item
. Todo Item
in a list.- You can mark
Todo Item
as complete, and the markedTodo Item
will be grayed out, and the sorting will be at the bottom. Todo Item
can be deleted, and it will not be displayed in the list after deletion.- 061f4ed0511431 can be
Todo Item
top and highlighted to increase the priority.
OK, next, let's build the basic page first.
Build the basic UI interface
Configure UI library
Currently, the UI frameworks that Vue3
Which ant-design
and elementui
from Vue2
along the way of the old UI library, and I experience Vue3
time decided to use light style ant-design
.
Install support Vue3
of ant-design-vue
it.
yarn add ant-design-vue@next
Then, configure on-demand loading, so that only the components that are used will be packaged, which can effectively reduce the size of the production package.
// vite.config.ts
import { 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(),
],
}),
]
});
Finally, import style files main.ts
// main.ts
import 'ant-design-vue/dist/antd.css';
Basic layout
Now, our layout needs an input box and a list, let's draw these two elements on the page first.
Prior to this, inApp.vue
we introduced in ourTodoList
components.
// 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>
This time I chose a set of yellow and orange color matching, let's take a look at the effect of the interface.
handle business logic
process input
Now, let's deal with our input logic. When the Enter key is pressed, the results of the input are collected and added to the Todo
array, and the input box is cleared.
Two-way binding is required here reference variable is defined to bind to the input box.
<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>
Now open the local development interface, enter a value, and press Enter, the value of the input box is cleared - this item is added to the todoList
array!
render list
After processing the input, it is now time to render the list.
v-for
syntax is still used here, and some state judgments need to be added at the same time.
<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>
This grammar is believed vue2
who have used 061f4ed05117f2, so I won't introduce it too much.
One thing to say,vscode
+volar
's support forvue3 + ts
is really good, and the code prompts and error prompts are very perfect. In the development process, it is simply a multiplier.
Handling delete and completion logic
Finally, let's deal with the logic of deletion and completion.
<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>
Finally, let's take a look at the effect of our interface. (As shown below)
Add sticky logic
We need to add a field is_top
to the array element first to determine whether the node is on top.
Then, add the logic processing and style display of the sticky function. (as follows)
<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>
Then, let's take a look at our interface effects! (As shown below)
With that, our Todo List
is complete!
Now let's take a look at our code, there are mainly two logical concerns:
todoList
related logic, responsible for the rendering of the list and related operations of the list (delete, top, complete).todoText
related logic, responsible for processing the input of the input box.
With the benefits of separating logical concerns, if I want to modify the processing logic related to the list, I only need to focus and adjust todoList
; if I want to adjust the logic related to the input, I only need to todoText
pay attention to and adjust the logic related to 061f4ed0511989.
If the logic of these two pieces becomes more and more complicated as the business develops, I can choose to split it into smaller pieces of business logic for maintenance, or split these logics into a single file Carry out maintenance management, so that you can have better control over subsequent maintenance and upgrades.
Handle front-end and back-end interaction logic
All of our previous logic was processed locally. Now let's access the server-side logic to persist all our data and changes. At the same time, let's take a look at how to deal with scenarios with front-end and back-end interaction logic Vue3
Suppose we have the following sets of interfaces (as shown below)
Then, based on the back-end interaction logic of these groups of interfaces, let's use the classic axios
.
Use yarn add axios
add dependencies.
Here, we first create a new service
src
service
we use for network requests. (as follows)
// src/service/index.ts
import axios from "axios";
const service = axios.create({
// 设置 baseURL,这个地址是我部署的后端服务
baseURL: "https://hacker.jt-gmall.com"
});
export default service;
User identity information
Todo List
we designed is an online web page, and we hope that every user who comes in sees his own Todo List
.
Let's take a look at the background interface design. He uses key
to Todo Item
, so we need to generate a unique user key
for each user when entering the page.
Let's design a function key
Hereuuid
used to generate the uniqueuser key
.
// service/auth.ts
import { 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
}
Get Todo List
Then, we go back to our TodoList.vue
file, we first write a logic to Todo
(as follows)
// TodoList.vue
import service from "@/service";
import { getUserKey } from '@/service/auth';
// 创建一个引用变量,用于绑定 Todo List 数据
const todoList = ref<{
title: string,
is_completed: boolean,
is_top: boolean
}[]>([]);
// 初始化 todo list
const getTodoList = async () => {
const reply = await service.get('/todo/get-todo-list', { params: { key: getUserKey() } });
todoList.value = reply.data.data;
}
getTodoList();
After adding the network request here, the page will not change, because the user currently has no data.
Next, we complete the rest of the logic.
Note: Thealias
alias function is used here, which needs to be configuredvite.config.ts
andtsconfig.json
import path from 'path';
// vite.config.ts
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
}
},
// ...
})
// tsconfig.json
{
"compilerOptions": {
// ...
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
}
}
Add, Stick, Finish, Delete Todo
Because users enter Todo List
view their own data, and this data can only be manipulated by themselves.
Therefore, in order to have a better user experience, after all our operation logic is completed, the echo data still uses the original logic.
Of course, when adding data, you still need to re-acquire the list data, because we need to use id
each item when operating the data.
To sum up, our refactored four functions look like this.
// 删除、完成、置顶的逻辑都与 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 = '';
}
After the logic modification is completed, let's go back to the page to check the effect! After we do some operations, refresh the page to take a look. (As shown below)
After refreshing the page, our data can still be displayed, indicating that the data has been successfully persisted on the server side!
summary
This time, we used Vue3
to complete a simple Todo List
system.
As can be seen, Vue3
of ts
support become more friendly, while the new vue
single file syntax and modular API to my experience is a little closer
React
+ JSX
. - I mean, the experience for developers is better.
Let's take a look at composite API (as shown below).
As can be seen from the above figure, our logical focus is divided into two parts, namely list related logic (rendering, operation) and adding Todo Item.
This clear division of responsibilities allows us to maintain a certain part of the function, and the related content is enclosed in a relatively small scope, which allows people to focus more on the functions that need to be adjusted.
If I were to Vue3
the (development) experience of 061f4ed0511ed7 and Vue2
8 and
6 respectively.
Well, this is the Vue3
our 061f4ed0511ef1 experience. My experience with Vue3
is still very good!
Finally, attach the Demo address this experience.
one last thing
If you have seen this, I hope you will give a like and go~
Your likes are the greatest encouragement to the author, and can also allow more people to see this article!
If you think this article is helpful to you, please help to light up star
github to encourage it!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。