前言
这个demo是一个简单的Job List app,跟着油管上的一个博主做的,学习了解Vue3和TS语法、项目结构等。
视频地址:Vue 3 with TypeScript Tutorial
repo地址:https://github.com/HaibiPeng/frontend-needtoknow/tree/master/hyrule-jobs
interface/接口定义
我们在setup()
中定义jobs
对象数组,这里需要对其进行类型定义,通过泛型(generics)定义。
import { defineComponent, ref } from "vue";
import Job from "./typings/Job";
export default defineComponent({
name: "app",
setup() {
const jobs = ref<Job[]>([
{
title: "farm worker",
location: "lon lon ranch",
salary: 30000,
id: "1",
},
{
title: "quarryman",
location: "death mountain",
salary: 40000,
id: "2",
},
{
title: "flute player",
location: "the lost woods",
salary: 35000,
id: "3",
},
{ title: "fisherman", location: "lake hylia", salary: 21000, id: "4" },
{
title: "prison guard",
location: "gerudo valley",
salary: 32000,
id: "5",
},
]);
return { jobs };
}
});
Note:
setup()
是一个组件选项,在创建组件之前执行,一旦 props 被解析,并作为组合式 API 的入口点。
新建Job.d.ts
类型定义文件:
interface Job {
title: string;
location: string;
salary: number;
id: string;
}
export default Job;
这里用interface
定义Job的内部结构/字段,描述一个对象或者函数,在后续的引用中TypeScipt会检查是否引用的是Job类型中存在的字段。
因为创建的是对象数组,因此需要在Job
后加一个[]
,表示以Job
类型创建的对象数组:
const jobs = ref<Job[]>([])
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。
下面是应用了“option bags”的例子:
interface SquareConfig {
color?: string;
width?: number;
}
type alias
在OrderTerm.d.ts中,使用type定义OderTerm
的不同类型:
type OrderTerm = "location" | "title" | "salary";
export default OrderTerm;
我们一直在通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但是想要多次使用同一个类型并用一个名称来引用它是很常见的。
一个类型别名正是-一个名称为任何类型。类型别名的语法是:
type Point = {
x: number;
y: number;
};
实际上,您可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型:
type ID = number | string;
PropType
在JobList.vue
中定义props时,需要对props的类型进行推断和定义:
import { computed, defineComponent, PropType } from "vue";
import Job from "@/typings/Job";
import OrderTerm from "@/typings/OrderTerm";
export default defineComponent({
props: {
jobs: {
// type asserti + generics
type: Array as PropType<Job[]>,
required: true,
},
order: {
type: String as PropType<OrderTerm>,
required: true,
},
},
setup(props) {
const orderedJobs = computed(() => {
return [...props.jobs].sort((a: Job, b: Job) => {
return a[props.order] > b[props.order] ? 1 : -1;
});
});
return { orderedJobs };
},
});
这里用到了vue
中的PropType
。
过渡/动画
在对jobs进行排序时,引入了动画:
<p>Ordered by {{ order }}</p>
<transition-group name="list" tag="ul">
<li v-for="job in orderedJobs" :key="job.id">
<h2>{{ job.title }} in {{ job.location }}</h2>
<div class="salary">
<p>{{ job.salary }} rupees</p>
</div>
<div class="discription">
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Rem omnis
voluptatum eius doloremque optio iusto sequi dignissimos. Pariatur
earum assumenda dolores possimus quidem quam, reprehenderit aliquid
consequuntur amet non facere.
</p>
</div>
</li>
</transition-group>
这里为transition-group
定义了一个name
为list
,然后再style中添加对应的动画:
.list-move {
transition: all 1s;
}
<transition-group>
像其它自定义组件一样,需要一个根元素。默认的根元素是一个 <span>
,但可以通过 tag prop 定制。
<transition-group tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</transition-group>
点击事件
<div class="order">
<button @click="handleClick('title')">Order by title</button>
<button @click="handleClick('salary')">Order by salary</button>
<button @click="handleClick('location')">Order by location</button>
</div>
在setup()
中,定义hanleClick
方法,通过传入参数决定order字段的值:
setup() {
const order = ref<OrderTerm>("title");
const handleClick = (term: OrderTerm) => {
order.value = term;
};
return { order, handleClick };
},
在JobList.vue
组件中,通过在setup()
中传入props
参数引用jobs
和order
字段,通过order
字段的值为key,用sort
方法对jobs
进行排序:
setup(props) {
const orderedJobs = computed(() => {
return [...props.jobs].sort((a: Job, b: Job) => {
return a[props.order] > b[props.order] ? 1 : -1;
});
});
return { orderedJobs };
},