前言
记得有段时间特别想在Vue中玩TypeScript
,但是 Vue2.x对 TypeScript 的支持并不是特别好,一种比较好的实现方式是通过 Vue Class Component
库去实现,主要是通过装饰器
来对组件的行为进行修改,通过写Class
组件的方式去写组件,从而加上 TypeScript。但是装饰器目前还只是提案,并没有正式纳入规范中。
终于,2020.9.19凌晨,Vue3.0发布了,它的源码98%都是通过TypeScript写的,并且,通过组合式的方式去编写代码,几乎完美契合TypeScript的编码风格,我们可以随意的在Vue中写TypeScript,而不需要通过各种修饰或者插件去强行辅助我们写TS了。
Vue3.0 部分 API
尝鲜
这里就不过多的去介绍新的 API 了,官网里面最详细了。
createApp
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。可以在 createApp 之后链式调用其它方法。该函数第一个参数
接收一个根组件选项的对象,第二个参数
可以将 props 传递给应用程序
createApp(
App,
{
username: 'alex cheng' // 提供 props,App 组件内部通过 props: ['username'] 接收
}
).mount('#app')
provide / inject
provide 和 inject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。
// 父组件
setup() {
const globalData = reactive({
name: 'alex.cheng'
})
provide('globalData', globalData)
}
// 子/孙 组件
setup() {
const globalData = inject('globalData')
}
注意:
可以在 setup 中获取,也可以在生命周期(onMounted/onBeforeMount)中获取。但是不能在方法中获取,比如点击事件在方法中打印,会是 undefined,并且Vue会给出警告。
function showInject () {
// 获取顶层组件的数据
const globalObj = inject('globalObj')
console.log(globalObj)
}
setup
一个组件的选项,在创建组件之前执行,代替了beforeCreate
和created
钩子函数。它有两个参数,第一个是 props
,第二个是当前上下文context
,用来代替以前的this
,emit
方法就挂载在context
上。(优先于 data 方法执行)
并作为组合式 API 的入口点,后续都会在这里做数据处理。
defineComponent
顾名思义,这是一个定义组件的方法,传递一个包含组件选项的对象
import { defineComponent, h } from 'vue'
const DefineComp = defineComponent({
data () {
return {
count: 11
}
},
render () {
return h(
'h1',
{
class: 'define-comp'
},
`${this.count}`
)
}
})
或者是一个setup
函数(函数名将作为组件名来使用)
import { defineComponent, ref } from 'vue'
const HelloWorld = defineComponent(function HelloWorld() {
const count = ref(0)
return { count }
})
getCurrentInstance
getCurrentInstance
支持访问内部组件实例,用于高阶用法或库的开发。只能在 setup 或生命周期钩子中调用。
注意: 如需在 setup 或生命周期钩子外使用,请先在 setup 中调用 getCurrentInstance() 获取该实例然后再使用。
const MyComponent = {
setup() {
const internalInstance = getCurrentInstance() // works
const id = useComponentId() // works
const handleClick = () => {
getCurrentInstance() // doesn't work
useComponentId() // doesn't work
internalInstance // works
}
onMounted(() => {
getCurrentInstance() // works
})
return () =>
h(
'button',
{
onClick: handleClick
},
`uid: ${id}`
)
}
}
// 在组合式函数中调用也可以正常执行
function useComponentId() {
return getCurrentInstance().uid
}
watch
Vue3中的 watch 方法和 Vue2 中的watch不太一样,它的第一个参数是要监听的对象,第二个参数是回调函数((newVal, oldVal) => {}),第三个参数是监听属性({ immediate, deep })
watch(
() => state.list.length, // 监听 list 数组的长度
(v = 0) => {
state.nums = v // 当数组的长度发生变化,将 nums 的值修改
},
{ immediate: true } // 立即执行
)
computed
使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
const count = ref(0)
const plusOne = computed(() => count.value + 1)
plusOne.value++ // error
如果需要修改计算属性,就需要加上 setter
函数
onst count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
// 直接计算 count.value
count.value = val - 1
}
})
watchEffect
在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
reactive | ref
创建响应式对象或者基础类型变量
toRef | toRefs | unRef
TodoList Demo
每次学习一门新的语言或者一些新的 API, TodoList demo 肯定是跑不掉的。
看看如何用 Vue3 的 Composition API
来实现一个 todolist demo 吧!
需求
一个 todolist,它应该是包含输入框,添加任务按钮,过滤(状态:全部,已完成,未完成),显示任务列表,还有删除任务按钮。
界面大概是这样的!
看到这个界面,大概可以拆分成三个组件,一个是包含输入框和添加按钮的组件(TodoAdd.vue
),然后是过滤的组件(TodoFilter.vue
),最后是列表组件(TodoList.vue
),不过 Todolist
还可以继续拆分,将每条任务当成一个组件(TodoListItem.vue
)。
毫无疑问,这三个组件理应被容器组件(App.vue
)包裹起来,并且初始化各功能组件的数据。代码如图:
Vue3 中最重要的思想就是 Composition API
,通过组合的方式,更容易抽离出逻辑处理,并拆分逻辑与结构,降低代码的耦合性。
如何初始化界面的数据呢 ?
我们已经知道 setup
方法相当于 Vue2 中的 beforeCreate
和 created
钩子,那么在这里我们可以做数据初始化的工作。
首先,我们在 src
下面创建 composables
文件夹,文件夹下面放组件可能需要用到的逻辑或者方法。接着我们创建一个 useTodo.js
文件,它的作用是获取任务列表,操作列表(添加,删除)。
// src/composables/useTodos.js
export default function useTodos() {
const todos = ref([]);
// 添加 todo
const addTodo = (todo) => todos.value.push(todo);
// 删除 todo
const deleteTodo = (id) => {
const Index = todos.value.findIndex(todo => todo.id === id)
todos.value.splice(Index, 1)
}
// 获取远程 todos
const fetchTodos = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos?_limit=5"
);
const rawTodos = await response.json()
todos.value = rawTodos.map((todo) => ({
id: todo.id,
content: todo.title,
completed: todo.completed,
}));
};
onMounted(() => {
fetchTodos();
});
// 返回列表数据,以及操作方法,这里返回的东西,将会在 setup 方法中通过 return 暴露出去
return {
todos,
addTodo,
deleteTodo
};
}
接着回到 App.vue
中,在 setup
方法中,使用 useTodos.js
中的数据和方法。
setup() {
// 拿到 todo 清单,以及向 todo 清单添加任务的方法
const {todos, addTodo, deleteTodo} = useTodos()
return {
todos,
addTodo,
deleteTodo
}
}
todos 数据 和 deleteTodo 方法就可以当作 props 传递给 todolist
组件了,addTodo 方法就传递给 todoAdd 组件。
// todoAdd.vue
setup(props, context) {
return useEmitAddTodo(context.emit);
}
// 我们还可以创建组件专属的 composable logic
function useEmitAddTodo(emit) {
const todoContent = ref("");
const emitAddTodo = () => {
if (todoContent.value === '') {
return alert('请填写明确的任务!')
}
const todo = {
id: Math.random(),
content: todoContent.value,
completed: false,
};
// 通过 emit 方法,调用传递给当前组件的方法
emit("add-todo", todo);
todoContent.value = "";
};
return {
todoContent,
emitAddTodo,
};
}
接着,我们在 composables
文件夹下创建 useFilteredTodo.js
文件,里面存放处理过滤列表的方法和过滤之后的数据。
// src/composables/useFilteredTodo.js
export default function useFilteredTodos(todos) {
// 通过 ref 创建一个响应式的变量 filter
const filter = ref("all");
// 过滤 todo
const filteredTodos = computed(() => {
switch (filter.value) {
case "done":
return todos.value.filter((todo) => todo.completed);
case "todo":
return todos.value.filter((todo) => !todo.completed);
default:
return todos.value;
}
});
return { filter, filteredTodos };
}
然后在 TodoFilter.vue
组件中导入进去,代码如下:
结构如下:
<template>
<div class="filters">
<span
v-for="filter in filters"
:key="filter.value"
@click="$emit('change-filter', filter.value)"
class="filter"
:class="{ active: selected == filter.value }"
>{{ filter.label }}</span
>
</div>
</template>
逻辑部分:通过 emit 调用 change-filter
方法,将当前点击的 state 传递出去,改变 useFilteredTodos
中的 filter,由于过滤后的数据,是通过计算属性来处理的,所以当 filter 发生改变,那么计算属性就会重启计算,拿到新的 todo list 数据!
export default {
name: "TodoFilter",
props: ["selected"],
setup() {
const filters = reactive([
{ label: "全部", value: "all" },
{ label: "已完成", value: "done" },
{ label: "未完成", value: "todo" },
]);
return { filters };
},
};
到此,基本上就完成了TodoList Demo
了,通过 Composition API 的方式,我们可以更方便的去组合代码。以前我们将方法都放在 methods
里面,当我们的代码量越来越大的时候,我们就只能通过搜索的方式,在代码里面反复横跳。特别是接手了别人的代码,这种现象尤为明显。
源码链接:戳这里