从0学习Vue3(2)

165 阅读6分钟

延续上一章的学习继续学嗷~跟我一起敲代码!一起加油!

2.4 表单提交

表单是form,那我们先写一个简单的表单,里面有一个输入框还有一个提交按钮:

 <div id="app">
     <form>
         <input type="text" />
         <button>提交表单</button>
     </form>
 </div>

这里我们可以去输入然后点击一下按钮,会发现呢,每次点击提交按钮之后都会自动刷新页面。那么如何做到,点击提交的时候不让表单刷新呢:

首先我们先自己写一个提交表单的功能

 methods:{
    post(){
        console.log('提交表单')//在控制台输出文字,按F12、右键检查 可以打开控制台
    }
 }

然后把它交给我们的表单 form,表单提交事件是submit,像我们之前点击事件是click一样:

 <div id="app">
     <form @submit="post">
         <input type="text" />
         <button>提交表单</button>
     </form>
 </div>

这样我们就把新的提交功能给到form了,但是呢表单默认的事件还是存在的,因此我们需要阻止它,用到prevent阻止默认行为

 <form @submit.prevent="post">

这样呢点击提交就不会再刷新页面了。

2.5 数据的双向绑定

上个点我们解决了表单提交的问题,那么如果我想要实现:点击提交之后,在控制台输出我们输入框中的数据。

那么就需要到新的知识点数据的双向绑定

这里用到的指令是:v-model

想要一个可以变化的数据,我们得先声明一个变量:

 data(){
     return {
         msg:'hello world'//为了能看到效果,给它设定了初始值"hello world"
     }
 },

接着把它绑定到输入框:

 <input type="text" v-model="msg" />

好了保存去看一下页面,刷新,会发现这时候输入框内有了我们msg的值,并且是可以修改的,只要我们去更改输入框的值,msg也会跟着改变,如果不确定 可以把 msg 输出出来看一下,放到<h1>标签里看看:

 <div id="app">
     <h1>{{msg}}</h1>
     <form @submit.prevent="post">
         <input type="text" v-model="msg" />
         <button>提交表单</button>
     </form>
 </div>

那我们已经实现了数据的双向绑定了,输入框里的值的改变,会改变 msg,那么我们提交表单就只需要使用msg的值就可以了。

 methods:{
    post(){
        console.log(this.msg)
    }
 }

学到这里呢,其实v-model也有一些修饰符,就跟我们上面用到的@submit.prevent一样,可以提供一些额外的效果。

情况1:我们的msg是字符串,但如果我们最终想要的东西是 数字 number类型,可以这样写:v-model.number

情况2:这里我们在输入框里面打字,每打一个字msg就会改变一下对吧,但是如果我们只想要在输入框失去焦点的时候,才改变一次数据,比如我们点击按钮的时候才更新最新数据,这样就不用一直更新数据了。可以这样写:v-model.lazy

情况3:比如我们做了一个表单,让用户输入用户名,但是他一不小心,在开头打了几个空格,或者是最后打了几个空格,他自己没意识到,如果这样被存到数据库的话,那他的用户名就会莫名地多了空格。所以有一个修饰符可以帮忙去除首尾的空格,:v-model.trim。跟JavaScript的去除字符串前后空格是一样的英文。

2.6 一个简单的购物车功能

先放一个效果图在这里对照:分别输入名称和单价后,点击提交,下方列表会增加一条数据,原本列表是空的。

image.png

,可以自己去敲一敲,实现一下,复习一下,这里提几个点给萌新们:

输入框前面的文字使用<label>标签,这样使用可以让它们绑定住:

 <!-- 用for去绑定input的id,这样点击前面的文字会自动选中输入框 -->
 <label for="inp1">商品名称 </label>
 <input id="inp1" type="text" v-model.trim.lazy="name" />

换行使用</br>,就可以换行了。

其他的之前都学过,忘记了可以看看上一篇文章哝:juejin.cn/post/716183…

可能有的人会觉得不知道数量怎么来的,只要在传名称和单价的时候顺便传一个数量为1就可以了。这里存进数组里的就得是对象了。

 post(){
    //list是用来展示商品列表的
    this.list.push({name:this.name,price:this.price,num:1})
 },

完成了之后嘞看看代码:

     <div id="app">
         <h1>购物车</h1>
         <form @submit.prevent="post">
             <label for="inp1">商品名称 </label>
             <input id="inp1" type="text" v-model.trim.lazy="name" />
             </br>
             <label for="inp2">商品单价 </label>
             <input id="inp2" type="number" v-model.number.lazy="price" />
             </br>
             <button>提交表单</button>
         </form>
         <!-- 上面是表单的部分 -->
 ​
         <ul>
             <li v-for="item in list">
                 商品名称:{{item.name}} 单价:{{item.price}} 数量:{{item.num}}
             </li>
         </ul>
         <!-- 上面是列表的部分 -->
 ​
     </div>
     <script src="https://unpkg.com/vue@next"></script>
     <script>
         Vue.createApp({
             data(){
                 return {
                     name:'',
                     price:'',
                     list:[],
                 }
             },
             methods:{
                 post(){
                     this.list.push({name:this.name,price:this.price,num:1})
                 }
             }
         }).mount('#app')
     </script>

这样呢,就完成了一个简易版,接着给他加上一个调节数量的功能,并能算出总价,先上效果图:

image.png

同样是上一章讲过的嘞。

最终代码是:

     <div id="app">
         <h1>购物车</h1>
         <form @submit.prevent="post">
             <label for="inp1">商品名称 </label>
             <input id="inp1" type="text" v-model.trim.lazy="name" />
             </br>
             <label for="inp2">商品单价 </label>
             <input id="inp2" type="number" v-model.number.lazy="price" />
             </br>
             <button>提交表单</button>
         </form>
         <!-- 上面是表单的部分 -->
 ​
         <ul>
             <li v-for="item,index in list">
                商品名称:{{item.name}} 单价:{{item.price}} 数量:<button @click="minus(item,index)">-</button> {{item.num}} <button @click="add(item)">+</button>
            </li>
         </ul>
 ​
         <!-- 上面是列表的部分 -->
         <h2>总价{{allPrice}}</h2>
     </div>
     <script src="https://unpkg.com/vue@next"></script>
     <script>
         Vue.createApp({
             data(){
                 return {
                     name:'',
                     price:'',
                     list:[],
                     allPrice:0
                 }
             },
             methods:{
                 post(){
                     this.list.push({name:this.name,price:this.price,num:1})
                     //总价加上新添的物品单价
                     this.allPrice = this.allPrice + this.price
                     //以下把输入框清空,比较干净
                     this.name=''
                     this.price=''
                 },
                 add(item){//传入参数,是我们点击的那个物品
                     //该物品的数量 +1
                     item.num++
                     //总价加上该物品单价
                     this.allPrice = this.allPrice + item.price
                 },
                 minus(item,index){//减法,这里我多加一点功能,多传一个下标进来
                    if(item.num>1){//这里做判断,只有在数量大于1的情况下,才能正常减,否则就要询问是否删除
                        item.num--
                        this.allPrice = this.allPrice - item.price
                    }
                    else if(confirm("是否删除")){ //这里用到confirm(),返回是一个布尔值true/false 可用来做判断
                        this.list.splice(index,1)//这里是数组的splice方法
                        this.allPrice = this.allPrice - item.price
                    }
                }
             }
         }).mount('#app')
     </script>

这一遍主要变化的是函数了。我都加上备注了,应该能看懂吧嘿嘿。

ok,好好理解之后,接着改进,我们发现在每一个函数里面啊,都要去更新这个allPrice,是不是非常多一样的代码呀,非常不爽,那么这个总价呢,其实说到底 就是一个需要计算得到的值吧。那么我们可以用到computed属性,跟methods和data同级,是一个计算属性。它里面的值是函数,拿到的值是它的返回值

computed是vue的计算属性,是根据依赖关系进行缓存的计算,只有在它的相关依赖发生改变时才会进行更新

例如:

 computed:{
     getAllPrice(){
         return 100
     }
 }

这样写的话,我们在代码中写上{{getAllPrice}}就会展示100了。那我们要得到的总价应该等于什么嘞,应该等于商品的数量乘以单价对吧,所以这样写:

 computed:{
     getAllPrice(){
         //循环遍历数组,把每个商品的价值加到sum中,最终返回
         //而每当与之有依赖关系的值发生变化时,它就会自动进行计算更新
         let sum = 0
         this.list.forEach(v => sum+=v.price*v.num)
         return sum
     }
 }

所以代码的最终形态是:

     <div id="app">
         <h1>购物车</h1>
         <form @submit.prevent="post">
             <label for="inp1">商品名称 </label>
             <input id="inp1" type="text" v-model.trim.lazy="name" />
             </br>
             <label for="inp2">商品单价 </label>
             <input id="inp2" type="number" v-model.number.lazy="price" />
             </br>
             <button>提交表单</button>
         </form>
         <!-- 上面是表单的部分 -->
 ​
         <ul>
             <li v-for="item,index in list">
                 商品名称:{{item.name}} 单价:{{item.price}} 数量:<button @click="minus(item,index)">-</button> {{item.num}} <button @click="add(item)">+</button>
             </li>
         </ul>
 ​
         <!-- 上面是列表的部分 -->
         <h2>总价{{getAllPrice}}</h2>
     </div>
     <script src="https://unpkg.com/vue@next"></script>
     <script>
         Vue.createApp({
             data(){
                 return {
                     name:'',
                     price:'',
                     list:[],
                 }
             },
             computed:{
                 getAllPrice(){
                     let sum = 0
                     this.list.forEach(v => sum+=v.price*v.num)
                     return sum
                 }
             },
             methods:{
                 post(){
                     this.list.push({name:this.name,price:this.price,num:1})
                     this.name=''
                     this.price=''
                 },
                 add(item){
                     item.num++
                 },
                 minus(item,index){
                     if(item.num>1){
                         item.num--
                     }
                     else if(confirm("是否删除")){
                         this.list.splice(index,1)
                     }
                 }
             }
         }).mount('#app')
     </script>

那这里就顺着computed计算属性学习另一个属性watch监视属性,依然用上面的代码做例子:

1
    <script>
2
        Vue.createApp({
3
            data(){
4
                return {
5
                    name:'',
6
                    price:'',
7
                    list:[],
8
                }
9
            },
10
            computed:{
11
                getAllPrice(){
12
                    let sum = 0
13
                    this.list.forEach(v => sum+=v.price*v.num)
14
                    return sum
15
                }
16
            },
17
           watch:{
18
               'list':{ //监视谁,就写谁 我这里监视list
19
                   handler(newValue,oldValue){ //当list发生改变时调用
20
                       console.log(`list从${oldValue}变成了${newValue}`)
21
                   },
22
                   immediate:true,//默认是false的,设置为true会在初始化时调用handler()
23
               }
24
           }
25
        }).mount('#app')
26
    </script>

而且watch()不仅能监视data()中定义的变量,还能检测computed计算属性中的值,也就是上面的getAllPrice()同样能被监视。然后也可以拿到Vue的实例然后在外部检测:

1
    <script>
2
        const app = Vue.createApp({ //拿到Vue实例
3
            data(){
4
                return {
5
                    name:'',
6
                    price:'',
7
                    list:[],
8
                }
9
            },
10
            computed:{
11
                getAllPrice(){
12
                    let sum = 0
13
                    this.list.forEach(v => sum+=v.price*v.num)
14
                    return sum
15
                }
16
            },
17
        })
18
        
19
        app.$watch('list',{  //同样能监视
20
            immediate:true,
21
            handler(newValue,oldValue){
22
                       console.log(`list从${oldValue}变成了${newValue}`)
23
                   }
24
        })
25
        
26
        app.mount('#app')
27
    </script>

那具体怎么分呢,如果说我们在刚创建vue实例的时候就已经知道我们要监视谁,那就可以直接在实例内部使用watch(),但是如果是需要用户后续的行为来决定要监视谁,那就在外部调用这个watch api就可以了。

那么学会了监视,接下来进阶一下深度监视

1、监视多级结构中某个属性的变化

1    <script>
2        Vue.createApp({
3            data(){
4                return {
5                    numbers:{ //创建一个对象,而我单单只想监视a,不监视b
6                        a:1,
7                        b:1
8                    }
9                }
10            },
11            methods:{
12                aAdd(){
13                    this.numbers.a++
14                }
15            }
16           watch:{
17            //切记这是字符串,如果是单个属性可以不要引号,但如果像这个numbers是对象,想只监视a,必须用引号
18               'numbers.a':{ 
19                   handler(newValue,oldValue){
20                       console.log(`a从${oldValue}变成了${newValue}`)
21                   },
22               }
23           }
24        }).mount('#app')
25    </script>

在模板中触发其中的aAdd(),会发现控制到可以监视numbers中a的值了。

2.监视多级结构中所有属性的变化

1//一看到这个是不是在想像下面这样就行了呀
2'numbers':{ 
3     handler(newValue,oldValue){
4           console.log(`从${oldValue}变成了${newValue}`)
5     },
6}

其实是不行的,因为这样子呢它监视的其实是整个numbers:{} 这个结构,而我们真正改变的是它内部的a、b,地址是不一样的,因此也就不会触发watch()。但其实呢,也只需要加一个配置项就可以了:

1'numbers':{ 
2    deep:true, //深度监视,默认是false,这下就可以了
3    handler(newValue,oldValue){
4        console.log(`从${oldValue}变成了${newValue}`)
5    },
6}

但这里不能被误导,Vue自身呢是可以监测对象内部值的改变,但Vue提供的watch默认是不能监测到的。

监视的简写形式:简写是需要代价的,不能够使用 immediate和deep,只有handler()

1watch:{
2    //正常写法
3    'list':{ 
4        immediate:true,
5        deep:true, 
6        handler(newValue,oldValue){
7            console.log(`list从${oldValue}变成了${newValue}`)
8        },
9    },
10    //简写
11    list(newValue,oldValue){
12        console.log(`list从${oldValue}变成了${newValue}`)
13    }
14}
1516//在外部的写法一样可以简写,但一样不能使用 immediate和deep
17app.$watch('list',{  
18     immediate:true,
19     deep:true, 
20     handler(newValue,oldValue){
21            console.log(`list从${oldValue}变成了${newValue}`)
22     }
23})
24​
25app.$watch('list',function(newValue,oldValue){ //直接写函数
26    console.log(`list从${oldValue}变成了${newValue}`)
27})
28//这里有一个需要注意的代诺,如果我这里写成箭头函数,那么里面的this指向的就是window而不是app了
29app.$watch('list',(newValue,oldValue)=>{ 
30    console.log(`list从${oldValue}变成了${newValue}`)
31})

好嘞,如果你学到这里,基础就学得差不多了,下面一章学习组件化开发。一定要多巩固喔,敲点自己感兴趣的页面或效果出来。一起加油!!!