You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

369 lines
15 KiB
Markdown

2 years ago
# 06 | 新的代码组织方式Composition API + <script setup> 到底好在哪里?
你好,我是大圣,欢迎进入课程的第六讲。
在上一讲中,我带你搭建了项目的雏形,这是后面项目开发的起点。从今天开始,我就带你在这个骨架结构的基础之上,开始项目的实战开发。首先我们要掌握的,就是 Vue 3 的Composition API + <script setup>
![](https://static001.geekbang.org/resource/image/6f/0a/6fd86f3d33a0200d64c7423bc88e890a.png?wh=1222x432)
我们在前面的第三讲中,有详细地讲到过 Composition API 相信你对这个API 的语法细节已经有所掌握了。那你肯定会很好奇,这个<script setup><script setup>
别急今天我就带你使用Composition API 和 <script setup> **Composition API ** <script setup>Composition API
## Composition API 和 <script setup> 上手
首先我想提醒你,我们在这一讲中写代码的方式,就和前面的第二讲有很大的区别。
在第二讲中,我们开发清单应用时,是直接在浏览器里使用 Options API 的方式写代码;但在接下来的开发中,我们会直接用单文件组件——也就是 `.vue` 文件,的开发方式。这种文件格式允许我们把 Vue 组件的HTML、CSS和JavaScript写在单个文件内容中。下面我带你用单文件组件的方式去重构第二讲做的清单应用。
我们现在已经搭建好了项目的骨架,以后在这个骨架之内会有很多页面和组件。从这里开始,我们就要逐步适应组件化的开发思路,新的功能会以组件的方式来组织。
按照上一讲制定的规范首先我们打开项目文件夹下面的src下的components目录新建一个Todolist.vue ,并在这个文件里写出下面的代码:
```xml
<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标记我们使用<script setup>styleCSS
从具体效果上看,这段代码实现了一个累加器。在 <script setup> 语法中我们使用引入的ref函数包裹数字返回的count变量就是响应式的数据使用add函数实现数字的修改。需要注意的是对于ref返回的响应式数据我们需要修改 `.value` 才能生效,而在 <script setup> 标签内定义的变量和函数,都可以在模板中直接使用。
实现累加器以后我们再回到src/pages/Home.vue 组件中使用如下代码显示清单应用。在这段代码里我们直接import TodoList.vue组件然后<script setup>template使
```xml
<template>
<h1>这是首页</h1>
<TodoList />
</template>
<script setup>
import TodoList from '../components/TodoList.vue'
</script>
```
这个时候我们就把清单功能独立出来了,可以在任意你需要的地方复用。在课程的后续内容中,我会详细给你介绍基于组件去搭建应用的方式。**通过这种方式,你可以实现对业务逻辑的复用。这样做的好处就是,如果有其他页面也需要用到这个功能,可以直接复用过去。**
然后我们就可以基于新的语法实现之前的清单应用。下面的代码就是把之前的代码移植过来后使用ref包裹的响应式数据。在你修改title和todos的时候注意要修改响应式数据的value属性。
```xml
<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代码块中加入下面的代码
```xml
<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
```xml
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>useTodosuseTodos
```xml
<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文件下独立维护提供给项目的所有组件使用。
```xml
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事件。
```xml
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的值可以在模板任意地方使用也会随着鼠标的移动而改变数值。
```xml
import {useMouse} from '../utils/mouse'
let {x,y} = useMouse()
```
相信到这里,你一定能体会到 Composition API 对代码组织方式的好处。简单来看,**因为ref和computed等功能都可以从 Vue 中全局引入,所以我们就可以把组件进行任意颗粒度的拆分和组合**,这样就大大提高了代码的可维护性和复用性。
## <script setup> 好用的功能
Composition API 带来的好处你已经掌握了,而<script setup>使Composition API <script setup>
```xml
<script >
import { ref } from "vue";
export default {
setup() {
let count = ref(1)
function add() {
count.value++
}
return {
count,
add
}
}
}
</script>
```
在上面的代码中,我们要在<script>setupOptionssetup使setupcountadd
**使用 <script setup> Vue 3 **<script setup>使await使
## style样式的特性
除了script相关的配置我也有必要给你介绍一下style样式的配置。比如在style标签上当我们加上scoped这个属性的时候我们定义的CSS就只会应用到当前组件的元素上这样就很好地避免了一些样式冲突的问题。
我们项目中的样式也可以加上如下标签:
```xml
<style scoped>
h1 {
color: red;
}
</style>>
```
这样组件就会解析成下面代码的样子。标签和样式的属性上新增了data-的前缀,确保只在当前组件生效。
```xml
<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的样式修改点击累加器的时候文本颜色会随机切换为红或者蓝。
```xml
<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>>
```
点击累加器时文本颜色的切换效果,如下图所示:
![图片](https://static001.geekbang.org/resource/image/59/18/5974c0d7dbce32306bd2a207a6a37f18.gif?wh=343x105)
## 总结
我们来总结一下今天都学到了什么吧。今天的主要任务就是使用Composition API +<script setup>ref使Composition API使<script setup>使
然后我们通过把功能拆分成函数和文件的方式掌握到Composition API组织代码的方式我们可以任意拆分组件的功能抽离出独立的工具函数大大提高了代码的可维护性。
最后我们还学习了style标签的特殊属性通过标记scoped可以让样式只在当前的组件内部生效还可以通过v-bind函数来使用JavaScript中的变量去渲染样式如果这个变量是响应式数据就可以很方便地实现样式的切换。
相信学完今天这一讲你一定会对我们为什么需要Composition API有更进一步的认识而对于<script setup>Compostion使Composition API + <script setup>
## 思考题
最后给你留一个思考题Composition API 和 <script setup> ref valueimport使 Vue
欢迎你在留言区分享你的想法,当然也推荐你把这一讲推荐给你自己的朋友、同事。我们下一讲见!