乐趣区

关于前端:Vite-Vue3-初体验-Vue3-篇

在上一篇 Vite + Vue3 初体验 —— Vite 篇 博客中,我感触到了 Vite 带来的运行时效率晋升,这一期再来感触感触 Vue3 带来的新变动 —— 关注点拆散。

Todo List 设计

这次体验 Vue3,我想做一个能体验(局部)Vue3 新个性的功能模块。

想了想,用一个 Todo List 应该是比拟适合的。

咱们来布局一下它的性能清单吧。

  1. 输出 Todo,按下回车即可增加一条新的 Todo Item
  2. 以列表的模式显示所有的 Todo Item
  3. 能够将 Todo Item 标记为实现,标记实现后的 Todo Item 会置灰,并且排序处于最上面。
  4. 能够将 Todo Item 删除,删除后在列表中不展现。
  5. 能够将 Todo Item 置顶,高亮显示,以进步优先级。

OK,接下来,咱们先把根底页面搭建进去吧。

搭建根底 UI 界面

配置 UI 库

目前反对 Vue3 的 UI 框架有上面几种:

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

其中 ant-designelementui 是从 Vue2 一路走来的老 UI 库了,我在体验 Vue3 的时候决定还是应用轻格调的 ant-design

先装置反对 Vue3ant-design-vue 吧。

yarn add ant-design-vue@next

而后,再配置一下按需加载,这样的话,只有被应用到的组件才会被打包,可无效减小生产包的体积。

// 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(),
      ],
    }),
  ]
});

最初,在 main.ts 中引入款式文件。

// main.ts
import '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 + volarvue3 + 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 就实现了!

当初再来看看咱们的代码,次要是有两块逻辑关注点:

  1. todoList 相干逻辑,负责列表的渲染以及列表的相干操作(删除、置顶、实现)。
  2. todoText 相干逻辑,负责解决输入框的输出。

在拆散了逻辑关注点后带来的益处时,如果我想要批改列表相干的解决逻辑,我只须要关注和调整 todoList 相干的代码即可;如果我想要调整输出相干的逻辑,我只须要关注和调整 todoText 相干的逻辑即可。

如果这两块的逻辑前面随着业务倒退而变得越来越简单了,我能够抉择将其拆分成更小块的业务逻辑来进行保护,还能够将这些逻辑都拆分到单文件中进行保护治理,这样对于后续的保护和降级都可能有更好的把控。

解决前后端交互逻辑

咱们之前所有的逻辑都是在本地做的解决,当初咱们来接入服务端的逻辑,将咱们的所有数据及变更进行长久化。同时,咱们也来看看在 Vue3 中,如何解决有前后端交互逻辑的场景。

假如咱们有上面这么几组接口(如下图)

那么,基于这几组接口的后端交互逻辑,咱们还是用经典的 axios 来做吧。

应用 yarn add axios 增加依赖。

这里,咱们先在 src 目录下新建一个 service,用于初始化咱们用于网络申请的 service。(如下)

// src/service/index.ts
import 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.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}

获取 Todo List

而后,咱们回到咱们的 TodoList.vue 文件,咱们先写一个获取远端 Todo 列表的逻辑。(如下)

// 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();

这里加上网络申请后,页面也是不会有什么变动的,因为这个用户目前是没有数据的。

接下来,咱们把剩下的几个逻辑都补全。

留神:这里应用到了 alias 别名性能,须要在 vite.config.tstsconfig.json 中进行配置。

import path from 'path';

// vite.config.ts
export 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 零碎。

能够看出,Vue3ts 的反对变得更敌对了,而新的 vue 单文件语法和 组合式 API 给我的体验也有点靠近 React + JSX。—— 我的意思是,给开发者的体验更好了。

咱们再来看看咱们用 组合式 API 实现的逻辑局部(如下图)。

从上图能够看出,咱们的逻辑关注点被分成了两大块,别离是列表相干逻辑(渲染、操作)和新增 Todo Item。

这种清晰的职责划分使得咱们须要保护某一部分的性能时,与之相干的内容都被圈在了一个比拟小的范畴,可能让人更加聚焦到须要调整的性能上。

如果当初让我给 Vue3Vue2 的(开发)体验打个分的话,我会别离给出 8 分 6 分

好啦,咱们这次的 Vue3 体验就到此为止了,Vue3 给我的体验还是十分不错的!

最初附上本次体验的 Demo 地址。

最初一件事

如果您曾经看到这里了,心愿您还是点个赞再走吧~

您的点赞是对作者的最大激励,也能够让更多人看到本篇文章!

如果感觉本文对您有帮忙,请帮忙在 github 上点亮 star 激励一下吧!

退出移动版