Vue组合式API

news2025/2/26 14:53:32

目录

一. 为什么要使用Composition API

1.1.一个Options API实例

1.2.Options API存在的问题

1.3.Composition API简介

二.Composition API

2.1.setup()入口

2.2.ref 响应式监听

2.3.reactive与toRefs

2.4.computed的用法

2.5.watch的用法

2.6.setup()参数

2.6.1.props参数

2.6.2.context参数

三.Composition API的使用

3.1.provide与inject的使用

3.2.vue生命周期的用法

3.3.编程式路由的使用

3.4.Vuex的使用

3.5.获取DOM的使用

四.使用Composition API重写todoList

五.setup语法糖

5.1.setup语法糖的基本结构

5.2.响应数据的使用

5.3.其它语法的使用

5.4.引入组件的使用

5.5.父子组件传值的使用

5.5.1.defineProps的使用

5.5.2.defineEmits的使用

六.使用setup语法糖重写todoList

七. 前端学习专栏


一. 为什么要使用Composition API

1.1.一个Options API实例

        在前面的课程中,我们都是采用 Options API(基于选项的 API ) 来写一个组件的。下面是一个实例:

<template>
  Count is: {{ count }}, doubleCount is: {{ doubleCount }} 
  <button @click="add">加</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    },
  },
  methods: {
    add() {
      this.count++;
    }
  }
}
</script>

        当要去理解一个组件时,我们更加关心的是:“这个组件是要干什么(即代码背后的意图)”,而不是:“这个组件用到了什么选项”。

Options API 撰写出来的代码自然采用了后者的表述方式,然而对前者的表述并不好。

1.2.Options API存在的问题

        在 Options API 中实际上形成了一种强制的约定:

  • props 里面设置接收参数
  • data 里面设置变量
  • computed 里面设置计算属性
  • watch 里面设置监听属性
  • methods 里面设置事件方法

        我们会发现: Options API 都约定了我们该在哪个位置做什么事,这在一定程度上也强制我们进行了代码分割。这就为展示背后的逻辑关注点设置了障碍。我们必须不断地在选项代码块之间“跳转”,以找到与该关注点相关的部分。

        尤其是在大型组件中,数据与方法会很多,而数据与其相关联的方法就会被其他数据和方法分隔的很远,往往很难被看出它们之间的关联。

        这是一个大型组件的示例,其中逻辑关注点是按颜色分组。

        这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。

        如果我们能够将与同一个逻辑关注点相关的代码配置在一起,这样会更好。而这正是组合式 API 使我们能够做到的。

1.3.Composition API简介

        Composition API:组合式 API;一组低侵入式的、函数式的 API,使得我们能够更灵活地【组合】组件的逻辑。

        这是有别于 Options API 的一种函数式 API。无需通过很多选项来完成业务逻辑,Composition API提供了一个setup函数,我们可以将data数据、计算属性、方法等等,都放在setup函数中,这样就可以对业务进行集中处理了。

采用Composition API来重写上面的组件:

<template>
  Count is: {{ state.count }}, doubleCount is: {{ state.doubleCount }}
  <button @click="add">加</button>
</template>

<script>
import { reactive, computed } from "vue";

export default {
  setup() {
    const state = reactive({
      count: 0,
      doubleCount: computed(() => state.count * 2),
    });

    function add() {
      state.count++;
    }

    return {
      state,
      add
    }
  }
}
</script>

        还有一个 setup 函数,setup 函数是一个新的组件选项。作为在组件内使用 Composition API 的入口点,如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文,我们就可以在模板里使用对应的属性和方法。


二.Composition API

2.1.setup()入口

        setup 函数是一个新的组件选项,它是在组件内使用 Composition API 的入口点。它会在Vue实例创建完成前被调用。所以,setup函数中没有this指针

<template>
  <div></div>
</template>

<script>
export default {
  setup() {
	//这里书写本地状态(data数据)、计算属性或方法等
    console.log('setup函数');
  }
}
</script>

        如果 setup 返回一个对象,则对象的属性将会被合并到组件模板的渲染上下文,我们就可以在模板里使用对应的属性和方法。所以,我们可以将本地状态(data数据)、方法、计算属性等写在 setup 函数中。

<template>
  Count is: {{ count }}
</template>

<script>
export default {
  setup() {
    let count = 10;
    
    return {
      count
    }
  }
}
</script>

        上面代码中,在 setup 函数中声明了一个 count 数据。然后使用 return 返回需要暴露的内容。运行之后可以看到:视图能够正确显示count数据。

        setup函数总结:

  • setup函数是Composition API 的入口点,是它的核心。
  • 由于执行 setup 时,组件实例尚未被创建,因此在 setup 中不能使用 this。
  • setup中定义的东西必须要return出去,才能被使用或绑定视图。

2.2.ref 响应式监听

上面实例中,虽然视图能够显示数据。但当改变数据时,视图却不会得到响应。

<template>
  Count is: {{ count }}
  <button @click="add">加</button>
</template>

<script>
export default {
  setup() {
    let count = 10;

    function add(){
      count++;       //setup函数中没有this指针
    }
    
    return {
      count,
      add
    }
  }
}
</script>

        原因很简单,count只是声明的一个普通数据,不具备响应功能。

        在 Vue 3.0 中,我们可以通过一个 ref 函数来实现响应式数据监听。ref 接受一个参数,并将其包裹在一个带有 value 属性的对象中返回,然后可以使用该 value 属性访问或更改响应式变量的值:

<template>
  Count is: {{ count }}
  <button @click="add">加</button>
</template>

<script>
//注意:要导入ref
import {ref} from 'vue';

export default {
  setup() {
    let count = ref(10);  //count成为响应式数据。

    function add(){
      count.value++;       //使用value属性获取响应数据
    }
    
    return {
      count,
      add
    }
  }
}
</script>

        为什么要将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。因为在 JavaScript 中,Number 或 String 等基本类型是通过值传递的,而不是通过引用传递的:

2.3.reactive与toRefs

        上面实例中,操作数据时需要使用 value 属性,比较麻烦。

        可以使用 reactive 与 toRefs 解决这个问题。首先使用 reactive 创建响应式对象,封装数据。

<template>
  <p>Count is: {{ state.count }}</p>
  <button @click="add">加</button>
  <p>{{ state.user.username }}</p>
</template>

<script>
//注意:要导入ref
import {reactive} from 'vue';

export default {
  setup() {
    //所有响应数据都声明在这里,包括对象、数组
    const state = reactive({
      count: 10,
      user:{
        userId: 1,
        username: '张三'
      }
    })

    function add(){
      state.count++;       //这里可以不使用value了
    }
    
    return {
      state,
      add
    }
  }
}
</script>

        此时不用使用 value 属性了。

        但是因为只有state是响应式数据,而state中的那些数据还不是响应式的。所以在视图访问数据时都需要使用 state 作为前缀才可以。这就比较麻烦了。

        此时我们可以使用 toRefs 进行优化。toRefs可以将state中的每一个数据进行展开,且都包装成响应数据。这样视图层就可以直接使用了。

<template>
  <p>Count is: {{ count }}</p>
  <button @click="add">加</button>
  <p>{{ user.username }}</p>
</template>

<script>
//注意:要导入ref
import {reactive, toRefs} from 'vue';

export default {
  setup() {
    //所有响应数据都声明在这里,包括对象、数组
    const state = reactive({
      count: 10,
      user:{
        userId: 1,
        username: '张三'
      }
    })

    function add(){
      state.count++;       //这里可以不使用value了
    }
    
    return {
      ...toRefs(state),    //这里使用使用 ...toRefs(state)
      add
    }
  }
}
</script>
  • 在返回时使用 ...toRefs(state) ,这样视图层就可以不使用 state 前缀了。
  • 为什么要使用 ... 参数扩展运输符呢?因为toRefs(state) 将state对象展开,并包装成多个响应数据。

2.4.computed的用法

<template>
  <p>Count is: {{ count }}</p>
  <p>doubleCount is: {{ doubleCount }}</p>
  <button @click="add">加</button>
  <p>{{ user.username }}</p>
</template>

<script>
//注意:要导入ref
import { reactive, toRefs, computed } from "vue";

export default {
  setup() {
    //所有响应数据都声明在这里,包括对象、数组
    const state = reactive({
      count: 10,
      user: {
        userId: 1,
        username: "张三",
      },
      doubleCount: computed(() => {    //使用computed函数
        return state.count * 2;
      }),
    });

    function add() {
      state.count++; //这里可以不使用value了
    }

    return {
      ...toRefs(state),
      add,
    };
  },
};
</script>
  • 首先要import导入computed。
  • 在reactive({})中声明computed即可。

到现在为止,响应式数据就都可以处理了。

2.5.watch的用法

<template>
  <div>
    {{ num }}
    <button @click="add">加</button>
  </div>
</template>

<script>
import { reactive, toRefs, watch } from "vue";

export default {
  setup() {
    const state = reactive({
      num: 0,
    });

    watch(state,(newValue, oldValue) => {
        console.log(newValue, oldValue);
      }
    );

    function add() {
      state.num++;
    }

    return {
      ...toRefs(state),
      add,
    };
  },
};
</script>
  • 使用watch函数来进行数据监听。watch函数有两个参数。
  • 第一个参数:要监听的数据。
  • 第二个参数:触发监听时的处理函数(包括newValue, oldValue)

        上面实例中直接监听state响应对象。但我们知道,在state中会有很多数据,如果只想监听其中的某个数据,就需要换一种写法:

watch(() => state.num,(newValue, oldValue) => {
    console.log(newValue, oldValue);
});

        第一个参数要写成函数返回值的形式,这样就能监听state响应对象中的某个数据了。

2.6.setup()参数

        setup() 函数有两个参数:props 和 context。

        为什么要有这两个参数呢?我们知道父子组件之间是可以传值。但是现在我们的业务逻辑都写在setup函数中,而setpu中没有this指针,那么就只能靠这两个参数来进行传递了。

  • props:父组件向子组件传值的参数。
  • context:子组件向父组件传值的参数。

2.6.1.props参数

        setup() 函数的 props 是父组件向子组件传值的参数。

在components文件夹中创建子组件(Hello.vue):

<template>
  <div>我是子组件</div>
</template>

<script>
export default {
  setup(props, context) {
    console.log(props.msg)
  },
  props: {
    msg: String,
  },
};
</script>

<style>
</style>

父组件(HomeView.vue):

<template>
  <div>
    <Hello msg="hello"></Hello>
  </div>
</template>

<script>
import Hello from '../components/Hello.vue'
import { reactive, toRefs, computed } from "vue";

export default {
  setup() {
    const state = reactive({

    });
    return {
      ...toRefs(state),
    };
  },
  components:{
    Hello
  }
};
</script>

        注意,要先import导入子组件,然后使用components挂载子组件。

2.6.2.context参数

        setup() 函数的 context 是子组件向父组件传值的参数。

子组件(Hello.vue):

<template>
  <div>
    <div>我是子组件</div>
    <button @click="send">给父组件发数据</button>
  </div>
</template>

<script>
export default {
  setup(props, context) {
    function send() {
      context.emit("childmsg", "hello world!");
    }

    return {
      send,
    };
  },
  props: {
    msg: String,
  },
};
</script>

<style>
</style>

父组件(HomeView.vue):

<template>
  <div>
    <Hello msg="hello" @childmsg="get"></Hello>
    <p>我是父组件,接受子组件传的值:{{welcome}}</p>
  </div>
</template>

<script>
import Hello from '../components/Hello.vue'
import { reactive, toRefs, computed } from "vue";

export default {
  setup() {
    //所有响应数据都声明在这里,包括对象、数组
    const state = reactive({
      welcome: ''
    });

    function get(param) {
      state.welcome = param;
    }

    return {
      ...toRefs(state),
      get
    };
  },
  components:{
    Hello
  }
};
</script>

三.Composition API的使用

        下面我们会将前面学过的知识点都改写为Composition API的形式。而且,Vue3兼容Options API和Composition API两种写法。所以这两种写法都要会。

3.1.provide与inject的使用

        我们学过provide与inject可用于多级组件直接传递数据,下面学习provide与inject在Composition API中的使用。

创建孙子组件(SubHello.vue)

<template>
  <div>
    <div>我是孙组件</div>
  </div>
</template>

<script>
import { inject } from "vue";

export default {
  setup(props, context) {

    console.log(inject('msg'))
      
    return {};
  }
};
</script>

在孙子组件中import导入inject,并使用inject接收上级组件的传值。

  • 在子组件(Hello.vue)中使用孙子组件
<template>
  <div>
    <div>我是子组件</div>
    <SubHello></SubHello>
  </div>
</template>

<script>
import SubHello from './SubHello.vue'

export default {
  setup(props, context) {

    return {};
  },
  components:{
      SubHello
  }
};
</script>

<style>
</style>
  • 在父组件中使用provide给多级组件传值
<template>
  <div>
    <Hello></Hello>
  </div>
</template>

<script>
import Hello from "../components/Hello.vue";
import { provide } from "vue";

export default {
  setup() {
    provide('msg','hello');
  },
  components:{
    Hello
  }
};
</script>

        注意,由于父组件向孙子组件传递数据是单向的,所以孙子组件不能修改传递的值。如果子组件

3.2.vue生命周期的用法

在 setup () 内部调用生命周期钩子:

选项式API

setup () 内部调用生命周期钩子

beforeCreate()

setup()

created()

setup()

beforeMount()

onBeforeMount()

mounted()

onMounted()

beforeUpdate()

onBeforeUpdate()

updated()

onUpdated()

beforeUnmount()

onBeforeUnmount()

unmounted()

onUnmounted()

  • 注意:在Composition API中没有beforeCreate()和created()这里两个声明周期函数了,统一使用setup()。

实例:

<template>
  <div>
    {{ num }}
    <button @click="add">加</button>
  </div>
</template>

<script>
import { reactive,toRefs,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from "vue";

export default {
  setup() {
    const state = reactive({
      num: 1,
    });

    function add() {
      state.num++;
    }

    onBeforeMount(() => {
      console.log("DOM挂载前!");
    });

    onMounted(() => {
      console.log("DOM挂载后!");
    });

    onBeforeUpdate(() => {
      console.log("数据更新前!");
    })

    onUpdated(() => {
      console.log("数据更新后!");
    })

    onBeforeUnmount(() => {
      console.log("实例卸载前!");
    })

    onUnmounted(() => {
      console.log("实例卸载后!");
    })
 
    return {
      ...toRefs(state),
      add,
    };
  },
};
</script>

3.3.编程式路由的使用

        下面学习如何在Composition API中使用路由。

        打开App.vue组件,这里已经有了路由。当然,是使用router-link标签来实现的。现在我们将它改成编程式路由。

<template>
  <nav>
    <!--
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
    -->
    <button @click="toHome">Home</button>
    <button @click="toAbout">About</button>
  </nav>
  <router-view />
</template>

<script>
import { useRouter } from "vue-router";

export default{
  setup() {
    const router = useRouter();

    function toHome(){
      router.push('/');
    }
  
    function toAbout(){
      router.push({path:'/about',query:{name:'zhangsan'}});
    }

    return {
      toHome,
      toAbout
    }
  },
}
</script>
  • 先import导入useRouter模块。
  • 通过useRouter模块获取router对象。以后的路由写法就与前面所学一样了。

下面是获取路由参数,打开AboutView.vue文件

<template>
  <div>   
  </div>
</template>

<script>
import { useRoute } from "vue-router";

export default {
  setup(){
    const route = useRoute();

    console.log(route.query.name);
  }
}
</script>

通过同样的方式获取route对象后就可以获取路由参数了。

3.4.Vuex的使用

下面学习如何在Composition API中使用Vuex。

<template>
  <div>
  </div>
</template>

<script>
import { useStore } from "vuex";

export default {
  setup() {
    const store = useStore();
    console.log(store.state.num);
    console.log(store.getters.newnum);
  }
};
</script>
  • 先import导入useStore模块。
  • 通过useStore模块获取store对象。就可以通过store对象获取Vuex中的所有数据了。

3.5.获取DOM的使用

        前面我们知道在Vue中,可以使用ref来获取DOM对象。下面学习如何在Composition API中使用ref。

<template>
  <div ref="myRef">获取单个DOM元素</div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const myRef = ref(null);   //ref(null)是一个固定的写法

    onMounted(() => {
      console.dir(myRef.value);
    });
    return {
      myRef
    };
  }
};
</script>
  • 在HTML标签中使用 ref 属性标识一个DOM对象。
  • 需要 import 导入 ref 对象。
  • 使用 const myRef = ref(null); 的方式获取包装好的DOM对象,命名为HTML中的 ref 属性名。并且此数据需要暴露出去。
  • 使用 value 属性即可获取 DOM对象。

四.使用Composition API重写todoList

  • AddNew组件
<template>
  <div>
    <input type="text" v-model="newItem" />
    <button @click="handleAdd">添加</button>
  </div>
</template>

<script>
import {reactive, toRefs} from 'vue';

export default {
  setup(props, context){
    const state = reactive({
      newItem: ""
    })

    function handleAdd() {
      if (state.newItem == "") {
        alert("不能为空");
        return;
      }
      //注意:这里使用setup参数context来出发父组件事件
      context.emit("submitNewItem", state.newItem);
      state.newItem = "";
    }

    return {
      ...toRefs(state),
      handleAdd
    }
  }
}
</script>
  • TheList组件
<template>
  <ol>
    <li v-for="(item, index) in list" :key="index" @click="judgeItem(index)">
      {{ item }}
    </li>
  </ol>
</template>

<script>
export default {
  setup(props, context) {
    //这里分别使用了setup的两个参数  
    function judgeItem(index) {
      if (props.listType) {
        context.emit("handleDelete", index);
      } else {
        context.emit("handleJudge", index);
      }
    }

    return {
      judgeItem
    };
  },
  props: {
    list: {
      type: Array,
      required: true,
    },
    listType: {
      type: Boolean,
      default: false,
    },
  },
};
</script>
  • TodoList组件
<template>
  <div>
    <h1>todoList</h1>
    <AddNew @submitNewItem="addNewItem"></AddNew>
    <TheList :list="todoList" @handleJudge="toDone"></TheList>
    <hr />
    <TheList :list="doneList" :listType="true" @handleDelete="toDelete"></TheList>
  </div>
</template>

<script>
import AddNew from "../components/AddNew.vue";
import TheList from "../components/TheList.vue";
import {reactive, toRefs} from 'vue';

export default {
  setup(){
    const state = reactive({
      todoList: [],  //待办事项
      doneList: []   //完成事项
    })

    function addNewItem(newItem){
      state.todoList.push(newItem);
    }
    function toDone(index){
      state.doneList.push(state.todoList.splice(index,1)[0]);
    }
    function toDelete(index){
      state.doneList.splice(index,1);
    }

    return {
      ...toRefs(state),
      addNewItem,
      toDone,
      toDelete
    }
  },
  components: {
    AddNew,
    TheList,
  },
};
</script>

五.setup语法糖

        在Composition API中,在setup函数中声明的数据、函数等内容,都需要通过 return 对外暴露,才能被组件的视图模板(template)使用,这就造成了书写上的不方便。于是,Vue官方又给我们推出了一个新的setup语法糖。

        使用setup语法糖,就可以不用写setup函数了。并且,数据和函数也不用返回,组件也不需要注册了。

5.1.setup语法糖的基本结构

<template>
</template>

<script setup>
//此处直接写setup函数中的内容
</script>

<style>
</style>
  • 在script标签中使用setup属性即可。
  • 运行时,script标签中的内容会被重新编译成 setup() 函数的形式。
  • 而且,声明的数据、函数不需要通过 return 暴露,即可被 template所使用

5.2.响应数据的使用

<template>
  <div>
    <p>{{ num }}</p>
    <button @click="add">加</button>
  </div>
</template>

<script setup>
let num = 10;
//const num = 10;

//由于num不是响应数据,所以改变num是无效的。
const add = ()=>{
  alert("触发了此方法");
  num++;
}
</script>

        直接声明的数据不是响应式的,数据改变时不会响应到视图模板中。

<template>
  <div>
    <p>{{ num }}</p>
    <p>{{ dept.deptno }},{{ dept.dname }},{{ dept.loc }}</p>
    <ul>
      <li v-for="user in userArr" :key="user.userId">
        {{user.userId}},{{user.userName}},{{user.userAge}}
      </li>
    </ul>
    <button @click="add">加</button>
  </div>
</template>

<script setup>
import { reactive, ref } from "vue";

const num = ref(10);

const dept = reactive({
  deptno: 20,
  dname: "技术部",
  loc: '沈阳市',
});

const userArr = ref([
  {
    userId: 100,
    userName: "张三",
    userAge: 25,
  },
  {
    userId: 101,
    userName: "李四",
    userAge: 26,
  },
  {
    userId: 102,
    userName: "王五",
    userAge: 27,
  },
]);

const add = () => {
  num.value++;      //注意:要使用value属性获取
  dept.deptno++;
  //userArr.value[0].userAge++;
  userArr.value = [];
}
</script>

ref 和 reactive 都可以做响应式数据,它们的区别如下:

  • reactive:用于定义引用类型。只能修改数据,不能改变其引用。
  • ref:用于定义基本类型和引用类型。可以修改数据,也可以改变其引用。
  1. 在方法中修改数据时需要使用 value属性。因为,Ref的本质是通过Reactive创建的,Ref(10) 就相当于:Reactive({value:10});
  2. 在视图模板调用可以省略value属性的书写。

5.3.其它语法的使用

下面例子演示了computed计算属性、watch监听、生命周期函数的使用。

<template>
  <div>
    {{ num }}
    {{ newNum }}
    <button @click="add">add</button>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted } from 'vue';

const num = ref(10);

const newNum = computed(() => {
  return num.value*2;
})

const add = ()=>{
  num.value++;
}

watch(num,(newValue,oldValue)=>{
  console.log(newValue,oldValue);
})

//生命周期函数
onMounted(() => {
  console.log("DOM挂载后!");
});
</script>

5.4.引入组件的使用

引入的组件不必注册,可以直接使用。

<template>
  <div class="home">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script setup>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
</script>

5.5.父子组件传值的使用

5.5.1.defineProps的使用

defineProps用于父组件向子组件传值。

  • 父组件
<template>
  <div class="home">
    <HelloWorld msg="Welcome to Your Vue.js App" :num="num"/>
  </div>
</template>

<script setup>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

const num = 20
</script>
  • 子组件
<template>
  <div class="hello">
    <h1>{{ msg }},{{ num }}</h1>
  </div>  
</template>

<script setup>
//const myProps = defineProps(['msg','num']);

const myProps = defineProps({
  msg:{
    type: String
  },
  num:{
    type: Number,
    required: true
  }
});
</script>

<style scoped>
</style>

defineProps也可以有数组形式和对象形式两种写法。

5.5.2.defineEmits的使用

defineEmits用于子组件向父组件传值。

  • 父组件
<template>
  <div class="home">
    <HelloWorld 
           msg="Welcome to Your Vue.js App" 
           :num="num"
           @childmsg="get"/>
  </div>
</template>

<script setup>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

const num = 20;

const get = (value) => {
  console.log(value)
}
</script>
  • 子组件
<template>
  <div class="hello">
    <h1>{{ msg }},{{ num }}</h1>
    <button @click="send">给父组件传值</button>
  </div>  
</template>

<script setup>
const myProps = defineProps(['msg','num']);

const emit = defineEmits(['childmsg']);

const send = () => {
  emit('childmsg','子组件向父组件传的值');
}
</script>

<style scoped>
</style>

六.使用setup语法糖重写todoList

  • AddNew组件
<template>
  <div>
    <input type="text" v-model="newItem" />
    <button @click="handleAdd">添加</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

const newItem = ref("");

const emit = defineEmits(["submitNewItem"]);

const handleAdd = () => {
  if (newItem.value == "") {
    alert("不能为空");
    return;
  }
  emit("submitNewItem", newItem.value);
  newItem.value = "";
};
</script>
  • TheList组件
<template>
  <ol>
    <li v-for="(item, index) in list" :key="index" @click="judgeItem(index)">
      {{ item }}
    </li>
  </ol>
</template>

<script setup>
const emit = defineEmits(['handleDelete','handleJudge']);

const judgeItem = (index) => {
  if (myProps.listType) {
    emit("handleDelete", index);
  } else {
    emit("handleJudge", index);
  }
};

const myProps = defineProps({
  list: {
    type: Array,
    required: true,
  },
  listType: {
    type: Boolean,
    default: false,
  },
});
</script>
  • TodoList组件
<template>
  <div>
    <h1>todoList</h1>
    <AddNew @submitNewItem="addNewItem"></AddNew>
    <TheList :list="todoList" @handleJudge="toDone"></TheList>
    <hr />
    <TheList
      :list="doneList"
      :listType="true"
      @handleDelete="toDelete"
    ></TheList>
  </div>
</template>

<script setup>
import AddNew from "../components/AddNew.vue";
import TheList from "../components/TheList.vue";
import { reactive } from "vue";

const todoList = reactive([]); //待办事项
const doneList = reactive([]); //完成事项

const addNewItem = (newItem) => {
  todoList.push(newItem);
}
const toDone = (index) => {
  doneList.push(todoList.splice(index, 1)[0]);
}
const toDelete = (index) => {
  doneList.splice(index, 1);
}
</script>

七. 前端学习专栏

https://blog.csdn.net/weixin_53919192/category_11897910.html?spm=1001.2014.3001.5482https://blog.csdn.net/weixin_53919192/category_11897910.html?spm=1001.2014.3001.5482

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/405360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端如何将项目部署到服务器(Nginx)

文章目录一、准备环境二、安装Nginx1、 安装Nginx依赖2、下载Nginx3、解压下载好的Nginx 压缩包4、编译安装Nginx5、启动Nginx服务三、操作步骤1、使用Xshell连接服务器2、上传静态资源文件3、 配置Nginx4、 重启Nginx服务我们在会开发项目的同时&#xff0c;也应该了解一下前端…

基于Java Web的随意购商城系统(开源项目)

提示&#xff1a;此项目仅作为本博主的学习笔记记录&#xff0c;不作为商品售卖&#xff0c;资源往下翻看源码获取 文章目录前言Web端功能设计首页热销商品新到商品商品分类商品详情购物车添加地址提交订单部分代码展示可能会出现的错误如果拿到项目后发现图片不显示源码获取前…

在Vue中使用高德地图

在Vue中使用高德地图一、如何在Vue中引入基础高德地图1、步骤一&#xff1a;注册并登录高德地图开放平台&#xff0c;申请密钥2、步骤二&#xff1a;安装高德地图加载器3、封装一个自定义地图组件&#xff0c;并初始化地图二、根据关键词搜索&#xff0c;并定位到搜索的位置三、…

谷歌浏览器自带翻译网页插件没用了怎么办?这里有解决办法。

前言 正当我打算来一波科学上网的时候&#xff0c;当我用谷歌浏览器打开文档网站时候&#xff0c;发现发现google浏览器网页翻译插件没用了。经过了我一段时间的搜寻&#xff0c;终于有了解决方案。 原因 从 10 月 20 日起&#xff0c;谷歌在陆续移除国内服务器上的谷歌翻译…

Vue学习之从入门到神经(两万字收藏篇)

写在前面 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家:人工智能学习网站 Vue写在前面前言Vue.js三种安装方式一、 Vue导入二、Vue基本语法1.钩子函数2. 插值表达式3.显示数据(v-text和v-html)4.数据双向…

深入理解Vue响应式原理

前言 Vue响应式原理是Vue最独特的特性之一&#xff0c;当数据模型进行修改时&#xff0c;视图就会进行更新&#xff0c;这使得状态管理简单直接&#xff0c;但是其底层的细节还是需要我们深入学习理解&#xff0c;这样遇到一些问题我们才能快速进行定位&#xff0c;并解决&…

【UML】-- 顺序图练习题含答案(自动售货机、学生选课、提款机、购买地铁票、洗衣机工作)

注意&#xff1a;对象表示法对象名需要下划线&#xff0c;此文章没有标注 一、练习一 根据下面的叙述&#xff0c;绘制一幅关于顾客从自动售货机中购买物品的顺序图。顾客&#xff08;User&#xff09;先向自动售货机的前端&#xff08;Front&#xff09;投币&#xff1b;售货…

CSDN文章点赞、收藏、评论后到底发生了什么?简要分析HTTP交互机制

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他拥有的&#xff0c;而不是他会的。所以可以不学无数&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关…

node.js是干什么的

一、Node.js简介 Node.js是一个开源和跨平台的JavaScript运行时环境。它几乎是任何类型项目的流行工具&#xff01; Node.js在浏览器之外运行V8 JavaScript引擎&#xff08;Google Chrome的内核&#xff09;。这使得Node.js的性能非常好。 Node.js应用程序在单个程序中运行&…

使用SpringBoot一小时快速搭建一个简单后台管理(增删改查)(超详细教程)

最近也是临近期末了&#xff0c;各种的期末大作业&#xff0c;后台管理也是很多地方需要用到的&#xff0c;为了方便大家能快速上手&#xff0c;快速搭建一个简单的后台管理&#xff0c;我花了两天时间整理了一下 我会从0开始介绍&#xff0c;从数据库的设计到前端页面的引入最…

使用uniapp开发APP时的调试/安卓打包等

一、调试 1.先用数据线连接电脑和手机&#xff0c;选择“文件传输”&#xff0c; 2.打开开发者模式&#xff0c;华为手机举列-->设置-->关于手机-->版本号&#xff0c;多次连续点击“版本号”&#xff0c;就会提示已打开 开发者模式 3.华为手机举列-->设置-->…

Vue:element-ui中表格过长内容隐藏显示

一、el-table表格 在使用VUE显示后台数据时&#xff0c;经常会遇到数据过长&#xff0c;显示出来的效果很难看&#xff0c;如下图所示&#xff1a; 上图中&#xff0c;红框框出的内容由于长度过长&#xff0c;占据了三行空间&#xff0c;如果内容更多的话&#xff0c;占据行数就…

基于WEB的网上购物系统的设计与实现(附:源码 论文 sql文件)

摘 要 随着计算机网络技术的飞速发展和人们生活节奏的不断加快&#xff0c;电子商务技术已经逐渐融入了人们的日常生活当中&#xff0c;网上商城作为电子商务最普遍的一种形式&#xff0c;已被大众逐渐接受。因此开发一个网上商城系统&#xff0c;适合当今形势&#xff0c;更加…

【Windows Server 2019】Web服务 IIS 配置与管理—— IIS 的安装与基本配置 Ⅲ

目录4. 安装 IIS 服务器5. IIS 的基本配置5.1 绑定 IP参考资料关联博文4. 安装 IIS 服务器 准备工作&#xff1a;选择一台服务器作为WEB-IIS服务器&#xff0c;IP地址为192.168.82.208。 在Windows Server 2019系统中&#xff0c;IIS角色是可选组件&#xff0c;默认情况下是没…

Nginx之负载均衡upstream模块简介和使用

一、upstream模块简介 Nginx的负载均衡功能依赖于ngx_http_upsteam_module模块&#xff0c;所支持的代理方式包括proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass和grpc_pass。ngx_http_upstream_module模块有允许Nginx定义一组或多组服务组&#xff0c;使用…

在 vue eslint 报错 error “Component name “*****“ should always be multi-word”,该怎么办?

目录 出现的问题&#xff1a; 报错的原因&#xff1a; 解决方案&#xff1a; 方案一 &#xff1a;重命名(亲测有效) 方案二 &#xff1a;配置 vue.config.js 文件&#xff08;网上方法&#xff0c;本人使用无效&#xff09; 方案三 &#xff1a;配置 .eslintrc.js文件&a…

css设置渐变色

css如何设置自定义渐变色&#xff1f;线性渐变篇 CSS渐变可以让你在两个或多个指定颜色之间显示平滑的过渡 CSS定义了三种渐变类型: Linear Gradients (goes down/up/left/right/diagonally) 下降/ 上升/左/右/对角线Radial Gradients (defined by their center) 由中心定义…

SpringBoot整合WebSocket实现后端向前端发送消息

目录 一、什么是 websocket 接口 二、适用场景 三、示例代码 3.1、添加pom.xml依赖 3.2、创建WebSokcet配置类 3.3、创建测试发送消息接口 3.4、测试webSocket&#xff08;http://www.jsons.cn/websocket/&#xff09; 一、什么是 websocket 接口 使用 websocket 建立长连…

vue中的render函数(通俗、易懂)

文章目录一、初步认识render函数二、为什么使用render函数三、render函数的解析一、初步认识render函数 import Vue from vue import App from ./AppVue.config.productionTip falsenew Vue({el: #app,render: h > h(App) })在使用脚手架创建vue项目的过程&#xff0c;我们…

纯前端文档预览,还要支持所有主流格式,有这一篇就足够了

写在前面 纯前端的文档预览功能&#xff0c;是非常常见的需求&#xff0c;但就是这么简单的需求&#xff0c;难住了许多可爱的小伙伴们。别急&#xff0c;先访问一下解决方案&#xff0c;给你一个惊喜&#xff0c;再往下看&#xff1a; 文件在线预览DEMO 服务器文件预览DEMO …