Vue3.x 从零开始(六)—— Router + Vuex + TypeScript 实战演练(下)
在上一篇实战演练中,已经将项目搭建好,并介绍了 Router、Vuex 的基本用法
接下来会以一个 Todo List 项目来介绍实战中如何使用 Composition API
一、输入框与列表(按键别名 + computed 类型声明 )
首先是输入框,由于需要支持回车键提交,所以需要监听 keydown 事件
如果是传统的按键处理,需要在事件对象中根据 keyCode 来判断按键
Vue 提供了一些常用的按键修饰符,不用在事件处理函数中再做判断
比如这里就使用了 enter 修饰符,直接监听 enter 键的 keydown 事件
列表部分,需要判断当前列表是否为空,如果为空则展示空状态
这里使用了 v-if 和 v-else 来做条件判断,而其判断条件 showList 是一个计算属性 computed
在 TypeScript 的项目中,如果像 JS 项目一样添加计算属性,无法进行类型推断
需要加上类型声明:
// 在 setup() 中通过 computed() 注册的计算属性不需要声明类型
二、添加、删除条目(在 setup 中使用 vuex)
创建的条目需要保存到 store 中,首先需要定义条目类型
然后在 state 中新增 todoList 字段,用于保存列表
这里还添加了一个 todoListMap 字段,是 todoList 的字典项,后面查找条目的时候会用到
同时在 mutations 中新增添加和删除方法
Store 已经调整好了,接下来只要在组件中调用即可
可以像之前介绍的那样,使用 mapState 和 mapMutations 来导出对应的字段和方法
不过如果想在 setup 中使用 vuex,就需要用到 vuex 4 提供的 useStore 方法
import { useStore } from 'vuex'; export default { //... setup() { const store = useStore(); console.log('store--->', store); return { ...store.state } } }
从截图可以看到,useStore() 的返回值其实就是 $store
接下来的事情就简单了,手动导出需要用到的 state 和 mutations、actions 即可
这种方式导出 state 还行,但对于 mutation 和 action,需要一个一个手动创建函数并导出,就比较笨重
没关系,我们还有 mapMutations 和 mapActions 可以使用
但需要注意,不要在 setup 使用 mapState!
因为 mapState 导出 state 是一个函数(computed),这个函数内部使用了 this.$store
而 setup 中 this 是一个空值,所以在 setup 中使用 mapState 会报错
如果确实希望以 mapState 的形式在 setup 中导出 state,可以看一下 vuex-composition-helpers
import { useState, useActions } from 'vuex-composition-helpers'; export default { props: { articleId: String }, setup(props) { const { fetch } = useActions(['fetch']); const { article, comments } = useState(['article', 'comments']); fetch(props.articleId); // dispatch the "fetch" action return { // both are computed compositions for to the store article, comments } } }
三、查看条目详情(在 setup 中使用 router)
在条目详情页,可以在 url 上携带条目 id,然后通过 id 在 store 中找到对应的数据
这就需要调整路由配置文件 src/router/index.ts,配置 vue-router 中的动态路由
路由配置好了,接下来需要在列表上添加“查看详情”按钮的处理函数
如果这个函数写在 methods 里面,可以直接通过 this.$router.push() 来跳转页面
但是在 setup 中,就需要用到 vue-router 提供的 useRouter
import { useRouter } from 'vue-router'; export default { // ... setup() { const router = useRouter(); const viewItem = (id: string) => { router.push(`/about/${id}`); }; return { viewItem }; } }
然后在详情页,通过 useRoute(注意不是 userRouter )获取 params
四、完全使用 Composition API 开发组件
以上都是在 setup 中使用 Composition API,整个组件本身依然是使用 Options API 开发
想象一下,如果整个组件的 <script> 部分就是一个 setup 函数,会发生什么呢?
给 <script> 标签加上 setup 修饰符试试!
<script lang="ts" setup> // ... </script>
然后把整个 <script> 当做 setup 函数,改写上面的详情页
这样就能完全使用 Composition API 来开发组件了
和 setup 的区别在于,setup 最终需要 return 一个对象,而现在需要使用 export 来导出变量
如果需要使用 setup 函数的参数 ( props 和 context ),可以这么写:
<script lang="ts" setup="props, { emit }">
等效于
setup(props, { emit }) { // ... }
最后再贴一份 script-setup 的组件示例:
<script lang="ts" setup="props, { emit }"> import { defineComponent, computed, onMounted, ref, toRefs, } from 'vue'; const { modelValue, disabled } = toRefs(props); /* data */ export const currentValue = ref(''); export const isComposing = ref(false); /* methods */ export function handleInput() { if (isComposing.value) return; emit('update:modelValue', currentValue.value); } /* lifecycle */ onMounted(() => { currentValue.value = modelValue.value; }); /* computed */ export const inputDisabled = computed(() => disabled.value); export default defineComponent({ name: 'test-input', props: { modelValue: [String, Number], disabled: Boolean, }, emits: { 'update:modelValue': null, }, }); </script>
在这份代码中,我还是按照组件选项的方式,将变量分类放到一起
其实更合理的方式是按功能分类,这样更利于抽取逻辑
五、小结
Vuex 和 vue-router 都提供了可以在 setup 中获取实例的方法
这也侧面体现了 Vue 3 的 setup 是一个独立的钩子函数
它不会依赖于 Vue 组件实例,如果需要用到函数外部的变量,都需要从外部获取
同时也提醒我们在开发 Vue 3 的插件的时候,一定要提供相应的函数让开发者能在 setup 中使用