尝鲜vue3.x+typeScript(toDolist)案例

尝鲜vue3.x+typeScript(toDolist)案例
是借鉴了大佬的demo代码,然后在其基础上进行改写了一些方法。就是感受了下ts的一些语法以及vue3.x的一些新特性。
参考博客:https://blog.csdn.net/qq_44812835/article/details/113195479
拉下来学习了下,简单尝试解读一下代码逻辑。大佬还用了数据库,node,我就是简单的想看下vue3.x新的一些特性以及使用方法,所以就直接屏蔽掉了接口调用那一块,改为localStorage+vueX存储的方式。
效果图:
尝鲜vue3.x+typeScript(toDolist)案例_第1张图片

存在内存中的数据:
尝鲜vue3.x+typeScript(toDolist)案例_第2张图片

项目目录:
尝鲜vue3.x+typeScript(toDolist)案例_第3张图片

页面组成:
app.vue:
尝鲜vue3.x+typeScript(toDolist)案例_第4张图片

note.vue:
原有的写法是,在这里调用hook中的事件,然后事件再去store中调用action中的方法,然后再去影响mutation,进而mutation去操作数据,我直接省略了接口请求,改为内存操作,很多地方直接在页面调用mutation的方法改变数据。

<template>
  <div class="note-box">
    <van-search
      v-model="searchValue"
      placeholder="搜索便签"
      input-align="center"
      @search="handleChange"
    />
    <note-list 
    :notes="notes"
    @ToAddNote="clickItem"
    @longTouch="longTouch"
    @loadMore="loadMore" ></note-list>
    <van-popup v-model:show="show" position="bottom" :style="{ height: '10%' }" >
      <div class="delete-box" @click="handleDel">
        删除
      </div>
    </van-popup>
    <div class="loading-box">
      <van-loading size="24px" v-show="isLoading" vertical>加载中...</van-loading>
    </div>
    <van-button
      round
      icon="plus"
      class="button"
      type="primary"
      @click="clickAdd"
    ></van-button>
  </div>
  <!-- <div v-model:a="a" v-model:b="b"></div>  可以多个v-model 相当于是 .sync 的操作-->
</template>

<script lang="ts">
// 平时开发的时候 都是插件给我们提示的 现在我们可以自带提示 通过defineComponent
import { GlobalState } from "@/store";
import { computed, defineComponent, PropType, reactive, ref, ssrContextKey, toRefs, watch } from "vue";
import { useStore, Store } from "vuex";
import { useRoute, useRouter } from "vue-router";
import { Note, Page } from "../../store/typings";
import * as Types from "../../store/action-types";
import NoteList from "../../components/NoteList.vue";
import useNoteState from '../../hooks/useNoteState';
import { Notify } from 'vant';
import {throttle} from '../../utils/uiils';
export default defineComponent({
  name: "Note",
  components: {
    NoteList,
  },
  // emits: ["addnotes"], // 这样通过context.emit 就可以做提示
  setup(props, context) {
    //使用vuex
    let store = useStore<GlobalState>();
    // let { notes, getNotesByPage,deleteNote,isLoading,searchNote } = useNoteState(store);
    //解构方法
    let { notes,deleteNote,isLoading,searchNote } = useNoteState(store);
    //使用路由
    let router = useRouter();
    //初始化数据
    const state = reactive({
      searchValue: "",
      page: 1,
      size: 15,
      delId: ''
    });
    //获取初始化数据 并且缓存 如果store存在 就不重新请求
    if(store.state.note.notes.length === 0) {
      //  getNotesByPage({
      //    page:state.page,
      //    size:state.size
      //  });
      //直接操作vuex,mutation拿数据
      store.commit(`note/${Types.PUSH_NOTES}`,{paylod:1})
    }
    // 路由跳转
    const clickAdd = ()=>{
      router.push({ path: "/addNote"});
    }
    const clickItem = (id:string)=>{
      router.push({ path: "/addNote",query:{id}});
    }

    // 处理长按
    const show = ref(false);
    let id:string;
    const longTouch =async (id:string)=>{
        show.value = true;
        state.delId = id;
    }
    // 处理删除
    const handleDel= async()=>{
      // store.commit(`note/${Types.DELETE_NOTES}`),state.delId  //删除的一直是最后一条
      await deleteNote(state.delId);
      show.value = false;
    }
    // // 处理加载更多
    // const loadMore = async () => {
    //    store.commit(`note/${Types.SET_LOADIBG}`,true);
    //    state.page++;
    //    await getNotesByPage({
    //      page:state.page,
    //      size:state.size
    //    });
    //   store.commit(`note/${Types.SET_LOADIBG}`,false);
    // }
    // 处理搜索请求逻辑
    let handleChange = async ()=>{
      if(!state.searchValue){
        store.commit(`note/${Types.PUSH_NOTES}`,{paylod:1})
      } else {
        await searchNote(state.searchValue);
      }
    }
    // 处理实时搜索
    // let handleSearch = throttle(handleChange,1000);
    // watch(()=>state.searchValue,handleSearch);
    // 处理搜索
    return {
      notes, 
      clickAdd, 
      clickItem,
      longTouch,
      ...toRefs(state),
      show,
      handleDel,
      // loadMore,
      isLoading,
      handleChange
   };
  },
});
</script>
<style lang="scss" scoped>
.note-box {
  width: 100%;
  overflow: hidden;
  flex: 1;
  padding:0 0.1rem;
  box-sizing: border-box;
  .van-search {
    padding: 0;
    ::v-deep .van-search__content {
      background-color: rgb(237, 237, 237);
      border-radius: 0.15rem;
    }
    background-color: rgb(247, 247, 247);
  }
  .delete-box{
    height: 100%;
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #1989fa;
    font-size: 0.23rem;
  }
  .button {
    position: fixed;
    bottom: 0.2rem;
    right: 0.2rem;
  }
  .van-button {
    width: 0.44rem;
    height: 0.44rem;
  }
  .loading-box{
    width: 100%;
    height: 30px;
    position: fixed;
    bottom: 0rem;
  }
}
</style>


addNote.vue:

<template>
  <div class="add-note-box">
    <van-nav-bar left-arrow @click-left="onClickLeft">
      <template #right>
        <van-icon name="success" size="18"  @click="onClickLeft"/>
      </template>
    </van-nav-bar>
    <van-field
      class="field"
      v-model="note.content"
      rows="1"
      autosize
      type="textarea"
      placeholder="请输入内容"
    />
  </div>
</template>

<script lang="ts">
// 平时开发的时候 都是插件给我们提示的 现在我们可以自带提示 通过defineComponent
import router from "@/router";
import { GlobalState } from "@/store";
import { Note, Result } from "@/store/typings";
import { computed, defineComponent, onMounted, reactive, toRefs, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Store, useStore } from "vuex";
import * as Types from "../../store/action-types";
import useNoteStore from "../../hooks/useNoteState"; // 引入组定义store hooks
import * as NoteAPI from "../../api/notes";

export default defineComponent({
  name: "ToDo",
  setup(props, context) {
    let store = useStore<GlobalState>();
    // let { notes, addNote ,updateNote ,deleteNote} = useNoteStore(store);
    let { notes  ,deleteNote} = useNoteStore(store);
    const state = reactive({
      note: {
        content: "",
        dates: "",
      },
      id: "",
      oldContent:''
    });
    const routr = useRouter();
    const rout = useRoute();
    const date = new Date();

    // 点击返回提交数据(存在id为更新,并且如果存在id,内容修改为空则为删除,不存在id为添加)
    const onClickLeft = async() => {
      if (!state.note.content.trim()) {
        // 如果没有数据 判断是点击详情进入 还是点击添加进入
        if (state.id) {
          // 如果为点击详情进入,则删除
          await deleteNote(state.id);
        }
        router.go(-1);
        return;
      }
      // 有数据需要判断是添加 还是更新
      state.note.dates = `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}`;
      if (state.id) {
        if (state.oldContent !== state.note.content){ // 如果点击进来没有改变就不更新
           // 更新
          // await updateNote({
          //   id:state.id, note:state.note
          // });
          //直接操作vuex,mutation修改数据
          store.commit(`note/${Types.UPDATE_NOTES}`,{id:state.id,note:state.note})
        }
      } else {
        // await addNote(state.note);
        //直接操作vuex,mutation新增数据
        store.commit(`note/${Types.ADD_NOTES}`,state.note)
      }
      router.go(-1);
    };
    // 初始化数据
    const initNote = () => {
      const notes = store.state.note.notes;
      const id = rout.query.id;
      if (!id) return; // 如果没有id说明是点击添加
      notes.forEach((item) => {
        // 首次从store中获取
        if (item._id === id) {
          state.note.content = item.content;
          state.oldContent = item.content; // 记录初始值
          state.id = id;
        }
      });
      if (!state.note.content) {
        initNote()
        //如果刷新页面从后端请求
        // NoteAPI.getNoteById<Result<Note>>(id as string).then((data) => {
        //   state.note.content = data.data.content;
        //   state.oldContent = data.data.content;
        //   state.id = data.data._id!;
        // });
      }
    };
    onMounted(() => {
      initNote();
    });
    return {
      onClickLeft,
      ...toRefs(state),
    };
  },
});
</script>
<style lang="scss" scoped>
.add-note-box {
  width: 100%;
  overflow-x: hidden;
  overflow-y: auto;
  flex: 1;
  background-color: #fff;
}
.field{
  font-size: 0.22rem;
}
</style>

hooks中的事件:
之前所有的事件都是放开的,我注释掉了,因为上面部分操作是直接操作的store,没有经过这里。
这里只剩搜索和删除,还有暴露notes(便签了列表)出去


import { GlobalState } from "@/store";
import { Store } from 'vuex';
import { computed } from 'vue';
import { Page, Note } from '@/store/typings';
import * as Types from "../store/action-types";
// 封装note vuex相关操作 这样就解决了options API中 计算属性 方法等等要写在固定位置
// 这样的写法 就可以把很多功能封装到一个函数中
export default function useNote(store: Store<GlobalState>) {
    let notes = computed(() => store.state.note.notes); // 使用computed可以使数据变成响应式
    let isLoading = computed(()=>store.state.note.isLoading);
    // function getNotesByPage(paylod:Page) {
    //   store.dispatch(`note/${Types.PUSH_NOTES}`, paylod);
    // }
    // function addNote(paylod:Note) { // 为了获取返回值 使用异步函数封装
    //   return store.dispatch(`note/${Types.ADD_NOTES}`, paylod);
    // }
    function searchNote(paylod:string) { // 为了获取返回值 使用异步函数封装
      return store.dispatch(`note/${Types.SEARCH_NOTE}`, paylod);
    }
    // function updateNote(payload:any){
    //   return store.dispatch(`note/${Types.UPDATE_NOTES}`,payload);
    // }
    function deleteNote(payload:string){
      return store.dispatch(`note/${Types.DELETE_NOTES}`,payload);
    }
    return {
      notes,
      // getNotesByPage,
      // addNote,
      // updateNote,
      deleteNote,
      isLoading,
      searchNote
    };
  }

还有个长按的方法:

import { onMounted, onUnmounted, Ref } from 'vue';

export default function (arr:Ref<null | HTMLElement>[],callbacks: Function) {
    let timer: any = null;
    let isMoving = false;
    const touchStart = (e: any) => {
        // 类似与防抖
        console.log('start')
        let id = e.targetTouches[0].target.id;
        timer = setTimeout(() => {
            // 长按1s
            if(!isMoving){
            callbacks(id); // 执行长按的回调
            }
        }, 700);
    }
    const touchEnd = (e: any) => {
        clearTimeout(timer);
        isMoving = false;
    }
    const touchMove = () => { 
        isMoving = true;
    }
    onMounted(()=>{
        arr.forEach((item)=>{
            item.value?.addEventListener('touchstart',touchStart);
            item.value?.addEventListener('touchend',touchEnd);
            item.value?.addEventListener('touchmove',touchMove);
        });
    });
    onUnmounted(()=>{
        arr.forEach((item)=>{
            item.value?.removeEventListener('touchstart',touchStart);
            item.value?.removeEventListener('touchend',touchEnd);
            item.value?.removeEventListener('touchmove',touchMove);
        })
    })
}

store中的方法:
此前的写法是,页面->hook->store->action->api->mutation->state,
现在部分操作直接省略,页面->store->mutation->state

import { Module } from 'vuex'
import store, { GlobalState } from '../index'
import { NoteState, Note, Page, Result } from '../typings'
// import * as NoteAPI from '../../api/notes'
import * as Types from '../action-types'
const state: NoteState = {
    notes: [],
    isRequestError: false,
    isLoading: false
}
// 需要传入两个泛型 一个是 本身的state类型 和 全局的state类型 这样用的时候就可以提示了
const note: Module<NoteState, GlobalState> = {
    namespaced: true,
    state,
    mutations: {
        //查询
        [Types.SET_NOTE](state, payload: Note[]) {
            state.notes = payload
        },
        //增
        [Types.ADD_NOTES](state, payload: Note) { // 添加一条便签
            payload._id=(new Date()).toString()
            payload = JSON.parse(JSON.stringify(payload))
            if(localStorage.noteList.length&&JSON.parse(localStorage.noteList)){
                let arr = JSON.parse(localStorage.noteList)
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
            }else{
                let arr =[]
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
            }
            state.notes = JSON.parse(localStorage.noteList)
        },
        //修改
        [Types.UPDATE_NOTES](state, payload: any) {
            state.notes.forEach((note:Note) => {
                if(note._id === payload.id){
                    note.content = payload.note.content;
                    note.dates =  payload.note.dates;
                }
            });
            localStorage.noteList = JSON.stringify(state.notes)
        },
        //删除
        [Types.DELETE_NOTES](state, payload: string) {
            const index = state.notes.findIndex((note:Note) => {
                return note._id === payload;
            });
            state.notes.splice(index,1);
        },

        [Types.PUSH_NOTES](state, payload: Note[]) {
            state.notes=JSON.parse(localStorage.noteList)
        }
        // [Types.SET_ERROR](state, payload: boolean) {
        //     state.isRequestError = payload
        // },
        // [Types.SET_LOADIBG](state,payload:boolean){
        //     state.isLoading = payload
        // }
    },
    actions: {
        // 分页请求
        [Types.PUSH_NOTES]({ commit }, payload: Page) {
            // NoteAPI.getNotes<Result<Note[]>>(payload.page, payload.size).then(data => {
                if(payload.page === 1) {
                    // commit(Types.SET_NOTE, data.data);
                    if( localStorage.noteList){
                        commit(Types.SET_NOTE, JSON.parse(localStorage.noteList));
                    }else{
                        localStorage.noteList = []
                        commit(Types.SET_NOTE, []);
                    }
                // }else{
                    // commit(Types.PUSH_NOTES, data.data);
                }
            // });
        },
        [Types.SEARCH_NOTE]({ commit }, payload: string){
            // NoteAPI.getNoteListByContent<Result<Note[]>>(payload).then(data=>{
                // commit(Types.SET_NOTE, data.data);
                if( localStorage.noteList){
                    let arr = JSON.parse(localStorage.noteList)
                    arr.forEach((item:any)=>{
                        if(item.content.search(payload)>-1){
                            commit(Types.SET_NOTE, [item]);
                        }else{
                            return
                        }
                    })
                }else{
                    return
                }
            // })
        },
        // 添加note
        [Types.ADD_NOTES]({ commit }, payload: Note) {
            // return NoteAPI.addNote<Result<Note>>(payload).then(data => {
            //     commit(Types.ADD_NOTES, data.data);
            // });
            payload._id=(new Date()).toString()
            if(localStorage.noteList.length&&JSON.parse(localStorage.noteList)){
                let arr = JSON.parse(localStorage.noteList)
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
                commit(Types.ADD_NOTES, payload);
            }else{
                let arr =[]
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
                commit(Types.ADD_NOTES, payload);
            }
        },
        // 修改note
        [Types.UPDATE_NOTES]({commit,state},payload:any) {
            // return NoteAPI.updateNote(payload.id,payload.note).then(data => {
            //     commit(Types.UPDATE_NOTES, {id:payload.id,note:payload.note}); // 更新数据
            // });
            if(JSON.parse(localStorage.noteList)){
                let arr = JSON.parse(localStorage.noteList)
                arr.forEach((item:any)=>{
                    if(item._id==payload.id){
                        item.content = payload.note
                    }
                })
                localStorage.noteList = JSON.stringify(arr)
                commit(Types.UPDATE_NOTES, {id:payload.id,note:payload.note}); // 更新数据
            }
        },
        // 删除note
        [Types.DELETE_NOTES]({ commit,state }, payload: string) {
            // return NoteAPI.deleteNote<Result<number>>(payload).then(data => {
            //     commit(Types.DELETE_NOTES, payload); // 更新数据
            // })
            if(JSON.parse(localStorage.noteList)){
                let arr = JSON.parse(localStorage.noteList)
                let index = arr.findIndex((item:any)=>item==payload)
                arr.splice(index,1)
                localStorage.noteList = JSON.stringify(arr)
                commit(Types.DELETE_NOTES, payload); // 更新数据
            }
        },
    }
}

export default note

另外的store中的文件,我的理解是,定义类型还有命名字典。
定义类型:
尝鲜vue3.x+typeScript(toDolist)案例_第5张图片

命名字典:
虽然这样子看起来还是很奇怪,在store中的使用方式,和vue2.0不一样。可能这就是ts的魅力吧
尝鲜vue3.x+typeScript(toDolist)案例_第6张图片

store的index.ts:
这个就和常规的没啥很大区别了

import { createStore } from 'vuex'
import { NoteState } from './typings'
import note from './modules/note'
export interface GlobalState {
  note: NoteState
}
// 同样也变成了函数的写法
const store = createStore<GlobalState>({
  mutations: {
  },
  actions: {
  },
  modules: {
    note
  }
})
export default store

数据处理逻辑:
以新增为例(新增和修改共用页面):
页面:
尝鲜vue3.x+typeScript(toDolist)案例_第7张图片
注意那个写法,其实也就是对应执行方法名称对应的方法

store.commit(`note/${Types.ADD_NOTES}`,state.note)

store:

尝鲜vue3.x+typeScript(toDolist)案例_第8张图片

再看这里面的方法名称就懂了:

//增
        [Types.ADD_NOTES](state, payload: Note) { // 添加一条便签
            payload._id=(new Date()).toString()
            payload = JSON.parse(JSON.stringify(payload))
            if(localStorage.noteList.length&&JSON.parse(localStorage.noteList)){
                let arr = JSON.parse(localStorage.noteList)
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
            }else{
                let arr =[]
                arr.push(payload)
                localStorage.noteList = JSON.stringify(arr)
            }
            state.notes = JSON.parse(localStorage.noteList)
        },

ts加持多了很多类型限制和规范。

你可能感兴趣的:(vue,vue3.x)