vue3 单文件组件的使用
# 单文件组件的使用
在vue等前端项目中,如果要导包引入其他文件内容,有三种格式内容可以被导入,分别是css、js、vue。src/App.vue,代码:
<script setup> // 前端导入其他文件内容[css/js/vue] import "./style.css" import settings from "./demo.js" import {func,func2} from "./demo.js"; console.log(settings.host); console.log(func(),func2()) import HelloWorld from './components/HelloWorld.vue' </script> <template> <HelloWorld msg="Vite + Vue" /> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>
src/demo.js,代码:
const settings = { host: "http://www.baidu.com", } // 暴露一个对象,允许其他页面通过导包语句 from 对象名 import xxx.js export default settings; // 暴露一个函数,允许其他页面通过导包语句 from {func, func2} import xxx.js export function func (){ return "helo!这是函数"; } export function func2 (){ return "helo!这是函数"; }
创建单文件组件
src/components目录下创建Home.vue。
组件文件名格式一般是大驼峰格式(每个单词首字母大写),以.vue结尾。
在组件中编辑三个标签,template标签中编写html内容、script标签操作 vm对象和style标签 编写css样式代码。
在script中因为vue发展过程中出现了2个不同的代码写法,所以存在了2个版本的script标签代码写法:传统的选项API与组合API。
其中,我们之前所学习的所有vue语法都是属于选项API写法,vue的所有代码全部被安排写到各种选项中,例如:data、methods、watch、created、updated、mounted、computed等都是选项。
然后,在vue3.0以后,支持了一种新的代码组织方法,就是组合API的写法。我们后面再去学习,现在我们在Home.vue组件中使用原来选项API语法进行使用。
src/components/Home.vue
<script> //vue3.0以后有2种组件的写法: //选项api的写法[vue2.0,vue3.0] //组合api的写法[vue3.0] //选项api的写法[vue2.0,vue3.0] export default { name: "Home", // 设置组件名,一般建议和组件名保持一次,否则报错 data(){ return { message: "hello,message", num: 100, } }, methods:{ show(){ console.log(this.message); } } } </script> <template> <p @click="show()">{{message}}</p> <button @click="num--">-</button> <input type="text" v-model.number="num"> <button @click="num++">+</button> </template> <style scoped> </style>
上面的组件中的script标签中的代码就是选项API的写法。其他的2个标签,template与style标签中的内容不管选项API还是组合API,写法都一模一样,没有任何区别。
通过App.vue导包引入Home.vue,展示效果。src/App.vue,代码:
src/App.vue
<script setup> // 前端导入其他文件内容[css/js/vue] import "./style.css" import settings from "./demo.js" import {func,func2} from "./demo.js"; console.log(settings.host); console.log(func(),func2()) import HelloWorld from './components/HelloWorld.vue' import Home from "./components/Home.vue" </script> <template> <!-- <HelloWorld msg="Vite + Vue" />--> <Home></Home> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>
src/main.js
import { createApp } from 'vue'
// import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
template 编写html代码的地方
<template> <div> <!-- vue2.0时代,必须外部增加一个div,保证template标签只有一个子元素 --> <p @click="show">{{message}}</p> <button @click="num--">-</button> <input type="text" v-model.number="num" > <button @click="num++">+</button> </div> </template>
script编写vue.js代码
单个组件中有且只有一个script标签,如果组件的代码风格使用的是选项API,则必须使用export default 把组件对象往外暴露。
如果组件的代码风格使用组合API,则script标签的开头部分必须是<script setup>
<script> //vue3.0以后有2种组件的写法: //选项api的写法[vue2.0,vue3.0] //组合api的写法[vue3.0] //选项api的写法[vue2.0,vue3.0] export default { name: "Home", // 设置组件名,一般建议和组件名保持一次,否则报错 data(){ return { message: "hello,message", num: 100, } }, methods:{ show(){ console.log(this.message); } } } </script>
style编写当前组件的css样式
单个组件内部,可以有0~1个style标签。如果存在style标签,style标签支持使用scoped属性来声明当前style标签内部的css样式是全局css样式还是组件内部的css样式。
src/App.vue
<script setup> // 前端导入其他文件内容[css/js/vue] import "./style.css" import settings from "./demo.js" import {func,func2} from "./demo.js"; console.log(settings.host); console.log(func(),func2()) import HelloWorld from './components/HelloWorld.vue' import Home from "./components/Home.vue" </script> <template> <!-- <HelloWorld msg="Vite + Vue" />--> <Home></Home> <h1>app组件的h1标签</h1> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.vue:hover { filter: drop-shadow(0 0 2em #42b883aa); } </style>
Home.vue
<script> //vue3.0以后有2种组件的写法: //选项api的写法[vue2.0,vue3.0] //组合api的写法[vue3.0] //选项api的写法[vue2.0,vue3.0] export default { name: "Home", // 设置组件名,一般建议和组件名保持一次,否则报错 data(){ return { message: "hello,message", num: 100, } }, methods:{ show(){ console.log(this.message); } } } </script> <template> <h1>home组件的h1标签</h1> <p @click="show()">{{message}}</p> <button @click="num--">-</button> <input type="text" v-model.number="num"> <button @click="num++">+</button> </template> <style> h1 { color: red; } </style>

Home.vue 写了全局样式 所以App.vue也被污染
<style> h1 { color: red; } </style>
Home.vue
<style scoped> h1 { color: red; } </style>
上面的代码效果:

有时候开发vue项目时,一个组件肯定存在多个不同的板块内容,而往往一个页面中的多个板块内容都会设置一个个单独的子组件,同时页面也可以算是一个大组件,此时页面组件就是分成多个子组件,加载显示出现的。而一个组件引入1个或多个小的组件,这种情况,我们就称之为“组件的嵌套”。
例如,我们就可以声明一个页面组件保存目录src/views

在views目录下, 编写页面组件List.vue的代码。
List.vue,代码:
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <Menu></Menu> </template> <style scoped> </style>
然后,在App.vue根组件中导入上面声明的List.vue页面组件。代码:
<script setup> import List from "./views/List.vue"; </script> <template> <List></List> </template> <style scoped> </style>
效果:

接着, List.vue页面组件中需要菜单组件,则可以导入Menu.vue。
src/components/Menu.vue,代码:
<script> export default { name: "Menu" } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
Home.vue引入Menu.vue的代码:

此时,页面的访问效果如下:

如果将来存在其他的页面组件也需要引入菜单组件,则可以按上面的流程作为参考。
例如:我们需要在App.vue中继续加载src/views/Register.vue注册页面组件。
src/views/Register.vue,代码:
<template> <h1>注册页面</h1> <Menu></Menu> </template> <script> import Menu from "../components/Menu" export default { name: "Register", components:{ Menu } } </script> <style scoped> </style>
注册页面组件Regiter.vue,导入Menu.vue菜单子组件。

如果希望浏览器能显示Register祖册页面组件,则可以采用App.vue导入Register.vue,代码:
<script setup> import List from "./views/List.vue" import Register from "./views/Register.vue"; </script> <template> <List></List> <Register></Register> </template> <style scoped> </style>

最终效果:

<script setup> import List from "./views/List.vue" import Register from "./views/Register.vue"; // 使用js提供的location对象,获取url地址的路径 // script声明的所有变量都会被自动填充到data中,可以直接在template模板标签中使用 let url = location.pathname; </script> <template> <List v-if="url==='/list'"></List> <Register v-if="url==='/reg'"></Register> </template> <style scoped> </style>


经过上面的代码处理以后,就实现了简单的路由分发了。当浏览器访问/list就会显示List页面组件,访问/reg则会显示Register页面组件。当然,现在这种直接判断的方式并不专业,我们将来会在vue项目中安装并使用vue-router这种专业的路由插件来完成项目组件和url地址的映射关联。
此时,整个项目的嵌套结构:

组件之间的数据传递
父组件的数据传递给子组件
把父组件的数据传递给子组件。可以通过props属性来进行数据传递。
传递数据的2个步骤:
- 
在父组件中调用子组件的组件名处,使用属性值的方式往子组件内部传递数据,src/views/List.vue 
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", data(){ return{ num:100, } }, components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <p><input type="text" v-model="num"></p> <Menu message="hello, 我是来自父组件List.vue的数据" :num="num"></Menu> </template> <style scoped> </style>
<script> export default { name: "Menu", //限制数据类型与格式 props:{ message:String, // 此处表示希望接收来自父组件的字符串变量title num:Number, } } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
不限制数据类型与格式
<script> export default { name: "Menu", //不限制数据类型与格式 props:["message","num"], // //限制数据类型与格式 // props:{ // message:String, // 此处表示希望接收来自父组件的字符串变量title // num:Number, } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
第三种
List
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", data(){ return{ num:100, } }, components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <p><input type="text" v-model="num"></p> <Menu message="hello, 我是来自父组件List.vue的数据" :num="num"></Menu> </template> <style scoped> </style>
Menu
<script> // 数据类型 // Number : 数值类型 // String: 字符串类型 // Object: 对象类型 // Boolean: 布尔类型 // Array: 数组类型 // Date: 日期 // Function: 函数类型 export default { name: "Menu", //不限制数据类型与格式 // props:["message","num"], //限制数据类型与格式 // props: { // message: String, // 此处表示希望接收来自父组件的字符串变量title // num: Number, // } //设置默认值 props:{ message:{ type:String, default: "默认值", }, num:{ type:Number, default: -1, } } } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
第四种方法
List
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", data(){ return{ num:100, } }, components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <p><input type="text" v-model="num"></p> <Menu message="hello, 我是来自父组件List.vue的数据" :num="num" :data="[1,2,3]"></Menu> </template> <style scoped> </style>
Menu.vue
<script> // 数据类型 // Number : 数值类型 // String: 字符串类型 // Object: 对象类型 // Boolean: 布尔类型 // Array: 数组类型 // Date: 日期 // Function: 函数类型 export default { name: "Menu", //不限制数据类型与格式 // props:["message","num"], //限制数据类型与格式 // props: { // message: String, // 此处表示希望接收来自父组件的字符串变量title // num: Number, // } //设置默认值 props:{ message:{ type:String, }, num:{ type:Number, default: -1, }, data: { type: Object, default:[] } } } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> <p>{{data}}</p> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>

第五种
List.vue
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", data(){ return{ num:100, } }, components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <p><input type="text" v-model="num"></p> <!-- <Menu message="hello, 我是来自父组件List.vue的数据" :num="num" :data="[1,2,3]"></Menu>--> <Menu message="hello, 我是来自父组件List.vue的数据" :num="num"></Menu> </template> <style scoped> </style>
Menu.vue
<script> // 数据类型 // Number : 数值类型 // String: 字符串类型 // Object: 对象类型 // Boolean: 布尔类型 // Array: 数组类型 // Date: 日期 // Function: 函数类型 export default { name: "Menu", //不限制数据类型与格式 // props:["message","num"], //限制数据类型与格式 // props: { // message: String, // 此处表示希望接收来自父组件的字符串变量title // num: Number, // } // 设置默认值或校验规则 props:{ message:{ type:String, }, num:{ type:Number, default: -1, validator(value){ // 校验父组件传递进行的数据,是否符合条件,符合返回true,不符合返回false if(value%2 === 0) { return true; } } }, data: { type: Object, default:[], required: true // 要求父组件必须传递的属性 } } } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> <p>{{data}}</p> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
步骤流程:

使用父组件传递数据给子组件时, 注意以下事项:
1. 传递数据是变量,则需要在属性左边添加英文冒号.
传递数据是变量,这种数据称之为"动态数据传递",父组件数据改动的时候,子组件中被随之改动。
传递数据不是变量,这种数据称之为"静态数据传递"
2. 来自props的数据,父组件中修改了数据,在子组件中会被同步修改。但是在子组件中如果修改了,因为不是data中声明的,所以父组件的数据不会发生变化,在开发时这种情况,也被称为"单向数据流"。
#### 子组件传递数据给父组件
在子组件中通过`this.$emit()`来声明一个自定义的信号(vue中称之为自定义事件),父组件中针对这个信号(事件源)在调用子组件的组件名处进行@监听并接收数据。
src/components/Menu.vue,代码:
<script> // 数据类型 // Number : 数值类型 // String: 字符串类型 // Object: 对象类型 // Boolean: 布尔类型 // Array: 数组类型 // Date: 日期 // Function: 函数类型 export default { name: "Menu", data(){ return{ text:"默认值", } }, watch: { text(){ //监听text如果发生变化,则往父组件传递数据 this.$emit("change_text", this.text); // 表示往父组件的change_text事件源传递1个数据,text变量 // 表示往父组件的change_text事件源传递2个数据,分别时100和text变量 // this.$emit("change_text",100,this.text()); } }, //不限制数据类型与格式 // props:["message","num"], //限制数据类型与格式 // props: { // message: String, // 此处表示希望接收来自父组件的字符串变量title // num: Number, // } // 设置默认值或校验规则 props:{ message:{ type:String, }, num:{ type:Number, default: -1, validator(value){ // 校验父组件传递进行的数据,是否符合条件,符合返回true,不符合返回false if(value%2 === 0) { return true; } } }, data: { type: Object, default:[], required: true // 要求父组件必须传递的属性 } } } </script> <template> <div class="menu"> <a href="">首页</a> <a href="">列表页</a> <a href="">会员中心</a> </div> <div> 子组件的内容的数据: <p>{{message}}</p> <p>num={{num}}</p> <p>{{data}}</p> </div> <div> 子组件中往父组件传递数据 <input type="text" v-model="text"> </div> </template> <style scoped> .menu{ height: 80px; overflow: hidden; } .menu a{ float: left; height: 80px; line-height: 80px; text-align: center; width: 140px; background: blue; color: #fff; } </style>
和子组件中this.$emit("自定义事件名称")对应,父组件中声明一个同样名称的事件属性。
src/views/List.vue,代码:
父组件中,声明一个方法用于在自定义事件发生时用于获取数据。
<script> // 导入组件 import Menu from "../components/Menu.vue"; export default { name: "List", data(){ return{ num:100, msg:"", } }, methods:{ get_text(data){ // 此处就是一个普通方法,但是因为被绑定到了子组件的事件源,所以需要接收参数 this.msg = data; } }, components:{ // 局部注册子组件 Menu, // "Menu": Menu, 可以简写 } } </script> <template> <h1>列表页</h1> <p><input type="text" v-model="num"></p> <!-- <Menu message="hello, 我是来自父组件List.vue的数据" :num="num" :data="[1,2,3]"></Menu>--> <Menu @change_text="get_text" message="hello, 我是来自父组件List.vue的数据" :num="num"></Menu> <div> <p>父组件接收来自子组件的数据,msg={{msg}}</p> </div> </template> <style scoped> </style>
练习:
1. 在父组件views/Goods.vue中的data选项中声明num1=10。传递到子组件components/Header.vue中显示num1的值出来。
   提示:通过props接收num1,类型是Number
2. 在子组件components/Header.vue中的data选项中声明num2=50,监听num2,当num2的值发生变化,则把num2的值传递到父组件views/Goods.vue中并在data保存并显示到templates中。
   提示:this.$emit("事件名", 变量1, 变量2...);
src/views/Goods.vue,代码:
<script> import Header from "../components/Header.vue"; export default { name: "Goods.vue", data(){ return{ num1:10, num:0, } }, methods:{ get_num2(data){ // 此处就是一个普通方法,但是因为被绑定到了子组件的事件源,所以需要接收参数 this.num = data; } }, components:{ Header, }, } </script> <template> <p>Goods.vue===>num1={{num1}},num={{num}}}</p> <input type="text" v-model="num1"> <Header :num="num1" @change_num2="get_num2"></Header> </template> <style scoped> </style>
src/App.vue,代码:
<script setup> import List from "./views/List.vue" import Register from "./views/Register.vue"; import Goods from "./views/Goods.vue"; // 使用js提供的location对象,获取url地址的路径 // script声明的所有变量都会被自动填充到data中,可以直接在template模板标签中使用 let url = location.pathname; </script> <template> <!-- <List v-if="url==='/list'"></List>--> <!-- <Register v-if="url==='/reg'"></Register>--> <Goods></Goods> </template> <style scoped> </style>
src/components/Header.vue,代码:
<script> export default { name: "Header.vue", props:["num",], data(){ return{ num2: 50, } }, watch: { num2(){ this.$emit("change_num2", this.num2); // 表示往父组件的change_text事件源传递1个数据,text变量 } }, } </script> <template> <div> <p>Header.vue接收来自父组件的数据,num={{num}}</p> <input type="text" v-model="num2"> </div> </template> <style scoped> </style>

 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号