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标签有且只能有一个。在vue2.0版本时代,template还必须只有有且只有一个子标签

<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,并提供1个页面组件List.vue,List.vue作为页面组件,同时在src/components创建一个子组件Menu.vue

 

在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>

 

 

最终效果:

 

上面虽然显示了注册页面的效果,但是同时也显示了List.vue页面组件的内容,在开发中一般不同的url地址应该显示不同的页面效果了。所以就需要实现url地址与页面组件之间的绑定映射关系了,这就是路由管理。那么,将来我们可以使用vue-router路由组件来完成,但是现在当前项目我们并没有安装这个插件,所以我们可以使用原生的js代码来简单实现路由管理功能。

路由的本质就是根据不同url地址判断显示对应的页面组件。src/App.vue,代码:

 

<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个步骤:

  1. 在父组件中调用子组件的组件名处,使用属性值的方式往子组件内部传递数据,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>

 

在子组件src/components/Menu.vue中接收父组件传递进来的数据,需要在script标签中,使用props属性接收。写法有3种。

代码:

第一种写法

<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>

 

 

posted @ 2025-03-14 02:25  minger_lcm  阅读(143)  评论(0)    收藏  举报