GDPU Vue前端框架开发 单文件组件
vue组件模块化开发入门,单文件组件应用。
表格组件
还记得上节课(bushi~)上篇文章npm的搭建运行项目吗。这篇用一些组件使用的例子来巩固一下。
首先,用npm init vite@latest新建好你的项目,然后cd进到项目里面,首次创建用npm i安装依赖,接着用npm run dev运行开发环境,点击url即可,当重新打开时,同样cd进入到项目里,然后npm run dev就行了。以上几步操作要记住,后面的vue项目开发会常用到。
进入到项目后,要清楚默认的文件哪些是可以替换的,要是你用的是vite记得把入口文件main.js文件默认的css文件注释掉或删掉,用脚手架cli的好像没有。
import { createApp } from 'vue'
// import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
由于这里挂载了app根组件,然后vite中index.html渲染页面文件引入了main.js,cli中则是挂载了app根组件节点,因此把你写好的自定义组件放到app根组件里,就可以在页面上显示了。
接着,先看题来定义组件。
1.实现表格的组件写法,实现表格奇偶行显示不同样式的案例,并实现鼠标移入及点击选中变化样式功能。要求:单文件mytable.vue方式,表格样式可以自定义。
展开项目的src目录下的components,然后把默认的vue文件删去,新建一个mytable.vue文件,当然为了养成做项目的习惯,也可以先新建一个mytale文件夹做分类,再新建一个mytable.vue文件,注意,这里的路径要记住,写完要导入到app组件才能显示。
新建完一个空文件后,创建一个单文件vue模板,什么是单文件组件,单文件组件是指将 Vue 组件的模板(HTML)、逻辑(JavaScript)和样式(CSS)都写在一个文件中的方式。其实就是一个组件写你学过的前端三件套,其中template对应html部分,script是js部分,style是css部分。这里可以用一个编译器的插件Vue VSCode Snippets,在新建的文件中输入vbase敲回车即可搭建vue单文件模板了。
<template>
<div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
然后就可以在里面写了,注意样式的scoped表示当前组件使用,比如这题是做表格,安排。
<template>
<table>
<thead>
<tr>
<th>序号</th>
<th>书名</th>
<th>作者</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in tableData"
:key="item.id"
:class="{
'odd-row': index % 2 === 0,
'even-row': index % 2 !== 0,
'hover-row': hoverId === index,
'selected-row': selectId === index
}"
@mouseenter="mEnter(index)"
@mouseleave="mLeave(index)"
@click="trClick(index)"
>
<td>{{ item.id }}</td>
<td>{{ item.title }}</td>
<td>{{ item.author }}</td>
<td>{{ item.price }}</td>
<td><button @click="deleteRow(index)">删除</button></td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyTable',
data() {
return {
tableData: [
{ id: 1, title: "高等数学基础", author: "李明", price: "98" },
{ id: 2, title: "Java程序设计", author: "张伟", price: "105" },
{ id: 3,title: "Vue.js从入门到实战",author: "王磊",price: "139",},
],
hoverId: null, // 鼠标悬停时的行索引
selectId: null, // 被选中的行索引
};
},
methods: {
mEnter(index) {
this.hoverId = index; // 鼠标移入时,设置悬停的行索引
},
mLeave(index) {
this.hoverId = null; // 鼠标移出时,清除悬停的行
},
trClick(index) {
this.selectId = index; // 点击行时,设置选中的行索引
},
deleteRow(index) {
// 删除该行数据
this.tableData.splice(index, 1);
},
},
};
</script>
<style scoped>
/* 奇数行背景色 */
.odd-row {
background-color: #f2f2f2;
}
/* 偶数行背景色 */
.even-row {
background-color: #d6d6d6;
}
/* 鼠标悬浮时的行高亮 */
.hover-row {
background-color: #bbdefb;
cursor: pointer;
}
/* 点击选中行的背景色 */
.selected-row {
background-color: #ff9800;
font-weight: bold;
}
/* 表头背景色 */
th {
background-color: #4caf50;
color: #ecf0f1;
padding: 10px;
text-align: center;
}
/* 表格样式 */
table {
margin: 20px;
width: 50%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #fff;
padding: 8px;
text-align: center;
}
button {
padding: 5px 10px;
cursor: pointer;
}
button:hover {
background-color: #d32f2f;
}
</style>
📚写完后别忘了导入app组件。记住,在vue中使用组件分四步:导出组件、导入组件、注册组件、使用组件。定义好文件后做导出export default暴露出去,然后在需要调用该组件的文件如app.vue中进行import,路径要写对,接着在export default组件里写components{}进行局部注册组件,或app.component('component', component)全局注册,注册完之后就可以使用在template里使用这个组件了,组价的双标签可以简写为如<component />,要记好这几步。
<template>
<div id="app">
<MyTable />
</div>
</template>
<script>
import MyTable from './components/mytable/mytable.vue';
export default {
name: 'App',
components: {
MyTable,
},
};
</script>
<style>
/* 主样式 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
</style>
在控制台的命令行运行npm run dev点进即可看到表格。
购物车组件
2.实现下列购物车实现,注意购物车的父子组件间的数据传递,整个购物车是一个父组件,购物车的每个项目是子组件。父组件传送数据给子组件显示,子组件按钮发送数据给父组件。
<template>
<div class="container">
<h1>购物车</h1>
<table>
<thead>
<tr>
<th>序号</th>
<th>商品</th>
<th>价格</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<CartItem
v-for="(product, index) in cartProducts"
:key="index"
:product="product"
:index="index"
@update-quantity="updateQuantity"
@remove-product="removeProduct"
/>
</tbody>
<tfoot>
<tr>
<td colspan="5" style="text-align: center; font-weight: bold;">
总价:{{ totalPrice }}
</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script>
import { ref, computed } from 'vue';
import CartItem from './CartItem.vue';
export default {
components: { CartItem },
setup() {
const cartProducts = ref([
{ name: '巧克力慕斯', price: 12.99, quantity: 3 },
{ name: '草莓冰淇淋', price: 8.99, quantity: 2 },
{ name: '蜜桃奶昔', price: 25.00, quantity: 5 }
]);
const totalPrice = computed(() => {
return cartProducts.value.reduce((total, product) => {
return total + product.price * product.quantity;
}, 0).toFixed(2);
});
const updateQuantity = (index, quantity) => {
cartProducts.value[index].quantity = quantity;
};
const removeProduct = (index) => {
cartProducts.value.splice(index, 1);
};
return { cartProducts, totalPrice, updateQuantity, removeProduct };
},
};
</script>
<style>
.container {
width: 80%;
margin: auto;
padding: 20px;
}
table {
width: 60%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ccc;
padding: 10px;
text-align: center;
}
tfoot td {
font-weight: bold;
}
</style>
<template>
<tr>
<td>{{ index + 1 }}</td>
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
<td>
<button @click="decrement">-</button>
{{ product.quantity }}
<button @click="increment">+</button>
</td>
<td>
<button @click="remove">删除</button>
</td>
</tr>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
product: Object,
index: Number,
},
setup(props, { emit }) {
const increment = () => {
emit('update-quantity', props.index, props.product.quantity + 1);
};
const decrement = () => {
if (props.product.quantity > 0) {
emit('update-quantity', props.index, props.product.quantity - 1);
}
};
const remove = () => {
emit('remove-product', props.index);
};
return { increment, decrement, remove };
},
});
</script>
<style>
button {
margin: 0 5px;
}
</style>
写完后,记得导入app根组件显示,别忘了使用组件的几个步骤。
<template>
<div id="app">
<MyTable />
<ShoppingCart />
</div>
</template>
<script>
import MyTable from './components/mytable/mytable.vue';
import ShoppingCart from './components/cart/ShoppingCart.vue';
export default {
name: 'App',
components: {
MyTable,
ShoppingCart,
},
};
</script>
<style>
/* 主样式 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
</style>
这题用了vue3的组合式api ,组合式api能够提高了代码的复用性,使得逻辑更加模块化,便于拆分和维护,适用于更复杂的应用场景,提升了灵活性。
再介绍一个快速导入组件使用的方法,下好vue-official插件,然后长按组件拖到template要使用的位置。
课后习题
1.组件的data为什么必须是一个函数?
在 Vue 组件中,data 必须是一个函数,以确保每个组件实例都有独立的状态。如果 data 直接是一个对象,所有组件实例将共享同一个数据对象,这会导致不同实例之间的数据相互干扰。通过将 data 定义为函数,Vue确保每个组件实例都有自己的独立数据副本,在每次创建组件实例时都会调用该函数,返回一个新的对象,从而保证了数据的独立性和组件的复用性。
2.写出Vue中组件的6种通信方法 。
(1)Props、$emit:
props:父组件通过props向子组件传递数据,实现数据的单向流动,保持数据的一致性和可预测性。
$emit:子组件可以通过触发事件($emit)来向父组件发送消息,父组件通过监听这些事件来响应子组件的行为。
(2)Provide/Inject:
这种方法允许一个祖先组件提供数据或方法,任何其后代组件都可以通过inject来接收这些数据或方法,实现跨组件层级的通信。
(3)Vuex:
Vuex是 Vue.js 的状态管理库,适用于大型应用。可以通过 Vuex 管理共享状态,实现组件之间的数据共享和通信。
(4)$attrs:
在Vue 3中,$attrs 对象包含了所有父组件传递给子组件的非 prop 属性(即类似 HTML 属性的内容)。这使得子组件能够接收任意的传入属性,而无需在组件中显式定义每一个 prop,为组件间的灵活数据传递提供了便利。
(5)Slots插槽:
插槽允许父组件向子组件的模板内部插入HTML或其他Vue组件,是一种灵活的内容分发机制,特别适用于组件库和高级布局设计。
(6)$refs和$parent/$children/$root:
$refs:用于直接访问组件内的DOM节点或子组件实例,常用于需要直接操作DOM或调用子组件方法的场景。
$parent/$children /$root:这些实例属性允许 组件直接访问其父组件、子组件列表或根组件实例,适用于直接的组件实例操作,但应谨慎使用以避免增加组件间的耦合。
3.怎么理解单向数据流,父子之间传值的方式是什么?
单向数据流是指数据只能从父组件流向子组件,确保了数据流动的可控性和可预测性。父子之间传值的方式主要有两种:
父组件向子组件传值: 使用 props,父组件通过 props 向子组件传递数据,子组件接收并使用这些数据,但不能直接修改。
子组件向父组件传值: 子组件通过 $emit 触发事件,通知父组件更新数据或进行操作,父组件通过监听事件来接收并处理这些数据。
实验心得
写剧本写到慌的。