script setup
在 setup() 函数中手动暴露大量的状态和方法非常繁琐,我们可以使用 script setup 来大幅度地简化代码
<template>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>
data
<script setup>
import { reactive,ref } from 'vue'
// 无响应式
let name = '张三'
// reactive() 函数创建一个响应式对象或数组
const state = reactive({ count: 0 })
// ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
const onClick1 = () => {
state.count++
}
//
const onClick2 = () => {
count.value++
}
</script>
<template>
<button @click="onClick1">
{{ state.count }}
</button>
<button @click="onClick2">
{{ count }}
</button>
</template>
method
<template>
{{count}}
<button @click='onClick'>按钮</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
// 声明method方法
const onClick = () => {
count.value++
}
</script>
computed
计算属性的出现是为了减少模板中出现冗余代码 computed和method做同样操作可以得到完全一样的结果,区别在于计算属性值会基于其响应式依赖被缓存
- 用法一:只读
<template>
<h1>{{ fullName }}</h1>
</template>
<script setup>
import { ref , computed } from 'vue'
const firstName = ref('Hello')
const lastName = ref('World!')
const fullName = computed(()=>{
return firstName.value + '-' + lastName.value
})
</script>
computed 可以实现过滤器功能
<script setup>
import { reactive,computed } from 'vue'
const data = reactive({
items: [
{
id: 0,
title: 'Item A',
list: 0
},
{
id: 1,
title: 'Item B',
list: 0
},
{
id: 2,
title: 'Item C',
list: 1
}]
})
console.log(data);
const listLeft = computed(()=>{
return data.items.filter(item => item.list === 1) // 将items数组中,过滤出list为1的,即id为2的项
})
const listRight = computed(()=>{
return data.items.filter(item => item.list === 0) // 将items数组中,过滤出list为0的
})
console.log(listLeft.value)
console.log(listRight.value)
</script>
- 用法二:可写
<template>
<h1>{{ sum }}</h1>
<button @click="sum=10">click</button>
</template>
<script setup>
import { ref , computed } from 'vue'
const count = ref('100')
const sum = computed({
// getter 读取时调用
get(){
return count.value * 2
},
// setter 写入时调用
set(newValue){
return count.value = newValue // 点击按钮 newValue==10
}
})
</script>
watch
- 监听 ref
<script setup>
import { ref, watch } from "vue"
const count = ref(0)
watch(
count, // 注意这里变化
(val,old) => {
console.log('val',val);
console.log('old',old);
}
)
</script>
- 监听 reactive
<script setup>
import { reactive, watch } from "vue"
const data = reactive({ count:0 })
watch(
() => data.count, // 区别点
(val,old) => {
console.log('val',val);
console.log('old',old);
}
)
- 开启深度监听和立即执行
深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大,谨慎使用!
<script setup>
import { reactive, watch } from "vue"
const data = reactive({ count:0 })
watch(
() => data.count,
(val,old) => {
console.log('val',val);
console.log('old',old);
},
{
deep: true, // 深度监听
immediate:true // 立即执行
}
)
</script>
动态样式
-
:class
<!-- 1.对象方法 --> <div class="class1" :class="{class2:isShow,class3:true}">HelloWorld</div> <!-- 2.三元表达式方法 --> <div class="class1" :class="isShow ? 'class2' : 'class3'">HelloWorld</div> <!-- 3.数组方法 --> <div class="class1" :class="[class1,class2,class3]">HelloWorld</div>
-
:style
<!-- 1.对象方法 --> <div :style="{ color: activeColor ,fontSize:size+'px'}">HelloWorld</div> <!-- 2.三元表达式方法 --> <div :style="{ color:(activeColor ? 'red' : 'blue')">HelloWorld</div> <!-- 3.数组方法 --> <div :style="[styles1,styles2]">HelloWorld</div>
css支持v-bind 指令
<template>
<div class="box">{{color}}</div>
</template>
<script setup>
import {ref} from 'vue'
let color = ref('red')
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
生命周期
setup中的生命周期和Vue3.0使用没有区别
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {})
onUnmounted(() => {})
</script>
Props
- 子组件接收
在使用
<script setup>
的单文件组件中,props 可以使用 defineProps() 宏来声明defineProps可以不导入,不过eslint可能报错
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,会自动更新为最新的值,而不会逆向传递。 注意:不要在子组件修改prop;如果需要操作prop,可以在子组件先将prop值取出,存储到新变量中
<script setup>
const props = defineProps(['foo'])
// 设置类型与默认值
const props = defineProps({
name: {
type: String,
default: ''
}
})
console.log(props.foo)
</script>
*************************************
// 也可以直接使用
defineProps({
name: {
type: Boolean,
default: false
},
close: {
type: Function,
default: fun => fun()
}
})
<div>{{ title }}</div>
<div @click="close(handleClose)">取消</div>
- 父组件传递
<template>
<child :name='张三'/>
</template>
<script setup>
// 引入子组件,组件会自动注册
import child from './child.vue'
</script>
emit
- 子组件
在使用
<script setup>
的单文件组件中,emit 可以使用 defineEmits() 宏来声明
<template>
<button @click='onClick'>更名</button>
</template>
<script setup>
const emit = defineEmits(['changeName'])
const onClick = () => {
// 第二次参数开始是需要传递的数据
emit('changeName', 'Tom')
}
</script>
**************************
// 语法糖形式
const emit = defineEmits(['update:show'])
const handleClose = () => {
emit('update:show', false)
}
<div @click="close(handleClose)">取消</div>
- 父组件
<template>
<child :name='state.name' @updateName='updateName'/>
</template>
<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'
const state = reactive({
name: 'Tom'
})
// 接收子组件触发的方法与参数
const updateName = (name) => {
state.name = name
}
</script>
********************
// 语法糖形式
<child v-model:show="show"/>
<script setup>
let show = ref(true)
</script>
在 Vue.js 中,父子组件之间的通信通常是通过 props 来实现的。当父组件中的数据发生改变时,它会向子组件传递新的 props,从而触发子组件的重新渲染。
当使用 $emit 方法向父组件传递对象时,如果直接将这个对象作为参数传递给 $emit,那么父组件中的数据并不会发生改变,也就失去了响应式。这是因为 Vue 是通过观察对象中的每一个属性来实现响应式的,如果直接将整个对象作为参数传递给 $emit,那么 Vue 无法监听到对象内部属性的改变。
为了解决这个问题,你需要在调用 $emit 方法时,将对象中的每一个属性分别作为参数传递给 $emit,这样 Vue 才能监听到每一个属性的变化,并触发父组件的重新渲染。
nextTick
等待下一次 DOM 更新刷新的工具方法
<script setup>
import { nextTick } from 'vue'
nextTick(() => {
// ...
})
</script>
defineExpose
使用
<script setup>
的组件是默认关闭的(即子组件的数据,父组件无法通过ref获取子组件数据);可以通过 defineExpose 编译器宏来显式指定在
<script setup>
组件中要暴露出去的属性
- 子组件
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
- 父组件
<template>
<child ref='childRef'/>
</template>
<script setup>
import { ref, nextTick } from 'vue'
// 引入子组件
import child from './child.vue'
// nextTick
nextTick(() => {
// 获取子组件值
console.log(childRef.value.a)
console.log(childRef.value.b)
})
</script>
slot
- 子组件
<template>
// 匿名插槽
<slot/>
// 具名插槽
<slot name='header'/>
// 作用域插槽
<slot name="footer" :scope="state" />
</template>
<script setup>
import { useSlots, reactive } from 'vue'
const state = reactive({
name: '张三',
age: '25岁'
})
</script>
- 父组件
<template>
<child>
// 匿名插槽
<div>默认插槽</div>
// 具名插槽
<template #header>
<div>具名插槽</div>
</template>
// 作用域插槽
<template #footer="{ scope }">
<footer>作用域插槽,{{ scope.name }}{{ scope.age }}</footer>
</template>
</child>
</template>
<script setup>
// 引入子组件
import child from './child.vue'
</script>
useSlots() 和 useAttrs()
这两个辅助函数可以在
<script setup>
使用 slots 和 attrs但是一般是直接通过 attrs 来访问它们
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
顶层 await
<script setup>
中可以使用顶层 await。结果代码会被编译成 async setup(),不必再写async
<script setup>
const post = await fetch(`/api/xxx`).then(() =>{})
</script>
路由
setup 里面没有访问 this,所以我们不能再直接访问
this.$router
或this.$route
。作为替代,我们使用 useRouter 函数
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 必须先声明调用
const route = useRoute()
const router = useRouter()
// 路由信息
console.log(route.query)
// 路由跳转
router.push('/home')
router.push({
name: 'home',
query: {
...route.query,
},
})
</script>
vuex
定义数据
src/store
文件夹下新建modules
文件夹
modules
文件夹下新建user.js
文件
import { getUname } from '@/api/user'
// 用户
export default {
// 开启命名空间
namespaced: true,
state: {
info: {
uname: 'Leo',
age: 21
},
user_name:''
},
mutations: {
updateUname(state, val) {
state.info.uname = val
},
updateAge(state, val) {
state.info.age = val
},
UPDATE_UNAME(state, val) {
state.user_name = val
}
},
actions: {
asyncUpdate(store, val) {
setTimeout(() => {
store.commit('updateAge', val)
}, 2000)
}
// 发起网络请求
getUname({ commit, state }, val) {
return new Promise((resolve, reject) => {
getUname().then(response => {
if (response.status == 1) {
commit('UPDATE_UNAME', response.name)
resolve()
}
}).catch(error => {
reject(error)
})
})
}
********************
// 另一种形式
getUname(context, val) {
context.commit('updateUname',val)
}
}
}
src/store
文件夹下创建getter.js
const getters = {
user_name: state => state.user.user_name
}
export default getters
src/store
文件夹下的index.js
文件中引入上面两个文件
import { createStore } from 'vuex'
import getters from './getters'
import user from './modules/user.js'
export default createStore({
getters,
modules: {
user
}
})
使用
<template>
<div>同步修改state:{{ $store.state.user.info.uname }}</div>
<p>-----------------------------------------------</p>
<div>异步修改state:{{ $store.state.user.info.age }}</div>
<p>-----------------------------------------------</p>
<div>getters数据:{{ $store.getters['user/format'] }}</div>
<button @click="handleClick">修改</button>
</template>
<script>
import { useStore } from 'vuex'
export default {
name: 'App',
setup() {
const store = useStore()
const handleClick = () => {
console.log(store)
store.commit('user/updateUname', 'Tom')
store.dispatch('user/asyncUpdate', 23)
store.dispatch('user/getUname', 'zhangsan')
}
return { handleClick }
}
}
</script>
- 在src下的api定义接口请求
import request from './request'
export const getUname = (data) => {
return request({
url: '/xxx/xxx',
method: 'POST',
data
})
}
vue-router
router-view
顾名思义: 路由视图,它实际上展示的是路由下面的子路由
- router.js
routes: [
{
path: '/',
name: 'HelloWorldA',
component: HelloWorldA,
},
{
path: '/helloWorldB',
name: 'HelloWorldB',
component: HelloWorldB,
children: [{
path: '',
name: 'A',
component: B
}, {
path: 'b',
name: 'B',
component: B
},
]
}
]
- App.vue
展示的是 HelloWorldA
<template>
<router-view />
</template>
- HelloWorldA.vue
<template>
<router-link to="HelloWorldB">跳转到 HelloWorldB</router-link>
</template>
- HelloWorldB.vue
这里也有router-view,这里展示的是路由HelloWorldB的子路由,默认展示路由HelloWorldB下的A页面
<template>
这是HelloWorldB
<router-view />
</template>
路由跳转
- router.js
const routes = [
{
path: '/',
name: 'A',
component: A
},
{
path: '/B',
name: 'B',
component: () =>
import ('../views/B.vue')
}
]
- 不传参跳转
通过router.push({})实现路由跳转
<template>
<div @click="goToB">Page A</div>
</template>
<script setup>
import { useRouter } from "vue-router";
export default {
const router = useRouter();
const goToB = () => {
router.push({
name: "B",
});
}
};
</script>
- 传参跳转
<template>
<div @click="goToB">Page A</div>
</template>
<script setup>
import { useRouter } from "vue-router";
export default {
const router = useRouter();
const goToB = (value) => {
router.push({
name: "B",
params:{
value: value
}
});
}
};
</script>
- 接收参数
注意区分 useRouter 和 useRoute 的区别,一个末尾有 -r(用于跳转),一个没有(用于获取参数)
<template>
<div>Page B</div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useRoute } from "vue-router";
export default {
const route = useRoute();
const getParams = () => {
return route.params;
};
onMounted(() => {
console.log("mounted:" + getParams().value);
});
};
</script>
- 总结
传参
import { useRouter } from 'vue-router'
const router = useRouter()
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?userId=123
router.push({ path: 'register', query: { userId: '123' }})
接收参数
import { useRoute } from 'vue-router'
//query
let userId=route.query.userId;
//params
let userId=route.params.userId;
- 补充
query和params区别 这两个都是跳转url的时候传递参数。
query可以用name和path来传递,但是params只能用name来传递,query是直接在url中,页面刷新后url传递的参数依旧在,params在刷新跳转页面的时候就没有了,下面实现一下具体的代码。
query url 格式:xxx.com/product?id=123 params url 格式:xxx.com/product/123
优劣:
- 动态路由, 优点 ,好看整齐 缺点 必须预先定义, 如果参数多起来多起来不好管控
- 问号的形式 灵活随意想改就改, 想加就加, 缺点就是太丑陋了, 也不直观
全局挂载属性
vue2中使用的方式
//在main.js文件
import Vue from 'vue';
import axios from 'axios';
Vue.prototype.$axios = axios;
//在其它组件使用,直接用this调用
this.$axios
vue3中使用的方式
- 依赖注入 provide 和 inject 来全局挂载(推荐使用)
import { createApp } from 'vue'
import App from './App.vue'
import * as echarts from 'echarts';
let app = createApp(App);
// 全局挂载echarts
app.provide('$echarts',echarts);
app.mount('#app')
调用
<script setup>
import { inject, onMounted } from "vue";
//导入挂载的echarts
const echarts = inject("$echarts");
onMounted(() => {
let myChart = echarts.init(document.getElementById('main'));
console.log(myChart);
});
</script>
- vue3组合式里面(不推荐)
vue3全局挂载属性方式要通过globalProperties 官方不推荐这样使用,这样就当作vue2中的组合式API一样用this了
//vue3 main.js文件
import { createApp } from 'vue'
import App from './App.vue'
import * as echarts from 'echarts';
let app = createApp(App);
//全局挂载echarts属性
app.config.globalProperties.$echarts = echarts;
app.mount('#app')
调用
<script setup>
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance();
//调用
proxy.echarts
</script>
- 挂载到window
const app = createApp(App)
const im = im
window.$im = im
//调用
$im;
ref 获取子组件实例
<template>
<div ref="root">This is a root element</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const root = ref(null)
onMounted(() => {
// DOM 元素将在初始渲染后分配给 ref
console.log(root.value) // <div>This is a root element</div>
})
</script>