学习vue的日常啦~~
Vue2
首先引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
1. 主要结构
<script>
let v = new Vue({
el: '#root', // 第一种关联方法
data: { // 第一种写法对象式
message:'hello,world'
}
data(){// 第二种函数式子 this是实例对象,不可以用箭头函数
console.log(this)
return {
message:''
}
}
methods: { // 在methods里写方法
click1(){
alert(1)
},
},
computed:{
计算属性名(){
基于现有数据编写求职逻辑
return 结果
}
}
created() { // 生命周期函数直接写进去
console.log('created')
},
updated() {
console.log('updated')
},
});
v.$mount('#root') // 另一种方式绑定, 第二种关联方法(挂在上面)
</script>
补充:
- export default
// 导出
export const router = new VueRouter({ ... });
export default router; // 用default在一个模块中只能用一次,并且导入的时候不需要大括号
// 导入
import router from './router'; // 导入的时候不需要大括号
- export
// 导出
export const router = new VueRouter({ ... })
export { router }; // 两者都可以,export可以多次使用
// 导入
import { router } from './router'; //导入的时候需要大括号
2. 基本语法
2.1 插值语法
用于解析标签体内容,写法:{{}} 两个大括号内的东西是js表达式(可得到返回值)!不是js语句,可以直接读取到data中的所有属性
<div class="fr">
{{message}} //
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14">
</script>
<script>
let v = new Vue({ //必须实例化,有配置对象
el:'.fr', // 第一种关联方法
data: { // 第一种写法对象式
message:'hello,world'
}
data(){ // 第二种函数式子 this是实例对象,不可以用箭头函数
return {
message:'hello,world'
}
}
methods: {
click1(){
alert(1)
},
},
});
v.$mount('.fr') // 第二种关联方法(挂在上面)
</script> // 容器与实例一一对应
2.2 指令语法
用于解析标签,同样是需要些表达式
* v-html
动态的修改innerhtml
<div id="root" v-html="msg">
</div>
<script>
let v = new Vue({ //必须实例化,有配置对象
el:'#root', // 第一种关联方法
data:{
msg: 'dadas'
}
});
</script>
* v-bind
将Vue实例中的数据和方法与HTML元素的属性进行绑定,
<div class="fr">
<a v-bind:href="href.toUpperCase()">悬浮</a>
<a :href="href">悬浮</a> // v-bind可简写为:
</div>
<script>
let v = new Vue({
el:'.fr',
data:{
href:'https://www.baidu.com/',
}
})
</script>
v-bind对于样式控制增强
- :class=“{类名1:布尔值,类名2:布尔值}” 适用于一个类名来回切换
- :class=“[类名1,类名2]” 适用于批量添加或删除类
- :style=“{CSS属性名1:CSS属性值,CSS属性名2:CSS属性值}”
补:在js中background-color这种带横杠的属性名不支持,有两种写法,一个是加引号‘background-color’,另一个是驼峰写法backgroundColor
* v-model
将表单输入框的内容同步给 JavaScript 中相应的变量,双向绑定
<div id="fr">
输入:<input type="text" v-model="message" > {{message}}
输入:<input type="text" v-model:value="message" > {{message}}
</div> // 由于v-model:value本身就是为了获取值,可以缩写为v-model
<script>
let v = new Vue({
el: '#fr',
data:{
message:'1'
}
});
</script>
<div id="fr">
性别:
<input type="radio" name="sex" v-model="message" value="男">男
<input type="radio" name="sex" v-model="message" value="女">女
<br>
我选择的是:{{message}}
</div>
<script>
let v = new Vue({
el: '#fr',
data:{
message:''
}
});
</script>
不仅是输入框,复选框,文本域,下拉菜单都可以,但都要设定value值,本质是在获取value
* v-on
事件处理
<button @mouseover="count--" @mouseleave="count--">-</button>
<span>{{count}}</span>
<button @mouseenter="count++" @mouseleave="count++">+</button> // 也可以直接写内联语句在里面
<div class="root">
<button v-on:click="click1(66)">点击1</button> // 第一种写法 66 如果只写click1函数则默认有一个点击事件
<button @click="click2($event,5)">点击2</button> // 第二种写法v-on->@ 同时$event为点击事件的占位符
<button @click="clk(flag)">点击</button>
<h1 v-show="flag">lzd</h1>
</div>
<script>
let v = new Vue({
el:'#root',
data:{
flag:true
},
methods: {
click1(num){
alert(num)
},
click2(e,num){
console.log(e.target.innerHTML)
console.log(num)
},
clk:function(flag){
this.flag = !this.flag
}
},
});
</script>
* v-if
条件渲染,本质上是在创建和销毁
<div class="fr">
<p v-if="ok">yes</p>
<p v-else>no</p>
</div>
<script>
let v = new Vue({
el:'.fr',
data:{
ok:false
}
})
</script>
else if:
<div class="fr">
<p v-if="type==='a'">a</p>
<p v-else-if="type==='b'">b</p>
<p v-else-if="type==='c'">c</p>
<p v-else>other</p>
</div>
<script>
let v = new Vue({
el:'.fr',
data:{
type:'d'
}
})
</script>
* v-show
与v-if基本一直但是本质上修改display: none;
* v-for
一定要绑定上key,并且要用唯一的id,不能用index下标值,否则会出现奇怪bug
<div class="fr">
<ul>
<li v-for="ele in fruits" :key="ele.id">
<span>{{ele.name}}</span>
<span>{{ele.author}}</span>
<button @click="clk(ele.id)">删除</button>
</li>
</ul>
</div>
<script>
let v = new Vue({
el:'.fr',
data:{
flag:true,
fruits:[
{id:1,name:'《红楼梦》',author:'曹雪芹'},
{id:2,name:'《西游记》',author:'吴承恩'},
{id:3,name:'《水浒传》',author:'施耐庵'},
{id:4,name:'《三国演义》',author:'罗贯中'}
]
},
methods: {
clk:function(id){
this.fruits = this.fruits.filter(ele=>ele.id!==id)
}
},
})
</script>
2.3 指令的修饰符
- 按键修饰符:@keyup.enter指键盘回车监听
- v-model修饰符:v-model.trim 指去除首尾空格 v-model.number 转化为数字
- 事件修饰符:@事件名.stop 阻止冒泡 @事件名.prevent 阻止默认行为
3. computed计算属性
性能更佳,有缓存机制,除非现有数据改变会存下数据,在多次使用时比methods性能更优,不需要声明在data对象中
普通写法
computed:{
计算属性名(){
基于现有数据编写求职逻辑
return 结果
}
}
这是一个属性,在调用时只需要{{计算属性名}}即可
完整写法,当直接修改绑定的值时才这样写,一般用不到
<div class="root">
姓:<input type="text" name="" v-model="fr">
名:<input type="text" name="" v-model="se">
={{res}}<br>
<button @click="change">change</button>
</div>
<script>
let vm = new Vue({
el:'.root',
data:{
fr:'',
se:'',
},
methods: {
change:function(){
this.res = '常雨轩'
}
},
computed:{
res:{
get(){ // 可以接收值
return this.fr+this.se
},
set(value){ // 直接修改时则需要set
this.fr = value.slice(0,1)
this.se = value.slice(1)
}
}
}
})
</script>
4. watch监听
如果原值发生变化则会触发,
<div class="root">
<input type="text" v-model="obj.fr">
={{obj.fr}}<br>
</div>
<script>
let btn = document.querySelector('button')
let vm = new Vue({
el:'.root',
data:{
obj:{
fr:''
}
},
watch:{ //
'obj.fr'(newv,oldv){ // 或者写成objFr
console.log(newv,oldv)
}
}
})
</script>
当在data中定义简单的数据属性不需要引号,Vue 默认将其作为响应式数据处理;定义对象时访问嵌套属性要使用字符串形式来表示属性路径,Vue 2.x 默认使用的是 Object.defineProperty
来实现响应式系统,它要求属性名必须是一个字符串。
完整的写法:
<div class="root">
<select v-model="obj.lang">
<option value="意大利">意大利</option>
<option value="英语">英语</option>
<option value="汉语">汉语</option>
</select>
<input type="text" v-model="obj.fr">
={{ans}}<br>
</div>
<script>
let vm = new Vue({
el:'.root',
data:{
obj:{
fr:'adw',
lang:'意大利'
},
ans:''
},
watch:{
obj:{ // 以对象作为监视对象
deep:true, // 表示是否深度监视,当出现多个对象的时候则需要深度监视
immediate:true, // 是否在页面刷新时立即执行一次
handler(newv){
clearTimeout(this.time)
this.time = setTimeout(()=>{
axios({
url:'https://applet-base-api-t.itheima.net/api/translate',
params:newv
}).then(result=>{
let res = result.data.data
this.ans = res
})
},300)
}
}
}
})
</script>
5. 生命周期函数
生命周期函数直接写入vue对象,首先是created,mounted,是固定的,结束后就是update在频繁更新,最后是destroy
<div class="root">
<button @click="change">{{res}}</button>
</div>
<script>
let vm = new Vue({
el:'.root',
data:{
res:''
},
methods: {
change:function(){
this.res = '常雨轩'
}
},
created() {
console.log('created')
},
updated() {
console.log('updated')
},
})
</script>
实际应用:
let vm = new Vue({
el:'.root',
data:{
res:'',
list:[]
},
created() { // 在网页初始时请求数据
let xhr = new XMLHttpRequest()
xhr.open('get','http://hmajax.itheima.net/api/news')
xhr.addEventListener('loadend',()=>{
console.log(JSON.parse(xhr.response));
})
xhr.send()
},
mounted() { // 在dom元素加载好后便在输入框自动获取焦点
document.querySelector('[name=text]').focus()
}
})
6 工程化开发&脚手架
首先配置环境变量
npm install -g @vue/cli
查看版本号
vue --version
创建项目
vue create project-name
启动
npm run serve
主要包含
* 组件(自定义元素)
局部注册:需要在component文件下写组件,并且在App.vue导入
<template>
<div class="root">
<PageHeader></PageHeader> // 以标签的形式写入
<PageBody></PageBody>
</div>
</template>
<script>
import PageHeader from './components/PageHeader.vue' // 导入组件
import PageBody from './components/PageBody.vue'
export default{
components:{ // 加入组件
PageHeader,
PageBody
}
}
</script>
全局注册:需要在component文件下写组件,并且在main.js导入
import PageButton from './components/PageButton.vue'
Vue.component('PageButton',PageButton)
注意:两者分别是components和component,区分开
7. 样式冲突
scoped:本质上是给不用的组件内的元素加了一个哈希值,data-v-hash,并且自动加了div[data-v-hash]属性选择器,以防止不同组件之间的互相影响:
8. data函数
在脚手架下写data需要将其作为一个函数,每个实例可以维护一份被返回对象的独立的拷贝,如果 data
是一个对象,则它的引用会在所有组件实例之间共享,这会导致如果一个组件实例更改了这个对象,所有其他组件实例的状态也会受到影响,从而导致数据污染。
data(){
return{
count:999
}
}
9. 组件通信
9.1 父子关系
- 传入data用props,
父传子,在组件上加入对象
<MyButton :title="mytitle"></MyButton> // 传对象
子接收
export default {
props:['title'],// 获取对象
}
- 传出修改父亲data,用$emit( event(自定义事件), arg ),允许多参数传入
子:
<button @click="change('cyx')">改变</button>
export default {
methods: {
change(name){
this.$emit('changetitle',name)
}
} }
父:
<MyButton :title="mytitle" @changetitle="changet"></MyButton>
export default {
methods: {
changet(newt){
this.mytitle = newt
}
}
* props
props是组件上的自定义属性,用于向子组件传递信息,可以传递任意数量任意类型的属性。子不能直接修改爹的数据。
简朴写法:
props:{
title:String, // 校验的属性名:类型,如Number String Boolean
}
复杂写法(写成对象来校验):
props:{
title:{
type: String, // 类型
required: true, // 是否必填
default: 'lzd', // 默认值
default(){
return {}
} // 如果是对象就return回去
validator(value){ // 自定义校验,返回true或false
return ...
}
},
},
9.2 兄弟关系
简易消息传递Event bus总线(复杂场景–>Vuex)
- 首先创建一个vue空实例(在util功能目录下)
import Vue from 'vue' // 导入包
const Bus = new Vue() // 创建空实例
export default Bus // 导出包
- 给要修改的组件内添加监听这个空实例(订阅消息)
import Bus from '../utils/EventBus.js'
export default {
created(){
Bus.$on('sendMsg',(msg)=>{
this.msg = msg
})
},
...
}
- 添加发送触发事件,传参
import Bus from '../utils/EventBus.js'
export default {
methods: {
changeMsg(){
Bus.$emit('sendMsg','快来找我玩ssssssss')
}
},
}
9.3 provide-inject方法
实现跨层级共享数据(当我们想让一个父级下的所有子孙都使用这些属性)
- 在父级填写
export default {
provide(){
return{
color:this.color, // 普通类型非响应式
object:this.object // 复杂类型响应式(推荐)
}
} }
- 在子孙直接使用
export default {
...,
inject:['color','object'],
}
9.4 v-model(固定传值value)
原理:v-model本质就是v-bind绑定后,通过事件监听封装后的双向绑定
我们可以借此来进行子组件和父组件的双向绑定
- 父级
<MyB :msg="msg" @click="msg=$event"></MyB>
<MyB :msg="msg" @cg="msg=$event"></MyB> // 两个含义相同
- 子级
<button @click="changeMsg">改变{{msg}}</button>
export default {
props:['msg'],
methods: {
changeMsg(){
this.$emit('cg','lzd')
},
},
...
}
(目前形参只能传一个适用)
简单写法有限制需要监听事件是input,因为v-model
默认会监听组件触发的input
事件,并将传递给该事件的数据作为新值赋给绑定的属性。这是Vue.js的约定。
- 父级
<MyB v-model="msg"></MyB>
- 子级
export default {
props:['msg'],
methods: {
changeMsg(){
this.$emit('input', 'lzd'); // 使用input事件
},
},
}
9.5 sync修饰符(非固定value)
- 父级
<MyB :msg="msg" @update:msg="msg=$event"></MyB>
<MyB :msg="msg" @update:msg="newm=>msg=newm"></MyB>
<MyB :msg.sync="msg"></MyB> // 三个含义相同
- 子级
export default {
props:['msg'],
methods: {
changeMsg(){
this.$emit('update:msg', 'lzd'); // 使用input事件
},
},
}
10. ref&&refs
可以用于获取dom元素或者组件实例
- 目标组件添加ref属性
<MyB ref="My"></MyB>
<button @click="clk">输出</button>
- 恰当时机,通过调用this.$refs.ref属性名,获取目标组件,可以调用组件对象里面的方法
export default {
...,
methods: {
clk(){
this.$refs.My.changeMsg()
}
},
}
同样可以取代**document.querySelector()**直接用this.$refs.ref属性名得到dom元素
11. $nextTick函数
当我们要让输入框显示的时候实际上它在队列里等待更新,不会立即渲染出来,所以常规下接this. r e f s . i n p . f o c u s ( ) ∗ ∗ 并不会获取焦点,这是因为异步更新。但是我们可以让他等一下异步更新则用 ∗ ∗ refs.inp.focus()**并不会获取焦点,这是因为异步更新。但是我们可以让他等一下异步更新则用** refs.inp.focus()∗∗并不会获取焦点,这是因为异步更新。但是我们可以让他等一下异步更新则用∗∗nextTick
<button v-show="isshow" ref="ref" @click="clk"> 输出 </button>
<input type="text" v-show="!isshow" ref="inp">
export default {
data(){
return{
isshow:true,
}
},
methods: {
clk(){
this.isshow = !this.isshow
this.$nextTick(()=>{ // $nextTick
console.log(this)
this.$refs.inp.focus()
})
}
},
}
12. 自定义指令
12.1 全局注册
在main.js中全局注册,
Vue.directive('focus',{
inserted(el){
el.focus()
}
})
12.2 局部注册
在组件中注册
export default {
...,
directives:{
focus:{
inserted(el){
el.focus()
}
}
}
}
- 当值修改的时候,仅仅是inserted无法更新视图,需要update
export default {
data() {
return {
color1:'red',
color2:'blue'
}
},
directives:{
color:{
// 仅仅是提供元素被添加到页面中时的逻辑
inserted(el,binding){
el.style.color = binding.value
},
// update 提供当值变化后,dom更新的逻辑
update (el,binding) {
console.log('修改!')
el.style.color = binding.value
},
}
}
}
13. 插槽
- 子组件内用slot占位,有时候并不需要在插槽中填值,我们可以在slot中填入内容将作为默认值
<template>
<div class="Son1">
我是头
<slot name="head" num="10" msg="测试文本">我是头和中间</slot> 传值时在slot里添加,像是在添加属性名一样
我是中间
<slot name="foot">我是中间和尾巴</slot> // 当有多个slot时用name属性区分
我是尾巴
</div>
</template>
- 父组件就可以直接使用
<template>
<div id="app">
<MySon1>
<template #head="content"> // 对应占位名字,直接可以获得内容,content是形参,随便定义,一个slot时为default
<div class="head" >{{content.num}}</div>
</template>
<template #foot>
<div class="foot">foot</div>
</template>
</MySon1>
</div>
</template>
组件之间需要传值
14. Router路由(Vue2)
14.1 路由基础
路径和组件的映射关系
* 基础步骤
首先在项目里配置,vue3将用router4版本
npm add vue-router@3.6.5
然后在main.js里配置插件
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router' // 引入
Vue.config.productionTip = false
Vue.use(VueRouter) // 安装注册
const router = new VueRouter() // 创建路由对象
new Vue({
render: h => h(App),
router // 注入建立关联
}).$mount('#app')
url出现/#/则表示成功
* 核心步骤
创建views目录在里面添加路由组件,然后再main.js中配规则
import VueRouter from 'vue-router'
import Find from './views/Find.vue'
import My from './views/My.vue'
import Friend from './views/Friend.vue'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({
routes:[ //路由规则们
{path:'/find',component: Find}, // route一条规则有路径path和组件component
{path:'/My',component: My},
{path:'/Friend',component: Friend},
]
})
准备导航链接,配置路由出口(匹配的组件展示的位置)
在需要切换的组件页面类似于:
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a> <!-- 配置路径 -->
<a href="#/my">我的音乐</a> <!-- 配置路径 -->
<a href="#/friend">朋友</a> <!-- 配置路径 -->
</div>
<div class="top">
<!-- 路由出口,想在哪里显示就放在哪里 -->
<router-view></router-view>
</div>
</div>
</template>
组件分类问题
- src/views:页面组件 – 页面展示 – 配合路由用
- src/components:复用组件 – 展示数据 – 常用于复用
14.2 路由进阶
为了便于维护,新建一个router文件夹里存index.js,同时可以使main.js变得更加干净整洁,只需要导入就行
- router/index.js
import VueRouter from 'vue-router'
import Vue from 'vue'
import Find from '../views/Find.vue'
import My from '../views/My.vue'
import Friend from '../views/Friend.vue'
Vue.config.productionTip = false
Vue.use(VueRouter)
export const router = new VueRouter({
routes:[
{path:'/find',component: Find},
{path:'/My',component: My},
{path:'/Friend',component: Friend},
]
})
// export { router }
// export default router
- main.js
import Vue from 'vue'
import App from './App.vue'
import {router} from './router/index.js'
new Vue({
render: h => h(App),
router
}).$mount('#app')
* router-link声明式导航
<router-link to=“”>来代替a标签,用to来接收路径并且可以省去#,最大优点是自动添加一个激活类名,可以帮助高亮
<template>
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
</div>
</template>
<style>
.footer_wrap a.router-link-exact-active{
background-color: #654ba4;
} // router-link-exact-active 自动给一个类
</style>
有两个类名:
-
router-link-exact-active:精确匹配,/find会高亮,在二级嵌套时/find/one则不会高亮
-
router-link-active:模糊匹配,/find会高亮,在二级嵌套时/find/one也会高亮
-
当然我们可以自定义
const router = new VueRouter({
routes:[...],
linkActiveClass:'active', // 自定义模糊
linkExactActiveClass:'exactactive' // 自定义精确
})
* 嵌套路由
-
编写
News
的子路由:Detail.vue
-
配置路由规则,使用
children
配置项:const router = createRouter({ history:createWebHistory(), routes:[ { name:'xinwen', path:'/news', component:News, children:[ { name:'xiang', path:'detail', component:Detail } ] }, ...., ] }) export default router
* 声明式导航,传参
- 查询参数传参(适合多个参数传参):在链接后面加查询参数,
- 语法格式
<router-link to="/friend?key=我的朋友">我的朋友</router-link>
- 查询:$route.query.参数名
<span>{{$route.query.key}}</span> // 直接用
export default {
name:'FindMusic',
mounted() {
console.log(this.$route.query.key) // js中需要用this
},
}
- 动态路由传参(适合单个参数传参):是必选的,如果不传就不会匹配到,可以选择在跳转路由后面加空格/ ,也可以在**/:参数名?**后面加?
- 配置路由:设置为**/:words**
new VueRouter({
routes:[
{path:'/find/:key',component: Find},
],
...
})
export { router }
- 跳转语法格式,直接写/
<router-link to="/find/发现音乐">发现音乐</router-link>
- 查询:$route.params.参数名
<span>{{$route.params.key}}</span>
export default {
name:'FindMusic',
mounted() {
console.log(this.$route.params.key)
},
}
* 重定向
当一开始进入页面时也有可能是空的,需要自动跳转到某一页,可以使用重定向redirect,配置路由
{path:'/',redirect:'/find/我的发现'},
* 404
在所有的路径后配这个,在搜索不到时会有这个页面
{path:'*',component: NotFind},
* 模式设置
hash路由(默认):http://localhost:8080/#/find
history路由(常用):http://localhost:8080/find
在配置中:
new VueRouter({
routes:[...],
mode:'history'
})
* 路由跳转方式(router!!!)
- 路径跳转:
export default {
methods: {
goother(){ // 添加点击事件
this.$router.push('/my/我的音乐') //跳向对应的路由,在此页面不可重复跳转
this.$router.push({ // 查询参数传参
url:'/my/我的音乐'
query:{
参数1:value1,
参数2:value2,
}
})
this.$router.push('/my/${value1}/${value2}') // 动态传参
}
},
};
- 名字跳转(适合路径名长的场景):
- 添加名字
{name:'hahah',path:'/find/:key?',component: Find},
- 跳转
export default {
methods: {
goother(){ // 添加点击事件
this.$router.push({ // 查询参数传参
name:'hahah'
query:{
参数1:value1,
参数2:value2,
}
})
this.$router.push({ // 查询参数传参
name:'hahah'
params:{
参数1:value1,
参数2:value2,
}
})
}
},
};
* 路由守卫
- 全局
- beforeEach(全局前置):每一个路由跳转前都需要经过这个,当我们需要满足某些情况时才访问这个路由,比如对应的学校才能访问这个路由时需要。
router.beforeEach((to, from, next) => { // to是到哪个路由,to是来自哪个路由,next()函数表示通行,满足条件则通行
console.log(to,from)
if(from.path === '/'){
next()
}
})
补充:我们也可以给每个路由meta属性里添加对象,用来判断这个路由是不是需要beforeEach,
const router = new Router({
routes: [{..., meta:{istrue:true} }]
})
- afterEach没有next,直接跳转走了,用处不多,例如只有在跳转后才改变document.title时适用这个
- 独享
前置独享守卫:beforeEnter;没有后置独享守卫
- 组件
前置组件守卫:beforeRouteEnter,后置组件守卫beforeRouteLeave
* 路由缓存(vue3)
基本使用,会保留原来的数据:
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
如果想要条件性地被 KeepAlive`缓存,就必须显式声明一个 name 选项。
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
15. Vuex状态管理
受不了了真勾吧多,建议直接去看最下面的pinia
Vue3(组合式api)
更容易维护,更快速度,更小体积,更优数据响应
1. 创建项目
npm init vue@latest
pnmp create vue
2. template小操作
比如这种操作,为了防止ul和li布局打乱,可以使用template,在循环之后template并不会显示会消失
<ul>
<template v-for="item in items">
<li>{{item.msg}}</li>
<li class="box">{{item.msg}}</li>
</template>
</ul>
3. Set up
也是生命周期函数,在beforeCreate钩子之前自动执行,也要定义函数setup(),然后以对象方式return数据从而使其能被调用,但是太麻烦了这样
所以我们语法糖封装更好使用,之后则不能使用this因为时间线太早还未创建Vue对象
<script setup> </script>
4. ref&reactive
4.1 生成响应式数据
在vue中本身都不是响应式的,需要生成响应式数据
- ref函数可以返回简单或复杂类型的数据
<script setup>
import {ref} from 'vue'
let obj = ref({
count:100,
age:10
}) // 返回的是一个响应式对象,会在外面“包一层”,所以脚本里要用count.value获取值
let getc = () => { obj.value.count++ }
</script>
<template>
<div class="root">
{{count}} // 注意在模板中自动脱一层,可以直接写count
<button @click="getc">佳佳</button>
</div>
</template>
- reactive函数只可以用于返回复杂类型
let obj = reactive({
count:100,
age:10
})
let getc = () => { obj.count++ }// 不需要写成obj.value.count
推荐使用ref,既可以用于简单也可以用于复杂类型
4.2 defineExpose借用ref
子属性暴露给父的元素
- 子,要调用defineExpose来让自己的属性暴露,才能让父访问到这个数据
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
- 父,因为setup时间非常早,所以要在mounted后使用
<script setup>
import Son from '@/components/Son.vue'
let son = ref(null) // 要先声明
onMounted(() => { console.log(son.value.a) }) // 用子元素的暴露的属性
</script>
<template>
<div class="root">
<Son ref="son"></Son>{{cnt}} // 要给传进来的组件一个ref变量值,才能用子元素的暴露的属性
</div>
</template>
5. computed计算属性
导入后改写法
import {computed} from 'vue'
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
6. watch监听
简朴写法,直接传不需要.value,就是传ref对象的
watch([num,name],([newnum,newname],[oldnum,oldname])=>{ // 也可以写成 (newn,oldn) [1, 'lzd'] ,[0, 'lzd']
console.log(newnum,oldnum) // 新
console.log(newname,oldname) // 旧 分开输出
})
复杂写法
let obj = ref({ num:0, name:'lzd'})
watch(()=>obj.value.name,(newn,oldn)=>{ // 如果只想监视对象里的某个值需要这样写return那个值
console.log(newn,oldn)
},{ // 再加个括号里写属性
deep:true,
immediate:true
})
7. 生命周期函数
组合式API的生命周期函数:setup,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted(销毁Unmounted)
8. 组件通信
组合式API下,不需要写components就可以直接调用
import Son from '@/components/Son.vue'
8.1 父子关系
- 父传子
- 父亲不变
- 子需要用defineProps(不需要导入)编译器宏,
<script setup>
const props = defineProps({
name:String
})
console.log(props.name) // 在脚本中应该这样写
</script>
<template>
<div>
儿子:{{name}} // 模板中不需要写props
</div>
</template>
- 子传父
- 父不变
const change1 = (newn) => { obj.value.name = newn }
const change2 = (newn) => { obj.value.name = newn }
<template>
<div class="root">
父亲:{{obj.name}}
<Son :name="obj.name" @change1="change1"
@change2="change2"></Son>
</div>
</template>
- 子,需要用到:defineEmits(不需要导入)声明
<script setup>
const emit = defineEmits(['change1','change2'])
const change1 = () => { emit('change1','lzd') }
const change2 = () => { emit('change2','hhh') }
</script>
<template>
<div> 儿子:{{name}}
<button @click="change1">change1</button>
<button @click="change2">change2</button>
</div> </template>
8.2 provide-inject方法
- 爷传孙
- 爷用provide(‘key‘,顶层数据)一次只能提供一个数据
let cnt = ref(10)
provide(
'cnt',cnt
)
- 子,key值必须相同
let num = inject('cnt')
console.log(num.value)
- 孙传爷
- 爷用provide(‘key‘,顶层数据)传递一个函数
provide('changecnt',(newcnt)=>{ cnt.value = newcnt })
- 子用爷的函数改爷的数据,实际控制权还是爷
let changenum = inject('changecnt')
const change2 = () => {
console.log(num.value)
changenum(1000)
console.log(num.value) }
9. defineOptions
用了
defineOptions({ name:'registerindex' })
10. v-model
仅使用一次在一个组件内
- 父
<Son v-model="cnt" ></Son>{{cnt}}
- 子,引入defineModel,补上代码后,则可以任意修改
”这个元素“
let model = defineModel()
11. Pinia状态管理
手动添加时: npm install pinia,然后再main.js文件中加
import {createPinia} from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia).mount('#app') // 将其挂载上去
都需要先新建一个store文件夹管理,导入import {defineStore} from ‘pinia’,并且暴露所有的内容(return)
11.1 同步管理
import {ref,computed} from 'vue'
import {defineStore} from 'pinia'
export const useCounterStore = defineStore('counter',()=>{ //(仓库的唯一表示不可乱写,()=>{})
const cnt = ref(10)
// 声明操作数据的方法action函数
const addcnt = () => cnt.value++
const subcnt = () => cnt.value--
// 声明基于数据派生的计算属性 getters(computed)
const double = computed(() => cnt.value * 2)
return{
cnt,
subcnt,
addcnt,
double
}
})
在App.vue中需要引入
<script setup>
import Son1 from '@/components/Son1.vue'
import {useCounterStore} from '@/store/counter.js'
const counterStore = useCounterStore()
</script>
<template>
<div>
我是父亲:{{counterStore.cnt}}
<Son1></Son1>
</div>
</template>
组件Son1.vue中需要引入
<script setup>
import {useCounterStore} from '@/store/counter.js'
const counterStore = useCounterStore()
</script>
<template>
<div>
我是Son1:{{counterStore.cnt}} - {{counterStore.double}}
<button @click="counterStore.addcnt">+</button>
<button @click="counterStore.subcnt">-</button>
</div>
</template>
11.2 异步操作(action也接受异步)
- pinia中
import {defineStore} from 'pinia'
import axios from 'axios'
import {ref} from 'vue'
export const useChannelStore = defineStore('channel',()=>{
let list = ref([])
let getList = async () => {
const {data:{data}} = await axios.get('http://geek.itheima.net/v1_0/channels')
list.value = data.channels
console.log(list.value)
}
return{
list,
getList
}
})
- App.vue中
<script setup>
import Son1 from '@/components/Son1.vue'
import {useChannelStore} from '@/store/channel.js'
const channelStore = useChannelStore()
</script>
<template>
<div>
我是父亲:{{counterStore.cnt}}
<Son1></Son1>
<button @click="channelStore.getList">得到属性</button>
<ul>
<li v-for="(item,index) in channelStore.list" :key="item.id">{{item.name}}-{{index}}</li>
</ul>
</div>
</template>
注意:虽然useChannelStore这些都是对象,但是不能直接解构,会丢失响应性,这就好像是对props解构,代表仅仅是声明了值。并且store是用reactive包装的对象,不需要在getters后面写value
所以我们需要使用storeToRefs借助解构来添加响应式的引用来包装,而函数不需要storeToRefs,
import {storeToRefs} from 'pinia'
import {useCounterStore} from '@/store/counter.js'
const counterStore = useCounterStore()
let {cnt} = storeToRefs(counterStore)
(两种写法取决于实际场景)
12. pinia-plugin-persistedstate持久化插件!!!
先安装到本地
npm i pinia-plugin-persistedstate
再配置到main.js里配置全局
import { createApp } from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'
import persist from 'pinia-plugin-persistedstate' // 插件
const pinia = createPinia()
createApp(App).use(pinia.use(persist)).mount('#app')
基础在需要store文件夹里需要持久化的数据里,在下一项里写
defineStore('counter',() => { ..., }, { persist:true // 开启持久化 })
可以补充
defineStore('counter',() => { ..., }, { persist:{
key:'cyx', // 改变存储名字key
paths:['cnt'] // 表示需要存储的值
}
}) // 默认存储到localstorage
还有很多可以到官网看https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
13. unplugin-auto-import自动导入插件
安装
pnpm install unplugin-auto-import
然后在vite-config.js文件中配置
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'; // 自动导入插件
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({ // 自动导入插件
imports:[
'vue', //导入vue
]
})
],
})