通过实战TodoList理解vue3和vue2

120 阅读4分钟

这是我参与2022首次更文挑战的第20活动详情查看:2022首次更文挑战」。

vue2.0

父类

父类提供给一个改变传给子类,测试子类watch监听的反应 子类

子类通过实现一个入门必备项目todoList进行增删改查的功能。

<template>
  <div class="main">
    <div class="header">---------------子类功能-------------------</div>
    <div>
      <input type="text" v-model="currentValue" />
      <button @click="handleSearch">搜索</button>
      <button @click="handleAdd">添加</button>
    </div>

    <ul class="list">
      <li v-for="(item, index) in respository" :key="index">
        {{ item }}
        <button @click="handleDelete(index)">删除</button>
      </li>
    </ul>
  </div>
</template>

<script>
import { fetchTodoList } from './api.js';

export default {
  props: {
    user: {
      type: Number,
      required: true,
    },
  },
  data() {
    return {
      currentValue: '',
      respository: [],
      searchQuery: '1232',
    };
  },

  computed: {
    repositoriesMatchingSearchQuery() {
      return this.respository.find(item => item.name === this.searchQuery);
    },
  },
  watch: {
    user: 'getUserRespository',
  },
  mounted() {
    this.getUserRespository();
  },
  methods: {
    async getUserRespository() {
      this.respository = await fetchTodoList();
    },

    handleSearch() {
      const target = this.respository.find(item => item === this.currentValue);
      this.respository = [target];
    },

    handleAdd() {
      this.respository.push(this.currentValue);
      this.currentValue = '';
    },

    handleDelete(key) {
      this.respository = this.respository.filter((item, index) => key !== index);
    },
  },
};
</script>

image.png

Vue3.0

Vue2.0中,随着功能的增加,组件变得越来越复杂,越来越难维护,而难以维护的根本原因是Vue的API设计迫使开发者使用watch,computed,methods选项组织代码,而不是实际的业务逻辑。
另外Vue2.0缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以minxis完成逻辑复用,但是当mixin变多的时候,会使得难以找到对应的data、computed或者method来源于哪个mixin,使得类型推断难以进行。 看上面的2.0代码你发现数据,方法,属性并且将多个功能写在一起后,写起来超级简单

2.1 get请求模块改造

首先改造第一阶段,获取todoList的请求数据,并且设置在respository的仓库列表里面,牵涉到请求数据,生命周期,data数据,方法。将这几个vue2.0散在不同地方的组织成一个复用可抽离的模块。

首先是请求使用Ts改造成js请求api方法,主要是添加类型控制

import axios from "axios";

interface DataType {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}
export async function fetchTodoList(): Promise<string[]> {
  const res = await axios.get("https://jsonplaceholder.typicode.com/todos");
  return res?.data?.slice(0, 4).map((item: DataType) => item.title);
}

其次是功能setup的改造,可以理解为setup 选项是一个接收 props 和 context 的函数,。此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板,也就是说他可以喝vue2.0的其他特性共存,也就是computed可以单独写,也可以在setup里面写。首先使用ref创建一个响应式对象,vue3.0不是所有的属性都自动响应式,需要自动设置,然后包装成一个响应式对象暴露出来,使用.value的形式调用,主要是为了防止值类型不能被响应监听到。

<script lang="ts">
import { fetchTodoList } from "../api";
import { defineComponent, onMounted, Ref, ref } from "vue";

export default defineComponent({
  setup() {
    const respository: Ref<string[]> = ref([]);

    const getUserRespository = async () => {
      respository.value = await fetchTodoList();
    };

    onMounted(getUserRespository);

    return {
      respository,
      getUserRespository,
    };
  },
});
</script>

2.2 添加备忘录信息功能模块改造

同样的改造方法,定义所需要的data属性,复用上面的仓库属性

 //添加备忘录
    const currentValue: Ref<string> = ref("");
    const handleAdd = () => {
      respository.value.push(currentValue.value);
      currentValue.value = "";
    };
    
    return {
        currentValue,
        handleAdd,
    }

2.3删除备忘录功能模块改造

     //删除备忘录功能模块
    const handleDelete = (key: number) => {
      respository.value = respository.value.filter(
        (item, index) => key !== index
      );
    };

2.4 搜索功能模块改造

搜索所需要的find会有个undefined返回值类型,这时候我们直接将值断言成string即可。

    //搜索功能模块
    const handleSearch = () => {
      const target: string | undefined = respository.value.find(
        (item) => item === currentValue.value
      );

      respository.value = [target as string];
    };

2.4 setup模块

setup就被分成4个相对独立的功能模,在下一步我们将继续独立拆分模块

<script lang="ts">
import { fetchTodoList } from "../api";
import { defineComponent, onMounted, Ref, ref, toRefs, watch } from "vue";

export default defineComponent({
  props: {
    user: {
      type: Number,
      required: true,
    },
  },
  setup(props) {
  
    //请求模块
    const { user } = toRefs(props);
    const respository: Ref<string[]> = ref([]);
    const getUserRespository = async () => {
      respository.value = await fetchTodoList();
    };
    onMounted(getUserRespository);
    watch(user, getUserRespository);

    //添加备忘录
    const currentValue: Ref<string> = ref("");
    const handleAdd = () => {
      respository.value.push(currentValue.value);
      currentValue.value = "";
    };

    //删除备忘录功能模块
    const handleDelete = (key: number) => {
      respository.value = respository.value.filter(
        (item, index) => key !== index
      );
    };

    //搜索功能模块
    const handleSearch = () => {
      const target: string | undefined = respository.value.find(
        (item) => item === currentValue.value
      );
      respository.value = [target as string];
    };

    return {
      respository,
      getUserRespository,
      currentValue,
      handleAdd,
      handleDelete,
      handleSearch,
    };
  },
});
</script>

2.6 终极拆分方案

对于其他的逻辑关注点我们也可以这样做,但是你可能已经在问这个问题了——这不就是把代码移到 setup 选项并使它变得非常大吗?嗯,确实是这样的。这就是为什么我们要在继续其他任务之前,我们首先要将上述代码提取到一个独立的组合式函数中。 新建四个功能模块的ts文件方法

image.png

分别为

useGetRespository

import { toRefs, Ref, ref, onMounted, watch } from "vue";

import { fetchTodoList } from "../api";
export default function useGetRespository(props: any): any {
  //请求模块
  const { user } = toRefs(props);
  const respository: Ref<string[]> = ref([]);
  const getUserRespository = async () => {
    respository.value = await fetchTodoList();
  };
  onMounted(getUserRespository);

  watch(user, getUserRespository);

  return {
    respository,
    getUserRespository,
  };
}

useAddRespository

import { Ref, ref } from "vue";
export default function useAddRespository(respository: Ref<string[]>): any {
  //添加备忘录
  const currentValue: Ref<string> = ref("");
  const handleAdd = () => {
    respository.value.push(currentValue.value);
    currentValue.value = "";
  };

  return {
    currentValue,
    handleAdd,
  };
}

useDeleteRespository

import { Ref } from "vue";
export default function useDeleteRespository(respository: Ref<string[]>): any {
  //删除备忘录功能模块
  const handleDelete = (key: number) => {
    respository.value = respository.value.filter(
      (item, index) => key !== index
    );
  };
  return {
    handleDelete,
  };
}

useAddRespository

import { Ref } from "vue";
export default function useAddRespository(
  respository: Ref<string[]>,
  currentValue: Ref<string>
): any {
  //搜索功能模块
  const handleSearch = () => {
    const target: string | undefined = respository.value.find(
      (item) => item === currentValue.value
    );

    respository.value = [target as string];
  };

  return {
    handleSearch,
  };
}

现在我们有了四个个单独的功能模块,接下来就可以开始在组件中使用它们了

<script lang="ts">
import { defineComponent } from "vue";
import useAddRepository from "../hooks/useAddRepository";
import useDeleteRepository from "../hooks/useDeleteRespository";
import useGetRepository from "../hooks/useGetRespository";
import useSearchRepository from "../hooks/useSearchRespository";

export default defineComponent({
  props: {
    user: {
      type: Number,
      required: true,
    },
  },
  setup(props) {
    const { respository, getUserRespository } = useGetRepository(props);
    const { currentValue, handleAdd } = useAddRepository(respository);
    const { handleDelete } = useDeleteRepository(respository);
    const { handleSearch } = useSearchRepository(respository, currentValue);

    return {
      respository,
      getUserRespository,
      currentValue,
      handleAdd,
      handleDelete,
      handleSearch,
    };
  },
});
</script>