本章学习目标:
- 了解Vue框架架构思想
- 掌握Vue核心指令
- 掌握计算属性与监视
- 掌握组件化开发模式
<script setup>
// 组合式API写法
script>
<script>
// 选项式写法
export default {
components:{},
data(){ return {}},
methods:{},
}
script>
1、VUE的优势:
2、Vue是什么?
3、VUE的引入步骤(三种方式)
4、创建一个vue应用
# 1、安装项目模板
npm create vue@latest
# 2、demo用,对话内容都选No
# 3、进入创建好的文件
cd vue-project
# 4、安装依赖
npm install
# 5、启动项目
npm run dev
打开项目后
- 打开App .vue(入口模板文件)
- 打开components目录,里面的所有.vue文件即为组件
- .vue文件即为vue模板组件,主要分为三大块script(交互脚本), template(HTML),style(样式)
- 清除内容,只留下script,template,style标签,看页面如何变化
<template>
<h1>hello world!h1>
template>
<script setup>
console.log("hello world!")
script>
<style scoped>
h1{
color:red;
}
style>
<template>
<div>
<p>定义的ref值:{{ refVal }}p>
<p>定义的reactive值:{{ obj.count }}p>
div>
template>
<script setup>
import {ref,reactive} from 'vue'
let refVal = ref("hello")
// 在template模板中会用ref对象的值时不需要.value,但在script中需要
console.log(refVal.value)
let obj = reactive({
count:1
})
script>
<style scoped>
style>
以下demo以vue3的composition API为例使用
v-if
: 条件渲染,显示与隐藏(布尔值)
v-show
:条件渲染
v-if与v-show的区别?
v-if通过值的真假来控制元素是否被渲染来实现显隐,而v-show通过css的display属性来控制实现显示与隐藏。v-if元素每一次触发都会引起重构。
<template>
<div>
<div v-if="type === 'A'">Adiv>
<div v-else-if="type === 'B'">Bdiv>
<div v-else-if="type === 'C'">Cdiv>
<div v-else>Not A/B/Cdiv>
div>
template>
<script setup>
// 声明一个变量
let type ='B'
script>
<style scoped>
style>
- 基于原始数据多次渲染元素或模板块
- 支持遍历的原始数据可以是:Array | Object | number | string | Iterable
<template>
<div>
<div>
<span v-for="(item,index) in arr" :key="index">{{item}},span>
div>
<div>
<span v-for="(value,key) in obj" :key="key">{{key}}-{{value}},span>
div>
div>
template>
<script setup>
let arr = ['apple','banana','peach','cherry']
let obj={
"name":"张三",
"age":"18",
"sex":"男"
}
script>
<style scoped>
style>
<template>
<div>
<div v-text="textStr">div>
<div v-html="htmlStr">div>
div>
template>
<script setup>
let textStr = "这是一段文本,html内容不会被解析为html.如h1标签
"
let htmlStr = "这是html内容,可以被解析
"
script>
<style scoped>
style>
.stop
- 阻止冒泡,调用 event.stopPropagation()。.prevent
- 阻止默认行为,调用 event.preventDefault()。.capture
- 在捕获模式添加事件监听器。.self
- 只有事件从元素本身发出才触发处理函数。.{keyAlias}
- 只在某些按键下触发处理函数。.once
- 最多触发一次处理函数。.left
- 只在鼠标左键事件触发处理函数。.right
- 只在鼠标右键事件触发处理函数。.middle
- 只在鼠标中键事件触发处理函数。.passive
- 通过 { passive: true } 附加一个 DOM 事件。<template>
<div>
<button v-on:click="onclick1">普通点击button>
<div v-on:click="testPropogation">
防止冒泡点击,使用@别名绑定
<button @click.stop="onclick2">防冒泡点击button>
div>
<div @click.capture="captureHandler">
captureHandler:捕获触发click,点button,先触发captureHandler,再必testCapture
<button @click="testCapture">testCapturebutton>
div>
<a href="https://www.baidu.com" @click.prevent="preventHandler">点击不跳转a>
<button @click.once="onceHandler">只响应一次,再点没用button>
<div>
按下enter触发
<input @keyup.enter="onEnter" />
div>
div>
template>
<script setup>
function onclick1(){
alert("普通点击")
}
// 冒泡测试
function onclick2(){
alert("防冒泡点击")
}
function testPropogation(){
alert("点击div或冒泡触发")
}
// 捕获测试
function captureHandler(){
alert("点了外层div")
}
function testCapture(){
alert("捕获触发,或者直接点击按钮触发")
}
// prevent测试
function preventHandler(){
alert("preventHandler")
}
// .once测试
function onceHandler(){
alert("once事件触发,再点没用")
}
// .{keyAlias}测试
function onEnter(){
alert("按下enter键")
}
script>
<style scoped>
style>
:
<template>
<div>
<img style="width:100px" v-bind:src="imgSrc"/>
<img :style="{width:'200px',height:'100px',marginLeft:'20px'}" v-bind:src="imgSrc"/>
<div :class="classArr" >绑定class数组div>
div>
template>
<script setup>
let imgSrc = "http://imgsrc.baidu.com/baike/pic/item/ac345982b2b7d0a25cd01174cbef76094b369a39.jpg"
// 绑定class
let classArr = ['w100','bg-blue']
script>
<style >
.w100{
width: 100px;
height: 100px;
}
.bg-blue{
background: blue;
}
style>
<template>
<div>
输入--------》
<input type="text" v-model="inputVal">
双向绑定显示--------》
<div>
inputVal: {{inputVal}}
div>
div>
template>
<script setup>
// 使用 ref() 函数来声明响应式状态
import {ref,reactive} from "vue"
let inputVal=ref("aaa")
// ref对象通过.value来获取其值
console.log(inputVal.value)
script>
<style>
style>
- 对于复杂的变量值(需要进一步通过逻辑处理返回的值)推荐通过计算属性来定义
- 返回值:一个计算属性 ref,但计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
- 计算属性会自动追踪响应式依赖
- computed函数参数:默认为一个getter函数,函数返回值即为想要的计算结果。传入包括get,set函数的对象可设置读写操作
<template>
<div>计算属性1(默认只可读):{{ authorMsg }}div>
<div>计算属性2:{{fullName}}div>
template>
<script setup>
// ref() 函数来声明响应式状态
// computed() 函数来声明计算属性
import { ref,computed } from "vue";
let author = {
name:"张三",
fruits:["apple","banana","peach","cherry"]
}
// 示例1:默认传入一个getter函数
let authorMsg = computed(()=>{
let authorName = author.name
let fruitsStr = author.fruits.map(item=>item+',')
return `${authorName}喜欢吃的水果有:${fruitsStr}`
})
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
let [first, last] = newValue.split("")
firstName.value=first
lastName.value =last
}
})
setTimeout(() => {
fullName.value="王五"
}, 2000);
script>
<style>
style>
- 概述:侦听器,在侦听的某个响应式状态发生变化时,触发其回调函数
- 应用场景:根据异步操作的结果去修改另一处的状态
- 语法:watch(数据源, 回调函数)
- 数据源: ref (包括计算属性)、一个响应式对象(reactive)、一个 getter 函数、或多个数据源组成的数组
- 深层侦听属性:deep: true
- 立即回调属性:immediate: true
- 回调触发时间: 默认情况下,回调会在vue组件更新之前调用,因此若在回调中访问DOM会是更新前的状态,所以尽量避免在watch回调中访问dom。如果想在回调中访问Vue更新之后 的DOM,需要添加属性 {flush: ‘post’}
watch(source, callback, {
deep: true, // 深层侦听
immediate: true, // 立即执行一次回调
flush: 'post', // 访问Vue更新之后的DOM
})
<template>
<div>
<div @click="randomChange('x')">触发x变化div>
<div @click="randomChange('y')">触发y变化div>
<div @click="() => (obj.count = 10)">reactive值变化div>
div>
template>
<script setup>
import { ref, watch, reactive } from "vue";
const x = ref(0);
const y = ref(0);
// 触发变化函数
function randomChange(name) {
let randomNum = Math.random() * 10;
if (name == "x") {
x.value = randomNum;
}
if (name == "y") {
y.value = randomNum;
}
}
// 1、监听单个 ref
watch(x, (newX) => {
console.log(`例1:监听单个ref,x is ${newX}`);
});
// 2、监听getter 函数,x,y的变化都会触发
watch(
() => x.value + y.value,
(sum) => {
console.log(`例2:getter函数监听变化,sum of x + y is: ${sum}`);
}
);
// 3、多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`例3:多来源数组监听,x is ${newX} and y is ${newY}`);
});
// 4、监听响应式对象
const obj = reactive({ count: 0 });
// 错误写法,因为 watch() 得到的参数是一个 number
// watch(obj.count, (count) => {
// console.log(`count is: ${count}`)
// })
// 正确写法
watch(
() => obj.count,
(count) => {
console.log(`例4、监听reactive响应式对象,count is: ${count}`);
}
);
script>
<style>
style>
watch(source, (newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
// deep: 深度监听
// immediate:立即执行,且当 `source` 改变时再次执行
}, { immediate: true ,deep:true})
watchEffect使用场景:有多个依赖项需要侦听,或需要侦听一个嵌套数据结构中的几个属性时,watchEffect() 比深度侦听器更有效
- 区别:
watch
只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西.,且仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖watchEffect
,会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。computed
,必须有return返回,使用方法与data中的数据一样,如果函数所依赖的属性没有变化,从缓存中读取
1、参考vue-project项目生成目录,其中components就是约定的组件存放目录,在components中新建Child.vue组件如下
<template>
<div >
<h1>{{ msg }}h1>
div>
template>
<script setup >
import {ref, reactive} from "vue"
let msg = ref("这是Child")
script>
<style scoped>
style>
2、父组件中引入。(以App.vue为例,此处以setup语法糖方式编写,只需引入Child.vue就会自动注册该组件。若在optionsApi中需要显示注册components)
<template>
<div class="">
这是Parent
<p>------------------------分隔线------------------p>
<Child />
div>
template>
<script setup lang="ts">
// 引入组件
import Child from './components/Child.vue'
import {ref, reactive} from "vue"
script>
1、父传子props
<template>
<div >
<h1>{{ parentMsg }}h1>
div>
template>
<script setup >
// 1. 引入defineProps宏
import { defineProps} from "vue"
// 2.定义props
defineProps({
parentMsg: {type: String}
})
script>
<template>
<div class="">
这是Parent
<p>------------------------分隔线------------------p>
<Child :parentMsg="toChild"/>
div>
template>
<script setup lang="ts">
import Child from './components/Child.vue'
import {ref} from "vue"
let toChild = ref("来自Parent的消息")
script>
2、子传父
<template>
<div class="">
这是Parent
<p>------------------------分隔线------------------p>
<Child @sayHi="hiHandler"/>
div>
template>
<script setup lang="ts">
import Child from './components/Child.vue'
// 4.接收子组件抛出事件的参数msg
function hiHandler(msg){
alert(msg)
}
script>
<template>
<div >
<h1 @click="clickHandler">点击传给Parenth1>
div>
template>
<script setup >
// 1. 引入defineEmits宏
import { defineEmits } from "vue"
// 2.定义组件会抛出的事件
const emit = defineEmits(['sayHi'])
function clickHandler(){
console.log("child 中触发点击")
// 3.触发抛出事件
emit('sayHi','来自Child的消息')
}
script>
3、py: provice/inject
如果是祖辈组件与孙辈组件间传值,可使用provide、inject
- provide(key,value), 必须在组件的setup()阶段同步调用
- inject(key,defaultValue) : 注入一个由祖先组件或整个应用(通过app.provide())提供的值,defaultValue可选,当没匹配到key对应的值时显示
<template>
<div class="">
这是App.vue,子组件为Middle,孙组件为Child(由middle包含)
<p>------------------------分隔线------------------p>
<Middle />
div>
template>
<script setup lang="ts">
import Middle from './components/Middle.vue'
import {provide,ref} from 'vue'
let count = ref(0)
// 提供静态值
provide("appName",'测试Demo')
// 提供响应值
provide("count",count)
script>
<template>
<div >
这是隔代组件,内部引入Child组件
<Child />
div>
template>
<script setup >
import Child from './Child.vue'
script>
<template>
<div >
这是Child.vue内容
<p>来自app.vue的appName: {{ appName }}p>
<p>这是app.vue的count: {{ count }}p>
div>
template>
<script setup >
import {inject} from 'vue'
let appName = inject("appName")
let count = inject("count")
script>
在vue3 compositionAPI中,生命周期钩子函数都有on前缀,常用如onMounted,onUpdated,onUnmounted(),onActived()
- onMounted(): 组件挂载完成后执行
- onUpdated(), 在组件因为响应式状态变更而更新其 DOM 树之后调用
- onUnMounted(), 在组件实例被卸载之后调用。
- onActived(),若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。
<template>
<div >
{{ count }}
div>
template>
<script setup lang="ts">
import {ref, onMounted,onUpdated} from "vue"
let count = ref(0)
setTimeout(()=>{
count.value++
console.log("count改变将触发onUpdated")
},2000)
onMounted(()=>{
console.log("onMounted")
})
onUpdated(()=>{
console.log("onUpdated")
})
script>
官网地址:https://router.vuejs.org/zh/, 在创建单页应用项目时脚手架会提醒是否安装vue router,VueRouter作为前端路由库,控制整个Vue项目的页面跳转服务。以下示例介绍vue-router集成到vue项目中的使用
1、安装vue router
npm install vue-router@4
2、定义路由
- 项目目录中新建router文件夹,并新建index.js
// router/index.js
import {createRouter,createWebHashHistory} from 'vue-router'
// 1、定义路由组件(实际应用中通过导入.vue文件组件)
const Home = ()=>import("@/pages/Home.vue")
const About = ()=>import("@/pages/About.vue")
// 2、定义路由
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 3. 创建路由实例并传递 `routes` 配置
const router = createRouter({
history:createWebHashHistory(), // 以hash模式创建路由
routes,
})
export default router
3、挂载路由到Vue实例中
- 打开项目src/main.js文件
// main.js
import './assets/main.css'
import router from '@/router'
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例
const app = createApp(App)
// 整个应用支持路由
app.use(router)
// 挂载整个应用
app.mount('#app')
4、页面中通过router-view渲染
<template>
<div>
这是App.vu
<p>------------------------分隔线------------------p>
<router-link to="/">Go to Homerouter-link> <br>
<router-link to="/about">Go to Aboutrouter-link>
<div class="router-page" style="border:1px solid #ddd;padding:10px;">
<router-view>router-view>
div>
div>
template>
<script setup >
script>
5、路由跳转及传参
在vue3中使用组合式API编写router业务时,参考文档:https://router.vuejs.org/zh/guide/advanced/composition-api.html
<script setup >
import {useRouter, useRoute } from 'vue-router'
import {onMounted} from 'vue'
const router = useRouter()
const route = useRoute()
function goAbout(){
// 路由跳转
router.push({path:'/about',query:{name:'lala'}})
}
onMounted(()=>{
console.log(route.path)
})
console.log(router)
</script>
官网地址: https://www.npmjs.com/package/axios#axios-api
axios是一个基于Promise的http客户端请求工具,可以在浏览器或者node.js中使用。
1、安装
npm install axios --save
2、src目录下创建api文件夹,简单封装
- 一般对axios进行简单封装,使用统一域名,请求拦截,响应拦截,设置超时时间等
// src/api/http.js
import axios from 'axios'
const request = axios.create({
baseURL: 'https://www.xxx.com', // 后端服务地址
timeout: 120000, // 超时时间
})
// 请求拦截器,用于在发出请求前,做一些操作
request.interceptors.request.use(config=>{
// 假设本地有token
if(localStorage.getItem("token")){
config.headers.token = token
}
return config
})
// 响应拦截器,用于处理后端报错等
request.interceptors.response.use(response=>{
if(response.status!==200){
console.log("请求响应服务出错了,具体再看")
}
return response
})
export default request
// src/api/login.js
import request from './http'
const Api = {
getCode:'/api/v1/getCode',
login:'/api/v1/login'
}
// 假设获取验证码
export function getCode(params){
return request.get(Api.getCode, params)
}
// 登录
export function login(params){
return request.post(Api.login, params)
}
3、组件间使用(伪代码示例用)
- pages/login.vue
<template>
<div >
这是登录页
div>
template>
<script setup >
import {getCode,login} from "@/api/login"
import {ref, onMounted} from "vue"
// 调用请求
let params = {
name:'zhangsan',
password:'123455'
}
login(params).then(res=>{
console.log(res)
})
script>