玩转Vue3全家桶06 | 新的代码组织方式:Composition API + <script setup> 到底好在哪里?

发布于:2022-12-03 ⋅ 阅读:(159) ⋅ 点赞:(0)

你好,我是大圣,欢迎进入课程的第六讲。

在上一讲中,我带你搭建了项目的雏形,这是后面项目开发的起点。从今天开始,我就带你在这个骨架结构的基础之上,开始项目的实战开发。首先我们要掌握的,就是 Vue 3 的 Composition API +

我们在前面的第三讲中,有详细地讲到过 Composition API ,相信你对这个 API 的语法细节已经有所掌握了。那你肯定会很好奇,这个

别急,今天我就带你使用 Composition API 和

Composition API 和

首先我想提醒你,我们在这一讲中写代码的方式,就和前面的第二讲有很大的区别。

在第二讲中,我们开发清单应用时,是直接在浏览器里使用 Options API 的方式写代码;但在接下来的开发中,我们会直接用单文件组件——也就是 .vue 文件,的开发方式。这种文件格式允许我们把 Vue 组件的 HTML、CSS 和 JavaScript 写在单个文件内容中。下面我带你用单文件组件的方式,去重构第二讲做的清单应用。

我们现在已经搭建好了项目的骨架,以后在这个骨架之内会有很多页面和组件。从这里开始,我们就要逐步适应组件化的开发思路,新的功能会以组件的方式来组织。

按照上一讲制定的规范,首先,我们打开项目文件夹下面的 src 下的 components 目录,新建一个 Todolist.vue ,并在这个文件里写出下面的代码:

<template>

  <div><h1 @click="add">{{count}}</h1>

  </div>

</template>

<script setup>

import { ref } from "vue";

let count = ref(1)

function add(){

​    count.value++

}

</script>

<style>

h1 {

  color: red;

}

</style>

在上述代码中,我们使用 template 标签放置模板、script 标签放置逻辑代码,并且用 setup 标记我们使用

从具体效果上看,这段代码实现了一个累加器。在

实现累加器以后,我们再回到 src/pages/Home.vue 组件中,使用如下代码显示清单应用。在这段代码里,我们直接 import TodoList.vue 组件,然后

<template><h1>这是首页</h1><TodoList />    

</template>

<script setup>

import TodoList from '../components/TodoList.vue'

</script>

这个时候我们就把清单功能独立出来了,可以在任意你需要的地方复用。在课程的后续内容中,我会详细给你介绍基于组件去搭建应用的方式。通过这种方式,你可以实现对业务逻辑的复用。这样做的好处就是,如果有其他页面也需要用到这个功能,可以直接复用过去。

然后,我们就可以基于新的语法实现之前的清单应用。下面的代码就是把之前的代码移植过来后,使用 ref 包裹的响应式数据。在你修改 title 和 todos 的时候,注意要修改响应式数据的 value 属性。

<template>

  <div><input type="text" v-model="title" @keydown.enter="addTodo" /><ul v-if="todos.length"><li v-for="todo in todos"><input type="checkbox" v-model="todo.done" /><span :class="{ done: todo.done }"> {{ todo.title }}</span></li></ul>

  </div>

</template>

<script setup>

import { ref } from "vue";

let title = ref("");

let todos = ref([{title:'学习Vue',done:false}])

function addTodo() {

  todos.value.push({

​    title: title.value,

​    done: false,

  });

  title.value = "";

}

</script>

计算属性

在第二讲开发的清单应用中,我们也用到了计算属性,在 Composition API 的语法中,计算属性和生命周期等功能,都可以脱离 Vue 的组件机制单独使用 。我们向 TodoList.vue 代码块中加入下面的代码:

<template>

  <div><input type="text" v-model="title" @keydown.enter="addTodo" /><button v-if="active < all" @click="clear">清理</button><ul v-if="todos.length"><li v-for="todo in todos"><input type="checkbox" v-model="todo.done" /><span :class="{ done: todo.done }"> {{ todo.title }}</span></li></ul>

    <div v-else>暂无数据</div>

    <div>

​      全选<input type="checkbox" v-model="allDone" /><span> {{ active }} / {{ all }} </span></div>

  </div>

</template>

<script setup>

import { ref,computed } from "vue";

let title = ref("");

let todos = ref([{title:'学习Vue',done:false}])

function addTodo() {

...

}

function clear() {

  todos.value = todos.value.filter((v) => !v.done);

}

let active = computed(() => {

  return todos.value.filter((v) => !v.done).length;

});

let all = computed(() => todos.value.length);

let allDone = computed({

  get: function () {return active.value === 0;

  },

  set: function (value) {

​    todos.value.forEach((todo) => {

​      todo.done = value;});

  },

});

</script>

在这这段代码中,具体的计算属性的逻辑和第二讲一样,区别仅在于 computed 的用法上。你能看到,第二讲的 computed 是组件的一个配置项,而这里的 computed 的用法是单独引入使用。

Composition API 拆分代码

讲到这里,可能你就会意识到,之前的累加器和清单,虽然功能都很简单,但也属于两个功能模块。如果在一个页面里有这两个功能,那就需要在 data 和 methods 里分别进行配置。但这样的话,数据和方法相关的代码会写在一起,在组件代码行数多了以后就不好维护。所以,我们需要使用 Composition API 的逻辑来拆分代码,把一个功能相关的数据和方法都维护在一起。

但是,所有功能代码都写在一起的话,也会带来一些问题:随着功能越来越复杂,script 内部的代码也会越来越多。因此,我们可以进一步对代码进行拆分,把功能独立的模块封装成一个独立的函数,真正做到按需拆分。

在下面,我们新建了一个函数 useTodos:

function useTodos() {

  let title = ref("");

  let todos = ref([{ title: "学习Vue", done: false }]);

  function addTodo() {

​    todos.value.push({

​      title: title.value,

​      done: false,});

​    title.value = "";

  }

  function clear() {

​    todos.value = todos.value.filter((v) => !v.done);

  }

  let active = computed(() => {return todos.value.filter((v) => !v.done).length;

  });

  let all = computed(() => todos.value.length);

  let allDone = computed({get: function () {return active.value === 0;},set: function (value) {

​      todos.value.forEach((todo) => {

​        todo.done = value;});},

  });

  return { title, todos, addTodo, clear, active, all, allDone };

}

这个函数就是把那些和清单相关的所有数据和方法,都放在函数内部定义并且返回,这样这个函数就可以放在任意的地方来维护。

而我们的组件入口,也就是

<script setup>

import { ref, computed } from "vue";

let count = ref(1)

function add(){

​    count.value++

}

let { title, todos, addTodo, clear, active, all, allDone } = useTodos();

</script>

我们在使用 Composition API 拆分功能时,也就是执行 useTodos 的时候,ref、computed 等功能都是从 Vue 中单独引入,而不是依赖 this 上下文。其实你可以把组件内部的任何一段代码,从组件文件里抽离出一个独立的文件进行维护。

现在,我们引入追踪鼠标位置的需求进行讲解,比如我们项目中可能有很多地方需要显示鼠标的坐标位置,那我们就可以在项目的 src/utils 文件夹下面新建一个 mouse.js。我们先从 Vue 中引入所需要的 ref 函数,然后暴露一个函数,函数内部和上面封装的 useTodos 类似,不过这次独立成了文件,放在 utils 文件下独立维护,提供给项目的所有组件使用。

import {ref} from 'vue'

export function useMouse(){const x = ref(0)const y = ref(0)return {x, y}

}

想获取鼠标的位置,我们就需要监听 mousemove 事件。这需要在组件加载完毕后执行,在 Composition API 中,我们可以直接引入 onMounted 和 onUnmounted 来实现生命周期的功能。

看下面的代码,组件加载的时候,会触发 onMounted 生命周期,我们执行监听 mousemove 事件,从而去更新鼠标位置的 x 和 y 的值;组件卸载的时候,会触发 onUnmounted 生命周期,解除 mousemove 事件。

import {ref, onMounted,onUnmounted} from 'vue'

export function useMouse(){const x = ref(0)const y = ref(0)function update(e) {

​      x.value = e.pageX

​      y.value = e.pageY

​    }onMounted(() => {

​      window.addEventListener('mousemove', update)})onUnmounted(() => {

​      window.removeEventListener('mousemove', update)})return { x, y }

}

完成了上面的鼠标事件封装这一步之后,我们在组件的入口就可以和普通函数一样使用 useMouse 函数。在下面的代码中,上面的代码返回的 x 和 y 的值可以在模板任意地方使用,也会随着鼠标的移动而改变数值。

import {useMouse} from '../utils/mouse'

let {x,y} = useMouse()

相信到这里,你一定能体会到 Composition API 对代码组织方式的好处。简单来看,因为 ref 和 computed 等功能都可以从 Vue 中全局引入,所以我们就可以把组件进行任意颗粒度的拆分和组合,这样就大大提高了代码的可维护性和复用性。

Composition API 带来的好处你已经掌握了,而

<script >

import { ref } from "vue";

export default {

  setup() {let count = ref(1)function add() {

​      count.value++}return {

​      count,

​      add

​    }

  }

}

</script>

在上面的代码中,我们要在

使用

style 样式的特性

除了 script 相关的配置,我也有必要给你介绍一下 style 样式的配置。比如,在 style 标签上,当我们加上 scoped 这个属性的时候,我们定义的 CSS 就只会应用到当前组件的元素上,这样就很好地避免了一些样式冲突的问题。

我们项目中的样式也可以加上如下标签:

<style scoped>

h1 {

  color: red;

}

</style>>

这样,组件就会解析成下面代码的样子。标签和样式的属性上,新增了 data- 的前缀,确保只在当前组件生效。

<h1 data-v-3de47834="">1</h1>

<style scoped>

h1[data-v-3de47834] {

​    color: red;

}

</style>

如果在 scoped 内部,你还想写全局的样式,那么你可以用:global 来标记,这样能确保你可以很灵活地组合你的样式代码(后面项目中用到的话,我还会结合实战进行讲解)。而且我们甚至可以通过 v-bind 函数,直接在 CSS 中使用 JavaScript 中的变量。

在下面这段代码中, 我在 script 里定义了一个响应式的 color 变量,并且在累加的时候,将变量随机修改为红或者蓝。在 style 内部,我们使用 v-bind 函数绑定 color 的值,就可以动态地通过 JavaScript 的变量实现 CSS 的样式修改,点击累加器的时候文本颜色会随机切换为红或者蓝。

<template>

  <div><h1 @click="add">{{ count }}</h1>

  </div>

</template>

<script setup>

import { ref } from "vue";

let count = ref(1)

let color = ref('red')

function add() {

  count.value++

  color.value = Math.random()>0.5? "blue":"red"

}

</script>

<style scoped>

h1 {

  color:v-bind(color);

}

</style>>

点击累加器时文本颜色的切换效果,如下图所示:
在这里插入图片描述

总结

我们来总结一下今天都学到了什么吧。今天的主要任务就是使用 Composition API +

然后,我们通过把功能拆分成函数和文件的方式,掌握到 Composition API 组织代码的方式,我们可以任意拆分组件的功能,抽离出独立的工具函数,大大提高了代码的可维护性。

最后我们还学习了 style 标签的特殊属性,通过标记 scoped 可以让样式只在当前的组件内部生效,还可以通过 v-bind 函数来使用 JavaScript 中的变量去渲染样式,如果这个变量是响应式数据,就可以很方便地实现样式的切换。

相信学完今天这一讲,你一定会对我们为什么需要 Composition API 有更进一步的认识,而对于

思考题

最后给你留一个思考题,Composition API 和

以任意拆分组件的功能,抽离出独立的工具函数,大大提高了代码的可维护性。

最后我们还学习了 style 标签的特殊属性,通过标记 scoped 可以让样式只在当前的组件内部生效,还可以通过 v-bind 函数来使用 JavaScript 中的变量去渲染样式,如果这个变量是响应式数据,就可以很方便地实现样式的切换。

相信学完今天这一讲,你一定会对我们为什么需要 Composition API 有更进一步的认识,而对于

思考题

最后给你留一个思考题,Composition API 和

欢迎你在留言区分享你的想法,当然也推荐你把这一讲推荐给你自己的朋友、同事。我们下一讲见!

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到