项目需求
前端的待办事项应用(To-Do List)是一个经典的练习项目,非常适合初学者。这个项目虽然看似简单,但涵盖了许多前端开发的核心概念和技术。那么,本文就实现简单的增删改查功能,实现效果如下图:
使用脚手架安装相关依赖
使用vueCli 安装预设的vuex+ts+less+router
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue3_todo //create vue3_cli_todo为自定义文件名
按方向键 ↓,选择Manually select features,enter
手动配置:是我们所需要的面向生产的项目,提供可选功能的 npm 包
手动配置,根据你需要用方向键选择(按 “空格键”选择/取消选择,A键全选/取消全选,i反选)对应功能
- ? Check the features needed for your project: (Press to select, to toggle all, to invert selection)
( ) TypeScript // JavaScript的一个超集(添加了可选的静态类型和基于类的面向对象编程:类型批注和编译时类型检查、类、接口、模块、lambda 函数)
- ( ) Progressive Web App (PWA) Support // 渐进式Web应用程序
- () Router // vue-router(vue路由)
- () Vuex // vuex(vue的状态管理模式)
- () CSS Pre-processors // CSS 预处理器(如:less、sass)
- () Linter / Formatter // 代码风格检查和格式化(如:ESlint)
- () Unit Testing // 单元测试(unit tests)
- () E2E Testing // e2e(end to end) 测试
我们选择如下
确认enter后,需要选择vue版本,选择3.x的版本然后继续确认
然后如下配置
是否使用class风格的组件语法:Use class-style component syntax?输入N
目录解析
├─ babel.config.js
├─ package-lock.json
├─ package.json
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ README.md
├─ src
│ ├─ App.vue
│ ├─ assets
│ ├─ components
│ │ ├─ TodoInput //输入框组件
│ │ │ └─ index.vue
│ │ └─ TodoList
│ │ ├─ index.vue //数据组件
│ │ └─ Item.vue
│ ├─ hooks
│ │ └─ index.ts //公用的hook函数
│ ├─ main.ts //入口文件
│ ├─ shims-vue.d.ts //让Vue识别TS的文件
│ ├─ store
│ │ ├─ actions.ts
│ │ ├─ actionTypes.ts //派发事件
│ │ ├─ index.ts
│ │ ├─ mutations.ts
│ │ └─ state.ts
│ └─ typings
│ └─ index.ts //定义vuex中的数据类型,方便使用TS
└─ tsconfig.json
实现路线简析
入口文件
src\main.ts
import {
createApp } from 'vue' // 引入 Vue 3 中的 createApp 函数
import App from './App.vue' // 引入根组件 App.vue
import store from './store' // 引入 Vuex store
import './assets/style/reset.less' // 引入样式重置文件(使用 Less 预处理器)
// 创建 Vue 应用实例
const app = createApp(App)
// 使用 Vuex store 插件,并将应用挂载到 id 为 'app' 的 DOM 元素上
app.use(store).mount('#app')
store
store一个基于Vuex的状态管理模块,我们将使用它管理待办事项列表(To-Do List)的状态。通过分离actions、mutations和state,这使代码更加模块化和可维护。
│ ├─ store
│ │ ├─ actions.ts
│ │ ├─ index.ts
│ │ ├─ mutations.ts
│ │ └─ state.ts
src\store\actions.ts
import {
IState, IList } from "@/typings"
import {
Commit } from "vuex"
//context对象声明
interface Icontext {
commit: Commit,
state: IState
}
export default {
//获取数据列表
// query包含两个参数,context对象,和payload参数
// context对象包含 commit和state两个参数
query(context: Icontext,todoList: IList[]): void {
context.commit("query",todoList)
},
}
定义了一个
actions
对象,用于处理异步操作或复杂的业务逻辑。
query
方法接收context
和todoList
,使用context.commit
调用mutations
中的query
方法,更新状态。
src\store\mutations.ts
import {
IState, IList,} from "@/typings"
export default {
query(state: IState, todoList: IList[]): void{
state.list = todoList
},
}
定义了一个
mutations
对象,用于同步地修改状态。
query
方法接收当前状态state
和一个新的待办事项列表todoList
,并将新的列表赋值给状态中的list
。
src\store\state.ts
import {
IState } from "@/typings"
// 这里使用了类型断言的方式对导出的对象进行了声明: <类型>值
export default <IState> {
list: []
}
定义了应用的初始状态。
list
初始化为空数组,表示初始状态下没有待办事项。
src\store\index.ts
import {
createStore } from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
export default createStore({
state,
mutations,
actions,
})
使用Vuex的
createStore
方法创建一个新的Store实例。将定义好的
state
、mutations
和actions
模块整合在一起,形成一个完整的Vuex Store。
src\typings\index.ts
//接口返回的数据列表类型声明
interface IList {
id: number,
content: string,
status:TODO_STATUS,
createTime: number
}
//state类型定义
interface IState {
list: IList[]
}
enum TODO_STATUS {
WILLDO = 'willdo',
DOING = 'doing',
FINISHED = 'finished'
}
export{
TODO_STATUS,
IState,
IList
}
定义了类型接口和枚举,确保在操作状态时有明确的类型约束。
IList
接口描述了每个待办事项的结构。
IState
接口描述了Vuex状态中list
的结构。
TODO_STATUS
枚举定义了待办事项的三种状态:WILLDO
、DOING
、FINISHED
。
App.vue
<template>
<div class="wrapper">
<TodoInput></TodoInput>
<TodoList :todoList="todoList"></TodoList>
</div>
</template>
<script lang="ts">
import {
computed, defineComponent, onMounted } from 'vue';
import {
Store, useStore } from 'vuex';
import TodoInput from './components/TodoInput/index.vue';
import TodoList from './components/TodoList/index.vue'
import {
IUseTodo, useTodo } from './hooks';
//defineComponent是为了帮助Ts语法有更好的类型提示
export default defineComponent({
name: 'App',
components: {
TodoList,TodoInput
},
setup () {
//useTodo是封装了增删改查函数的一个总函数;IUseTodo是函数定义
const {
query }: IUseTodo = useTodo();
//获取vuex的store对象
const store: Store<any> = useStore()
onMounted( () => {
//列表初始化
query();
})
return {
todoList : computed(() => store.state.list)
}
}
});
</script>
<style lang="less">
</style>
上面的代码是一个Vue 3组件,用于管理和显示一个待办事项列表。其中
<TodoInput>
组件用于用户输入<TodoList>
组件用于列表显示hooks
//用于封装组件的公用方法
import {
Store, useStore } from "vuex"
import axios from "../proxy";
//排序函数
function compare(property : any) {
return function (value1: any, value2: any) {
let v1 = value1[property];
let v2 = value2[property];
return v2 - v1
}
}
//定义useTodo ()函数接口
export interface IUseTodo {
query: () => void,
add: (value: string) => void,
remove: (id: number) => void,
edit: (item: object) => void,
}
//封装增删改查相关函数
function useTodo (): IUseTodo {
//使用vuex中的store
const store: Store<any> = useStore();
//1.查询数据
function query() {
axios.getList().then((res)=>{
let data = res.data
data.forEach((res: {
status: any; }) => {
switch(res.status){
case 1:
res.status = 'willdo'
break;
case 0:
res.status = 'doing'
break;
case 2:
res.status = 'finished'
break;
default:
return
}
});
data = data.sort(compare('createTime'))
store.dispatch("query", data)
})
}
//2.增加数据 ----->函数输入字符串数据,没有返回值
function add(value: string): void {
let data = {
content: value,
}
axios.addList(data).then(res => {
//数据增加成功后,重新获取数据列表
query()
})
}
//3.删除数据
function remove(id: string | number): void {
axios.deleteList({
id }).then(res => {
//根据id进行数据删除
query()
})
}
//4.更改数据
function edit(item: any): void {
let data = {
id:item.id,
content:item.content,
status:2
}
axios.editList(data).then(res => {
query()
})
}
return {
query, add,remove, edit }
}
export {
useTodo
}
使用vue3提供的hooks函数将代码逻辑进行了抽象,逻辑更加清晰
proxy
import axios from 'axios';
const baseUrl = 'http://47.97.50.125:8989/todoList/'
const httpUrl = {
getList:baseUrl + 'list/?token=',
editList:baseUrl + 'editList',
deleteList:baseUrl + 'deleteList',
addList:baseUrl + 'addList',
}
//获取列表
function getList(){
return axios.get(httpUrl.getList)
}
//编辑列表
function editList(editData: object) {
return axios.post(httpUrl.editList,editData)
}
//删除列表
function deleteList(deleteData: object) {
return axios.post(httpUrl.deleteList,deleteData)
}
//新增列表
function addList(addData: object) {
return axios.post(httpUrl.addList,addData)
}
export default{
getList,
editList,
deleteList,
addList
}
上述代码片段展示了一个用于与后端待办事项(To-Do List)API进行交互的模块。这个模块使用axios
库来发送HTTP请求,并且定义了几个方法来处理不同的操作,包括获取列表、编辑列表、删除列表和新增列表。
TodoInput
src\components\TodoInput\index.vue:
<template>
<div>
<div class="wrap" @mouseleave="clearVisible = false">
<input type="text" v-model="todoValue" @keyup="setTodoValue" @focus="clearVisible = true" />
<span class="ensure" @click="ensure"> 确认</span>
<span class="clerar" v-show="clearVisible || todoValue" @click="todoValue = ''"><img src="../../assets/cleaer.svg" alt=""></span>
</div>
</div>
</template>
<script lang="ts">
import{
defineComponent ,ref}from 'vue';
import {
IUseTodo, useTodo } from '../../hooks'
export default defineComponent({
name: "TodoInput",
setup() {
const todoValue =ref<string>('');
let clearVisible = ref<Boolean>(false)
const {
add }: IUseTodo = useTodo()
//增 键盘事件
const setTodoValue = (e: KeyboardEvent): void =>{
if(e.keyCode === 13 && todoValue.value.trim().length){
//设置数据
add(todoValue.value)
//清空数据
todoValue.value = ''
}
}
//增 按钮事件
const ensure = (): void =>{
if(todoValue.value.trim().length){
//设置数据
add(todoValue.value)
//清空数据
todoValue.value = ''
}
}
return {
todoValue,
clearVisible,
setTodoValue,
ensure
}
}
})
</script>
<style lang="less" scoped>
.wrap{
display: flex;
width: 700px;
margin: 20px auto;
justify-content: center;
position: relative;
input{
width: 590px;
height: 40px;
color: #909399;
font-size: 16px;
border: 1px solid rgb(196 199 206);
border-radius: 10px 0 0 10px;
padding: 0 10px;
&:focus{
border: none;
box-shadow: 0 0 0 1px rgb(78 110 242),0 0 0 1px #BDE7FF;
outline:none;
transform: translateY(1px);
height: 38px;
}
&::after{
content: '2222';
display: block;
width: 10px;
height: 10px;
background-color: aqua;
}
}
.ensure{
cursor: pointer;
width: 112px;
display: inline-block;
height: 40px;
line-height: 40px;
background-color: rgb(78 110 242);
border-radius: 0 10px 10px 0;
font-size: 17px;
color:rgb(255 255 255);
text-align: center;
}
.clerar{
position: absolute;
height: 40px;
width: 40px;
right: 112px;
display: flex;
align-items: center;
justify-content: center;
line-height: 40px;
img{
width: 60%;
height: auto;
}
}
}
</style>
TodoList
<template>
<div>
<todo-item
v-for="item in todoList"
:key = 'item.id'
:item="item"
@remove = "remove"
@edit = "edit"
@add = "add"
></todo-item>
</div>
</template>
<script lang="ts">
import {
IUseTodo, useTodo } from '@/hooks';
import {
IList } from '@/typings';
import{
defineComponent, PropType }from 'vue';
import TodoItem from './Item.vue'
export default defineComponent({
name: "TodoList",
props: {
//类型断言
todoList: Array as PropType< IList[] >
},
components: {
TodoItem
},
setup (props) {
const {
remove, add, edit }: IUseTodo = useTodo();
return{
remove,
add,
edit,
}
}
})
</script>
<style lang="less" scoped>
</style>