头图

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.

  1. Enter Todo and press Enter to add a new Todo Item .
  2. Todo Item in a list.
  3. You can mark Todo Item as complete, and the marked Todo Item will be grayed out, and the sorting will be at the bottom.
  4. Todo Item can be deleted, and it will not be displayed in the list after deletion.
  5. 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

  1. Ant Design Vue
  2. Element Plus
  3. Ionic
  4. Native UI

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, in App.vue we introduced in our TodoList 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.

image

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 for vue3 + 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)

image

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)

image

With that, our Todo List is complete!

Now let's take a look at our code, there are mainly two logical concerns:

  1. todoList related logic, responsible for the rendering of the list and related operations of the list (delete, top, complete).
  2. 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)

image

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

Here uuid used to generate the unique user 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: The alias alias function is used here, which needs to be configured vite.config.ts and tsconfig.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)

image

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).

image

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!


晒兜斯
1.8k 声望534 粉丝