青训营笔记创作活动 —— vue2 | 青训营

90 阅读23分钟

Vue2

vue官网:cn.vuejs.org/。(疯狂啊!!!不用手动操作dom!!!)

v2文档:v2.cn.vuejs.org/v2/guide/。(先v2再v3)

https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js   // 浏览器ctrl+s —— vue/lib
https://unpkg.com/vue@3/dist/vue.global.js       // 浏览器ctrl+s —— vue/lib

1、响应式原理。

在vue2中html使用双大括号{{}}来进行模板解析,其中{{}}中的内容被解析为js代码。在vue2中js使用new Vue来创建一个状态实例vm,其中el表示被挂载对象,data表示模板中的数据,可以通过vm.datakey获取数据。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <!-- 模板解析 双大括号中被解析为js代码 -->
        {{10+20}}
        {{ myname }}
    </div>
    <script>
        var vm = new Vue({
            // 将id为box的元素挂载托管
            el:"#box",
            // 页面数据
            data:{
                myname:"wxm"  // 状态 可以使用vm.myname获取数据
            }
        })
    </script>
</body>
</html>

原理:如果直接在obj中定义属性,那么对obj属性的任何改变是无法监听到的;相反,使用defineProperty方法来监听obj对象的myname属性,那么在获取myname时会调用get函数,在设置myname时会调用set函数,此时我们可以获取页面dom元素p标签,当设置obj的myname属性时,将对应的设置值赋值给p元素即可实时监听。vue2也是如此实现,只不过没让我们看到而已。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p id="myp"></p>
    <script>
        var obox = document.getElementById("myp") // id就是id名 不是css3啊!
        var obj = {}
        //监听obj对象的myname属性
        Object.defineProperty(obj,"myname",{
            //访问
            get:function(){
                console.log("get")
            },
            //修改
            set(value){
                obox.innerHTML = value
            }
        })
    </script>
</body>
</html>

2、模板语法。

:属性名=属性值   //属性值被解析为js数据     v-bind缩写
@属性名=属性值   //属性值被解析为js函数     v-on缩写
v-show   //false时存在节点但不显示
v-if    //false时压根不存在节点
v-for   //遍历列表   data in list / (item,index) in list
v-model  //双向绑定输入框值    v-model="mytext"
v-html  //渲染html   <div v-html="mytext"></div>  不可以使用{{}}防止被攻击
new Vue({
    el:被挂载对象,
    data:{
       模板数据
    },
    methods:{
       模板函数
       this指向的是当前创建的vue实例
    }
})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .red{
            background-color: red;
        }
        .yellow{
            background-color: yellow;
        }
    </style>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div>我的名字是: {{myname}} -  我的年龄是: {{myage}} </div>
        <!-- :表示其对应的属性被解析为模板语法 -->
        <div :class="whichcolor">切换背景色1</div>
        <div :class="isColor?'red':'yellow'">切换背景色2</div>
        <!-- @表示其对应的函数被解析为模板语法 -->
        <button @click="handleChange">Change</button>
        <!-- v-show为false只是不显示而v-if为false是完全不存在 -->
        <div v-show="isShow">我是动态显示和隐藏</div>
        <div v-if="isCreated">我是动态创建和删除</div>
        <ul>
            <li v-for="data in list">{{data}}</li>
        </ul>
    </div>
    <script>
        var vm = new Vue({
            el:'#box',
            //所有数据
            data:{
                myname:"wxm",
                myage:18,
                whichcolor:'red',
                isColor:true,
                isShow:false,
                isCreated:false,
                list:['aaa','bbb','ccc']
            },
            //所有方法
            methods:{
                handleChange(){
                    console.log("handleChange")
                    //this指向的new Vue实例
                    this.myname = "www"
                    this.myage = 22
                    this.whichcolor = 'yellow'
                    this.isColor = !this.isColor,
                    this.isShow = !this.isShow,
                    this.isCreated = !this.isCreated
                }
            }
        })
    </script>
</body>
</html>

使用上述知识写的todolist:对比原生js或者jquery好简单!!!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <input type="text" v-model="mytext">
        <button @click="handleAdd()">add</button>
        <ul v-show="list.length">
            <li v-for="(data,index) in list">
                {{data}}
                <button @click="handleDel(index)">Delete</button>
            </li>
        </ul>
        <div v-show="!list.length">待办事项空空如也</div>
    </div>
    <script>
        new Vue({
            el:"#box",
            data:{
                list:["111","222","333"],
                mytext:""
            },
            methods:{
                handleAdd(){
                    //获取输入框内容
                    console.log(this.mytext)
                    //将内容加入到数据列表
                    this.list.push(this.mytext)
                    //将数据框清空
                    this.mytext=""
                },
                handleDel(index){
                    //从当前位置删除一个
                    this.list.splice(index,1)
                }
            }
        })
    </script>
</body>
</html>

3、动态切换class和style。

如果class或者style只有两种类型,那么可以使用三目运算符,但是如果有多个又该如何呢?在data中设置一个classobj对象,其包含对应的多种样式类型,如果true就会显示,反之false就会隐藏。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .aa{
​
        }
        .bb{
​
        }
        .cc{
​
        }
    </style>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div :class="classobj">动态切换class-1-对象</div>
    </div>
    <script>
        var vm = new Vue({
            el:'#box',
            data:{
                classobj:{
                    aa:true,
                    bb:true,
                    cc:false
                }
            }
        })
    </script>
</body>
</html>

上面会出现如下问题:如果在浏览器控制台中设置vm.classobj.dd=true,那么在元素中无法显示dd,当你打印vm.classobj时,你发现dd没有被拦截,即浏览器无法监听到你设置的属性!

解决方案是:Vue.set(vm.classobj,"dd",true),这样输入vm.classobj时,就会发现dd被拦截了,即浏览器可以监听到你设置的属性!

Vue.set(对象,属性,true/false)

还有一种方法是使用数组!如果在浏览器控制台中设置vm.classarr.push("c"),那么在元素中可以显示c。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .aa{
​
        }
        .bb{
​
        }
        .cc{
​
        }
    </style>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div :class="classobj">动态切换class-1-对象</div>
        <div :class="classarr">动态切换class-2-数组</div>
    </div>
    <script>
        var vm = new Vue({
            el:'#box',
            data:{
                classobj:{
                    aa:true,
                    bb:true,
                    cc:false
                },
                classarr:["a","b"]
            }
        })
    </script>
</body>
</html>

那么如何设置style呢?同样有对象和数组两种方法!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .aa{
​
        }
        .bb{
​
        }
        .cc{
​
        }
    </style>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div :class="classobj">动态切换class-1-对象</div>
        <div :class="classarr">动态切换class-2-数组</div>
        <div :style="styleobj">动态切换style-1-对象</div>
    </div>
    <script>
        var vm = new Vue({
            el:'#box',
            data:{
                classobj:{
                    aa:true,
                    bb:true,
                    cc:false
                },
                classarr:["a","b"],
                styleobj:{
                    backgroundColor:'red'
                }
            }
        })
    </script>
</body>
</html>

上面会出现如下问题:如果在浏览器控制台中设置vm.styleobj.backgroundColor="yellow",那么该属性会被改变,但是如果在浏览器控制台中增加属性,比如设置vm.styleobj.fontSize="30px",那么是不会被拦截的!

解决方案是:Vue.set(vm.styleobj,"fontSize","30px"),这样输入vm.styleobj时,就会发现fontSize被拦截了,即浏览器可以监听到你设置的属性!

Vue.set(对象,属性,属性值)  //一定要刷新页面!style部分属性要写成驼峰形式!

还有一种方法是使用数组!如果在浏览器控制台中设置vm.stylearr.push({fontSize:"30px"}),那么在元素中可以显示fontSize。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .aa{
​
        }
        .bb{
​
        }
        .cc{
​
        }
    </style>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div :class="classobj">动态切换class-1-对象</div>
        <div :class="classarr">动态切换class-2-数组</div>
        <div :style="styleobj">动态切换style-1-对象</div>
        <div :style="stylearr">动态切换style-2-数组</div>
    </div>
    <script>
        var vm = new Vue({
            el:'#box',
            data:{
                classobj:{
                    aa:true,
                    bb:true,
                    cc:false
                },
                classarr:["a","b"],
                styleobj:{
                    backgroundColor:'red'
                },
                stylearr:[{backgroundColor:'yellow'}]
            }
        })
    </script>
</body>
</html>

4、条件渲染。

两种:v-if和v-else。

v-if v-else
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <div v-if="isCreated">111111111</div>
        <div v-else>22222222</div>
    </div>
    <script>
        var vm=new Vue({
            el:"#box",
            data:{
                isCreated:false
            }
        })
    </script>
</body>
</html>

多种:v-if、v-else-if以及v-else。

v-if  v-else-if  v-else
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <!-- <div v-if="isCreated">111111111</div>
        <div v-else>22222222</div> -->
        <ul>
            <li v-for="data in list">
                {{data.title}} --
                <span v-if="data.state===0">未付款</span>
                <span v-else-if="data.state===1">待发货</span>
                <span v-else-if="data.state===2">已发货</span>
                <span v-else-if="data.state===3">运输中</span>
                <span v-else="data.state===4">已签收</span>
            </li>
        </ul>
    </div>
    <script>
        var vm=new Vue({
            el:"#box",
            data:{
                isCreated:false,
                list:[
                    {
                        title:"111111",
                        state:0
                    },
                    {
                        title:"222222",
                        state:1
                    }
                    ,
                    {
                        title:"333333",
                        state:2
                    }
                    ,
                    {
                        title:"444444",
                        state:3
                    }
                    ,
                    {
                        title:"555555",
                        state:4
                    }
                ]
            }
        })
    </script>
</body>
</html>

多个盒子同生共死:外面套个template并且设置v-if,其并不影响页面结构。

<template v-if="isCreated">
    <div>111111111</div>
    <div>222222222</div>
    <div>333333333</div>
</template>

5、列表渲染。

v-for的in和of没有区别。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <ul>
            <li v-for="item in arr">{{item}}</li>
        </ul>
        <ul>
            <li v-for="item of arr">{{item}}</li>
        </ul>
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                arr:["111","222","333"]
            }
        })
    </script>
</body>
</html>

虚拟dom,diff算法:一般将item.id作为列表的key值。

为什么使用虚拟dom?因为一个dom的属性成千上万,而很多属性是不常用的,如果直接创建dom,那么会较为复杂,故一般选择dom的较为重要的几个属性来创建虚拟dom。

为什么给列表加key值?一般对于列表的增删改查需要对比原有的虚拟dom和现有的虚拟dom,而有的列表数据多至成千上万条,该如何比较呢?这时就需要给列表元素加一个唯一标记值,从而方便对比,以最小的代价更新虚拟dom树。

将index作为key值可以吗?不可以!因为对列表进行增删改查后,对应的index会发生变化,那么再对比index就可能出错,因为增删改查后index发生改变,从而使得原来的数据和新的数据没有联系,从而失去了key的意义。

 <li v-for="(item,index) in arr" :key="item.id">{{item}}-{{index}}</li>

6、模糊查询。

当输入mytext后,从datalist中过滤出包含mytext的元素,但是filter函数并不改变原有数组,故不会引起页面的变化;如果直接将过滤后的数组赋值给datalist,那么就会出现无法复原的问题;所以直接拷贝一份datalist到originlist中,每次从originlist中过滤出包含mytext的元素,再将过滤后的数组赋值给datalist即可。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <input type="text" @input="handleInput" v-model="mytext">
        <ul>
            <li v-for="data in datalist">{{data}}</li>
        </ul>
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                mytext:"",
                datalist:["aaa","bbb","ccc","ddd","eee","fff","abc","acw"],
                originlist:["aaa","bbb","ccc","ddd","eee","fff","abc","acw"],
            },
            methods:{
                handleInput(){
                    console.log(this.mytext)
                    this.datalist = this.originlist.filter(item=>item.includes(this.mytext))
                    console.log(this.datalist)
                }
            }
        })
    </script>
</body>
</html>

那么有没有其他的解决方法呢?当然有!每次mytext改变则会拦截所有涉及mytext的dom或者方法,故test()函数会被拦截,而其中使用的filter方法又不会影响原数组datalist,故可以轻松实现!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        <input type="text" @input="handleInput" v-model="mytext">
        <ul>
            <li v-for="data in test()">{{data}}</li>
        </ul>
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                mytext:"",
                datalist:["aaa","bbb","ccc","ddd","eee","fff","abc","acw"],
                originlist:["aaa","bbb","ccc","ddd","eee","fff","abc","acw"],
            },
            methods:{
                handleInput(){
                    console.log(this.mytext)
                    this.datalist = this.originlist.filter(item=>item.includes(this.mytext))
                    console.log(this.datalist)
                },
                test(){
                    return this.datalist.filter(item=>item.includes(this.mytext))
                }
            }
        })
    </script>
</body>
</html>

7、事件处理。

当使用@绑定事件时,如果对应的函数不加小括号,则可以使用evt获取对应的事件对象,其中evt.target是事件源,evt.target.value是事件源对象的值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        {{count}}
        <input type="text" @input="handleInput">
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                count:1,
            },
            methods:{
                handleInput(evt)
                {
                    //evt是系统传递过来的事件对象
                    //evt.target是事件源 即谁触发了事件
                    //evt.target.value是事件源的值
                    console.log("input:",evt.target.value)
                }
            }
        })
    </script>
</body>
</html>

如果又想得到系统事件,又想得到用户传递的参数,那该如何呢?第一个参数必须使用$event!!!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        {{count}}
        <input type="text" @input="handleInput">
        <button @click="handleAdd($event,1,2,3)">add</button>
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                count:1,
            },
            methods:{
                handleInput(evt)
                {
                    //evt是系统传递过来的事件对象
                    //evt.target是事件源 即谁触发了事件
                    //evt.target.value是事件源的值
                    console.log("input:",evt.target.value)
                },
                handleAdd(evt,a,b,c)
                {
                    this.count++
                    console.log(evt,a,b,c)
                }
            }
        })
    </script>
</body>
</html>

其实还有简写方法:@click="count--"。(一般逻辑特别简单才使用这种方法)

<button @click="count--">del</button>

正常情况下,当ul和li均有点击事件时,点击li会传递到ul,从而产生事件传播。那么如何阻止呢?

<ul @click="handleUclick">
    <li @click="handleLclick">111</li>
</ul>

解决方法:使用事件修饰符,即在li上使用click.stop或者在ul上使用click.self。

<ul @click.self="handleUclick">
    <li @click.stop="handleLclick">111</li>
</ul>

通俗理解,事件修饰符可以认为是,在事件的某些阶段进行处理。那么假设需要在回车的时候进行处理,又该如何处理呢?

<input type="text" @keyup="handleKeyup">
handleKeyup(evt)
{
   //回车键的keyCode是13
   if(evt.keyCode===13)
     console.log("li add",evt.target.value)
}

解决方法:使用按键修饰符,即在input上使用keyup.enter。

<input type="text" @keyup.enter="handleKeyup">
handleKeyup(evt)
{
    console.log("li add",evt.target.value)
}

v-model双向绑定:即当input输入框中内容改变时,对应mytext显示就会立刻改变。如果不想这么迅速呢?使用v-model.lazy即可实现在input输入框失去焦点时才显示,这样就不至于input一改变就导致对应涉及input内容的地方全部被拦截一遍。

<div>
   <input type="text" v-model.lazy="mytext">
   {{mytext}}
</div>

number类型的input:其对应的value值仍然是字符串形式,如果想要使其变为数字类型,即在控制台输入vm.myage得到的不是'5'而是5,则使用v-model.number即可实现。

<div>
  <input type="number" v-model.number="myage">
</div>

当要求用户输入用户名等信息时,用户可能会手抖在首部或者尾部多输入空格,如何去除输入框的首尾空格?使用v-model.trim即可实现。

<div>
   <input type="text" v-model.trim="myname">
</div>

8、表单控件。

如果需要获取是否勾选“记住用户名”,那么可以将其双向绑定一个bool类型变量,这样是否勾选就会映射bool。

<div>
    <label>用户名:<input type="text" v-model="mytext"></label>
    <input type="checkbox" v-model="isRemember">记住用户名
    <button @click="handleLogin">登录</button>
</div>
​
data:
{
    mytext:localStorage.getItem("username"),
    isRemember:true
},
methods:
{
     handleLogin()
     {
         if(this.isRemember)
            localStorage.setItem("username",this.mytext)
     }
}

那如果是多选框呢?也要一个个绑定吗?可以将其绑定到一个数组,同时将每一个选择框设置value,这样勾选选择框时就会将其对应的value映射到数组。

<div>
   <h2>注册页面-兴趣爱好</h2>
   <input type="checkbox" v-model="checkList" value="vue">vue
   <input type="checkbox" v-model="checkList" value="react">react
   <input type="checkbox" v-model="checkList" value="wx">小程序
</div>

那如果是单选框呢?可以将其绑定到一个字符串变量,同时将每一个选择框设置value,这样勾选选择框时就会将其对应的value映射到字符串变量。

<div>
   <h2>性别选择</h2>
   <input type="radio" v-model="select" value="a">男
   <input type="radio" v-model="select" value="b"></div>

注意:单个radio绑定布尔值,多个radio绑定字符串变量,单个checkbox绑定布尔值,多个checkbox绑定字符串数组。

vue架构模式:mvvm(双向数据绑定)。

@click和@change事件的区别:@click是在内容点击的时候触发,@change是在内容改变的时候触发。注意,点击不一定改变。(一般用于单选框或者多选框)

@input和@change事件的区别:@input是输入框内容发生改变则触发,@change是输入框内容发生改变并且失去焦点才触发。

9、计算属性computed。

一般来说,模板不能过重,否则难以维护,所以逻辑一般放在js中,这时就需要使用计算属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="lib/vue.js"></script>
</head>
<body>
    <div id="box">
        {{myComputedName}}
    </div>
    <script>
        var vm = new Vue({
            el:"#box",
            data:{
                myname:"wxm",
            },
            computed:{
                myComputedName()
                {
                    return this.myname.substring(0,1).toUpperCase()+this.myname.substring(1)
                }
            }
        })
    </script>
</body>
</html>

计算属性一般写在computed部分,其定义的像方法,使用的像属性。计算属性和方法的区别是,计算属性有缓存,即假如在一个页面中使用某一计算属性三次,那么其只会计算一次,而方法则是被调用三次。

10、监听watch。

计算属性需要立即得到结果,那如何存在异步呢?这不就来了watch嘛!

<input type="text" v-model="mytext">
watch:
{
    //mytext对应的mytext属性 newval对应的是新值
    mytext(newval)
    {
        console.log("改变了",newval)
        setTimeout(()=>{
           this.datalist = this.originlist.filter(item=>item.includes(newval))
        },2000)
     }
}

一般涉及到异步就会使用watch!watch写在watch中!

methods、computed以及watch的区别:

①methods:事件绑定!可以不用return!没有缓存!
​
②computed:必须有return!多次调用有缓存!但只能做同步!
​
③watch:异步!不用返回值!

11、fetch。

XMLHttpRequest和fetch是两种标准。

get:

handleFetch()
{
//第一个.then只能拿到状态码和请求头而拿不到真的数据                 fetch("./json/test.json").then(res=>res.json()).then(res=>console.log(res)).catch(err=>console.log(err))
}

post:

handleFetchPOST1()
{
   var obj=
   {
     method:'post',
     headers:
     {
        "Content-Type":"application/x-www-form-urrlencoded"
     },
     body:"name=wxm&age=18"
   }                   fetch("./json/test.json",obj).then(res=>res.json()).then(res=>console.log(res)).catch(err=>console.log(err))
}
handleFetchPOST2()
{
    var obj=
    {
        method:'post',
        headers:
        {
           "Content-Type":"application/json"
        },
        body:JSON.stringify({
             name:"wxm",
             age:100
        })
    }               fetch("./json/test.json",obj).then(res=>res.json()).then(res=>console.log(res)).catch(err=>console.log(err))
}

应用:

<button @click="handleFetchMAOYAN">MAOYAN</button>
<ul>
   <li v-for="item in datalist" :key="item.id">
       {{item.nm}}
   </li>
</ul>
handleFetchMAOYAN()
{
    //第一个.then只能拿到状态码和请求头而拿不到真的数据
    fetch("./json/maoyan.json").then(res=>res.json()).then(res=>
    {
         console.log(res.movieList)
         //将获取到的数据存储到data
         this.datalist=res.movieList
    }).catch(err=>console.log(err))
}

12、axios。

使用axios必须引用axios.js。

get:

handleClick()
{                  axios.get("./json/maoyan.json").then(res=>console.log(res.data.movieList))
}

post:

handleClickPOST1()
{
var obj="name=wxm&age=18"                axios.post("./json/maoyan.json",obj).then(res=>console.log(res.data.movieList))
}               
handleClickPOST2()
{
   var obj=
   {
      name:"wxm",
      age:18
   }                 axios.post("./json/maoyan.json",obj).then(res=>console.log(res.data.movieList))
}

axios比fetch更简单,可以等价理解为少一层then。

如何处理后端返回的数据格式呢?原始为w.h,可以根据实际需求修改w和h,此处统一处理为128和180。

原始:http://p0.meituan.net/w.h/movie/056d4f1a658e3e35c9f9e6c44c2aa3fe1831930.jpg
结果:http://p0.meituan.net/128.180/movie/056d4f1a658e3e35c9f9e6c44c2aa3fe1831930.jpg

解决方案一:可以使用函数来处理。

<ul>
  <li v-for="data in datalist" :key="data.id">
  <img :src="handleImg(data.img)">
  {{data.nm}}
  </li>
</ul>
handleImg(url)
{
   return url.replace('w.h','128.180')
}

解决方案二:可以使用过滤器来处理。

<ul>
   <li v-for="data in datalist" :key="data.id">
   //使用管道将数据data.img传送到过滤器imgFilter中进行处理
   <img :src="data.img | imgFilter">
   {{data.nm}}
   </li>
</ul>
//第一个参数是过滤器名称 第二个是回调函数
Vue.filter("imgFilter",(url)=>{
   return url.replace("w.h","128.180")
})

13、组件。

什么是组件?扩展html元素!封装可重用代码!(将dom、css和js封装在一起)

定义全局组件:其和vue对象结构类似,唯一的区别是其中的data必须是方法形式!!!

<div id="box">
   //命名:js驼峰 html连接符-
   <navbar></navbar>
</div>
<script>
//定义一个全局组件 组件之间无法通信
Vue.component("navbar",{
     //只能包含一个根节点!!! dom没有提示!css只能行内!
     template:`<div>
                <button @click="handleLeft">left</button>
                <span>wxm</span>
                <button @click="handleRight">right</button>
                </div>`,
     methods:{
        handleLeft(){
           console.log("left")
        },
        handleRight(){
           console.log("right")
        }
     },
     computed:{
​
     },
     watch:{
​
     },
     //data必须是函数形式!
     data(){
        return {
                    
               }
     }
})

上面写法也存在一系列问题:

1、命名:组件中的js推荐驼峰命名,组件中的html推荐连接符-命名;
2templatetemplate中只允许一个根节点,其dom没有高亮提示,css只能写成行内样式;
3、data:data必须是函数方法形式;
4、component:component之间无法通信;

定义局部组件:局部组件一般定义在components对象部分!其只能在其所定义的组件内部使用!而不能在外部使用!!(全局组件在任何地方均可以使用)

//定义一个全局组件
Vue.component("navbar",{
    template:`<div>
                 <button @click="handleLeft">left</button>
                 <span>wxm</span>
                 <button @click="handleRight">right</button>
                 <child></child>
              </div>`,
    methods:{
       handleLeft(){
          console.log("left")
       },
       handleRight(){
          console.log("right")
       }
    },
    computed:{
​
    },
    watch:{
​
    },
    data(){
       return {
​
       }
    },
    components:{
       "child":{
          template:`<div>child</div>`
        }
    }
})

父传子:在父组件中,将参数加在子组件的属性上,并在子组件的定义中,设置props数组接受参数,再在模板中显示参数即可。(实现自适应改变多功能复用)

<div id="box">
   <div style="background:yellow;">根组件标题</div>
   //将参数加上子组件的属性上
   <navbar myname="wxm"></navbar>
   <navbar myname="www"></navbar>
</div>
<script>
   Vue.component("navbar",{
      //在子组件定义中设置props数组接受父组件的参数
      props:["myname"],
      template:`<div>
                   <button>left</button>
                   //在模板中显示参数
                   <span>{{myname}}</span>
                   <button>right</button>
                </div>`
      })
      new Vue({
         el:"#box"
      })
</script>

如果使用组件的人员,并不知道应该传递什么类型的参数,一旦传参类型错误且收不到反馈,那岂不是很折磨人?有没有什么方法可以解决该问题呢?设置传参类型!

<div id="box">
     <div style="background:yellow;">根组件标题</div>
     <navbar myname="wxm" :myright="false"></navbar>
     <navbar myname="www" :myright="true"></navbar>
</div>
<script>
   Vue.component("navbar",{
      //接受父组件的参数
      //props:["myname"],
      //防止父组件传递参数类型错误且收不到反馈故设置类型 
      props:{
         myname:String,
         myright:Boolean
      },
      template:`<div>
         <button>left</button>
         <span>{{myname}}</span>
         <button v-show="myright">right</button>
        </div>`
      })
   new Vue({
      el:"#box"
   })
</script>

如何实现不传参则使用默认值,传参才设置对应参数呢?别急!有办法!

<div id="box">
     <div style="background:yellow;">根组件标题</div>
     //不加:则一律当做字符串处理 加:则当做js处理
     <navbar myname="wxm" :myright="false"></navbar>
     <navbar myname="www" :myright="true"></navbar>
</div>
<script>
   Vue.component("navbar",{
      //接受父组件的参数
      //props:["myname"],
      //防止父组件传递参数类型错误且收不到反馈或者没有传递参数而无法达到显示效果故设置类型 
      props:{
         myname:{
            type:String,
            default:""
         },
         myright:{
            type:Boolean,
            default:true
         }
      },
      template:`<div>
         <button>left</button>
         <span>{{myname}}</span>
         <button v-show="myright">right</button>
        </div>`
      })
   new Vue({
      el:"#box"
   })
</script>

父传子:将父组件的data数据转换为属性传递给子组件!!!(属性)

那么如何实现子传父呢???

子传父:将父组件的methods事件转换为事件传递给子组件!!!在子组件对应位置触发该事件!!!(事件)

<div id="box">
   <navbar @event="handleEvent"></navbar>
   <sidebar v-show="isShow"></sidebar>
</div>
<script>
   Vue.component("navbar",{
       template:`
       <div style="background-color:red;">
       <button @click="handleClick()">点击</button>-导航栏
       </div>
       `,
       methods:{
          handleClick(){
             console.log("子传父,告诉父组件,取反您的isShow")
             //触发事件
             this.$emit("event")
          }
       }
  })
  Vue.component("sidebar",{
       template:`
       <div style="background-color: yellow;">
       <ul>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
          <li>111111</li>
       </ul>
       </div>
       `
 })
 new Vue({
     el:'#box',
     data:{
         isShow:true
     },
     methods:{
         handleEvent(){
             console.log("父组件定义的事件")
             this.isShow=!this.isShow
         }
     }
 })
</script>

父组件给子组件传递数据是为了更好的复用,使用的方法是将父组件的data数据转换为属性传递给子组件;子组件给父组件传递数据是为了实际的需求,使用的方法是将父组件的methods事件转换为事件传递给子组件并且在子组件的对应位置触发该事件。

中间人模式:如果是父组件的两个同级子组件相互传递数据呢?该如何实现呢?子传给父,父再传给子。注意,子组件传递数据给父组件时应该将数据存在父组件的data中,以便后续使用,否则传递的数据是临时数据,无法在页面中显示。(亲兄弟传递)

<div id="box">
   <button @click="handleAjax">ajax</button>
   <film-item v-for="item in datalist" :key="item.filmId" :mydata="item"  @event="handleEvent"></film-item>
   <film-detail :film-data="filmData"></film-detail>
</div>
<script>
   Vue.component("filmItem",{
      props:["mydata"],
      template:`
         <div class="item">
         <img :src="mydata.poster"/>
            {{mydata.name}}
         <div>
         <button @click="handleClick">详情</button>
         </div>
         </div>
       `,
       methods:{
         handleClick(){
            // console.log(this.mydata.synopsis)
            this.$emit("event",this.mydata.synopsis)
         }
       }
   })
   Vue.component("filmDetail",{
       props:["filmData"],
       template:`
          <div class="filminfo">
             {{filmData}}    
          </div>
       `
   })
   new Vue({
       el:"#box",
       data:{
         datalist:[],
         filmData:""
       },
       mounted(){
          fetch("./json/test.json")
          .then(res=>res.json())
          .then(res=>{
               console.log(res.data.films)
               this.datalist = res.data.films
          })
       },
       methods:{
          handleAjax(){
             fetch("./json/test.json")
             .then(res=>res.json())
             .then(res=>{
                  console.log(res.data.films)
                  this.datalist = res.data.films
             })
         },
         //自定义事件处理器
         handleEvent(data){
             console.log("父组件定义",data)
             this.filmData = data
         }
      }
    })
</script>

如果是任意两个组件通信呢?如果一个地方任意两个组件通信则使用bus中央事件总线;如果七八个地方任意两个组件通信则使用vuex状态管理。

bus中央事件总线是订阅发布模式:订阅者是bus.on(event,()=>)监听事件;发布者是bus.on('event',()=>{})监听事件;发布者是bus.emit('event')触发事件;中央事件总线是bus=new Vue()。

<div id="box">
   <button @click="handleAjax">ajax</button>
   <film-item v-for="item in datalist" :key="item.filmId" :mydata="item"></film-item>
   <film-detail></film-detail>
</div>
<script>
   //中央总线处理事件
   //bus.$on监听事件
   //bus.$emit触发事件
   var bus=new Vue()
   Vue.component("filmItem",{
      props:["mydata"],
      template:`
         <div class="item">
         <img :src="mydata.poster"/>
         {{mydata.name}}
         <div>
         <button @click="handleClick">详情</button>
         </div>
         </div>
      `,
      methods:{
         handleClick(){
            // console.log(this.mydata.synopsis)
            bus.$emit('wxm',this.mydata.synopsis)
         }
      }
   })
   Vue.component("filmDetail",{
      //组件刚刚创建好就开始订阅
      //生命周期
      mounted(){
         bus.$on('wxm',(data)=>{
             this.info=data
         })
      },
      template:`
         <div class="filminfo">
         {{info}}
         </div>
      `,
       data(){
         return {
            info:""
         }
       }
    })
    new Vue({
      el:"#box",
      data:{
        datalist:[],
      },
      mounted(){
        fetch("./json/test.json")
        .then(res=>res.json())
        .then(res=>{
            console.log(res.data.films)
            this.datalist = res.data.films
        })
      },
      methods:{
        handleAjax(){
          fetch("./json/test.json")
          .then(res=>res.json())
          .then(res=>{
              console.log(res.data.films)
              this.datalist = res.data.films
          })
        },
     }
   })
</script>

即:中间者模式是子传父再父传子;而bus中央事件处理模式是创建全局变量var bus=new Vue()。子传父的子是使用bus.emit触发事件;父传子的子是使用bus.emit触发事件;父传子的子是使用bus.on监听事件。子传父是将父组件的方法转换为事件传递给子组件,子组件再在需要的地方触发该事件;父传子是将父组件的状态转换为属性传递给子组件,子组件再使用props接收对应的数据。

ref组件通信:组件通信还有一种方式即ref组件通信,其是在标签上绑定一个ref属性,那么就可以通过this.$refs.属性值拿到对应的内容了。如果ref绑定的是dom元素,那么拿到的就是dom对象;如果ref绑定的是组件,那么拿到的就是组件对象。

<div id="box">
  <input type="text" ref="mytext">
  <input type="password" ref="mypassword">
  <button @click="handleClick">add</button>
  <child ref="mychild"></child>
</div>
<script>
  Vue.component('child',{
    data(){
       return {
          myname:"child-1111111111111"
       }
    },
    template:`<div>
              child---{{myname}}
              </div>`
  })
  new Vue({
     el:'#box',
     methods:{
        handleClick(){
           this.$refs.mychild.myname="child-2222222222222"
        }
     }
  })
</script>

ref组件通信可以较为方便的获取元素内容,但是由于父组件可以改变子组件元素内容,故容易造成数据流的紊乱,即子组件内心os:谁改变了我的内容???(一般不建议使用)

状态data可以随意更改,属性props不可随意更改(否则容易造成数据流紊乱)。

动态组件:切换显示与隐藏。

<component :is="which"></component>  

但是在同一页面中,当组件发生切换时,页面会被重新渲染,如果原本页面存在用户输入的内容,那么再次切换回来时,重新渲染会导致用户输入的内容丢失,这又该如何解决呢?

<keep-alive>
  <component :is="which"></component>
</keep-alive>

14、slot。

自定义组件标签内部是否可以插入其他内容呢?当然可以!如果可以,又该如何实现呢?

Vue2旧slot:

<div id="box">
   <child>
     <div>111111111111111111111111</div>
   </child>
</div>
<script>
  Vue.component("child",{
     template:`
        <div>
        child
        <slot><slot>
        </div>
     `
  })
  new Vue({
    el:'#box'
  })
</script>

其会将自定义组件内部的所有元素均插入到<\slot>中!那么如何设置对应的元素插入到对应的插槽中呢?

<div id="box">
  <child>
    <div slot="a">111111111111111111111111</div>
    <div slot="b">222222222222222222222222</div>
    <div slot="c">333333333333333333333333</div>
    <div>444444444444444444444444</div>
  </child>
</div>
<script>
   Vue.component("child",{
     template:`
       <div>
         child
         <slot name='a'></slot>
         <slot name='b'></slot>
         <slot name='c'></slot>
         <slot></slot>
       </div>
     `
  })
  new Vue({
    el:'#box'
  })
</script>

Vue2新slot:

<div id="box">
  <child>
   //使用指令v-slot 使用:a表示对应的名字为a
   <template v-slot:a>
     <div>111111111111111111111111</div>
   </template>
   <template v-slot:b>
     <div>222222222222222222222222</div>
   </template>
   //也可以简写为#c c是名字
   <template #c>
     <div>333333333333333333333333</div>
   </template>
   <template>
     <div>444444444444444444444444</div>
   </template>
  </child>
</div>
<script>
  Vue.component("child",{
    template:`
      <div>
        child
        <slot name='a'></slot>
        <slot name='b'></slot>
        <slot name='c'></slot>
        <slot></slot>
      </div>
   `
  })
  new Vue({
    el:'#box'
  })
</script>

插槽的意义在于:扩展组件能力,提高组件复用性。

15、过渡。

vue中的过渡可以控制在显示时设置进入动画以及在隐藏时设置退出动画,这样就不需要我们手动去切换class。

<transition enter-active-class="kerwin-enter-active" leave-active-class="kerwin-leave-active">
  <div v-show="isShow">1111111111111111</div>
</transition>

transition还有简写方式。

<transition name="kerwin">
  <div v-if="isShow">
    <div >222222222222222222</div>
    <div >333333333333333</div>
    <div >44444444444444444</div>
    <div>55555555555555555</div>
  </div>
</transition>
​
.kerwin-enter-active   //css部分  进入动画名
.kerwin-leave-active   //css部分  退出动画名

如果希望初始transition就出现动画则需要加上appear。

<transition name="kerwin" appear>
  <div v-if="isShow">222222222222222222</div>
</transition>

如果transition中存在多个v-if和v-else标签:那么就会涉及到diff算法。

diff算法:同层级对比;同标签对比;同组件对比;同key对比。

<transition name="kerwin" mode="out-in">
   //标签均为div则尽最大可能保留标签而选择直接替换内容故没有动画效果
   <div v-if="isShow">111111111111111</div>
   <div v-else>2222222222222222222222222</div>
</transition>
//out-in指的是先走再来
<transition name="kerwin" mode="out-in">
   //标签一个为div一个为p则直接删掉div添加p故有动画效果
   <div v-if="isShow">111111111111111</div>
   <p v-else>2222222222222222222222222</p>
</transition>
<transition name="kerwin" mode="out-in">
   //虽然标签相同但是存在key故按照相同的key进行对比故有动画效果
   <div v-if="isShow" key="1">111111111111111</div>
   <div v-else key="2">2222222222222222222222222</div>
</transition>

diff算法原理:diff算法使用patch来修改dom的一小段,从而不会引起整颗dom树的重绘。当数据发生改变时,set方法会调用dep.notify通知所有订阅者watcher,订阅者watcher就会调用patch给真实的dom打补丁,从而更新相应的视图。通过isSameVnode进行判断,相同则调用patchVnode方法,patchVnode做了以下操作:找到对应的真实dom,称为el,如果都有文本节点且不相等,将el文本节点设置为Vnode的文本节点;如果oldVnode有子节点而VNode没有,则删除el子节点;如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el;如果两者都有子节点,则执行updateChildren函数比较子节点。updateChildren主要做了以下操作:设置新旧VNode的头尾指针,新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找key一致的VNode节点再分情况操作。devpress.csdn.net/viewdesign/…

多个组件的过渡:使用transition。

<keep-alive>
 <transition name="kerwin" mode="out-in">
   <component :is="which"></component>
 </transition>
</keep-alive>

多个列表的过渡:使用transition-group。

//transition-group控制多个元素而transition只能控制一个元素
//transition-group默认实例化为span标签 可以使用tag="ul"将外部实例化为ul元素
<transition-group name="kerwin" tag="ul" v-show="datalist.length">
     //使用transition-group必须使用key且不能为index
     <li v-for="(item,index) in datalist" :key="item">
        {{item}}--{{index}}
        <button @click="handleDel(index)">del</button> 
     </li>
</transition-group>

可复用过渡:将动画封装在组件内部,这样使用组件的同时也可使用动画。

16、生命周期。

生命周期钩子函数指的是Vue实例在某一个时间点会自动执行的函数。

生命周期主要分为三个阶段八个函数。

创建阶段:beforeCreate、created、beforeMount、mounted。创建阶段的四个函数在生命周期中只会被执行一次。

beforeCreate:无法访问data;
created:可以访问data;主要用于初始化状态或者挂载到当前实例的一些属性;首先判断当前Vue实例是否有el属性,如果没有则会是vm.$mount("#box");接着判断当前Vue实例是否有template属性,如果有则直接渲染template内容,反之则直接渲染el对应的html内容;
beforeMount:拿到未解析的模板;
mounted:拿到解析后的真实的dom节点;

更新阶段:beforeUpdate、updated。更新阶段的两个函数在生命周期中只要dom被更新就会被触发。

beforeUpdate:主要用于更新之前记录olddom的某些状态;
updated:主要用于更新之后拿到newdom;

销毁阶段:beforeDestroy、destroyed。销毁阶段的两个函数在生命周期中一般用于子组件被销毁前做善后工作,比如清楚定时器以及清掉绑定在window上等事件。

beforeDestroy:主要用于清楚定时器以及事件绑定;
destroyed:主要用于清楚定时器以及事件绑定;

一般常用的是:created、mounted、updated、destroyed。

<div id="box">
   {{myname}}
   <button @click=" myname = ' xiaoming' ">change</button>
   <child v-if="isCreated"></child>
</div>
<script>
   Vue.component("child",{
     template:`
       <div>
         抢购倒计时组件
         <div>{{time}}</div>
       </div>
     `,
     data(){
        return {
           time:1000
        }
     },
     created(){
        //不需要保存为状态 只是临时信息
        this.id = null
     },
     mounted(){
        this.id = setInterval(()=>{
            console.log("倒计时")
            this.time--;
        },1000)
     },
     //销毁阶段:一般用于子组件做善后工作 主要去除绑定在window上的事件
     beforeDestroy(){
        console.log("beforeDestroy","清楚定时器以及事件绑定")
        clearInterval(this.id)
     },
     destroyed(){
        console.log("destroyed","清楚定时器以及事件绑定")
     }
    })
    var vm=new Vue({
       el:'#box',
       data:{
         myname:"wxm",
         isCreated:true
       },
       //创建阶段:以下四个钩子函数只会执行一次
       beforeCreate(){
           console.log("beforeCreate",this.myname)
       },
       created(){
           console.log("created","初始化状态或者挂载到当前实例的一些属性")
           this.myname=this.myname+"111111111111111111"
       },
       beforeMount(){
           //拿到未解析的模版
           console.log("beforeMount",document.getElementById("box").innerHTML)
       },
       mounted(){
           //拿到真实的dom节点
           console.log("mounted",document.getElementById("box").innerHTML)
           //订阅bus.$on
           //发送ajax
       },
       //更新阶段:有更新就被触发
       beforeUpdate(){
           console.log("beforeUpdate","更新之前记录olddom的某些状态")
       },
       updated(){
           console.log("updated","更新之后拿到newdom")
       },
       //销毁阶段:
    })
</script>

总结:在自定义组件中,注意巧妙使用生命周期钩子函数!

17、指令。

指令:为了操作底层dom。指令最重要的是知道何时dom被创建完。

<div id="box">
   <!-- 自定义指令 属性值对应的是状态  状态对应的是字符串 故直接传递一个字符串也是可以的-->
   <div v-hello=" 'red' ">11111111111111111111</div>
   <div v-hello=" 'yellow' ">2222222222222222222</div>
   <div v-hello="whichColor">33333333333333333333</div>
</div>
<script>
   //定义指令
   Vue.directive("hello",{
     //指令的生命周期 el是对应元素 binding是一个对象
     //第一次插入到父节点中触发
     inserted(el,binding){
        console.log("inserted",el,binding)
        el.style.background=binding.value
     },
     //更新则被触发
     update(el,binding){
        console.log("updated")
        el.style.background=binding.value
     }
   })
   var app=new Vue({
      el:"#box",
      data:{
         whichColor:"blue"
      }
   })
</script>

注意:指令对应的属性值(双引号内的内容)是状态,即data中的数据变量,由于变量对应的值也是字符串,所以直接传递字符串作为属性值也可以,注意是双引号中的内容又是一个字符串!!!

//定义指令
Vue.directive("hello",(el,binding)=>{
   //两者合一
   console.log("创建或者更新均可以执行")
})

其中定义指令可以使用一个回调函数进行二合一。

18、单文件组件。

Vue.component定义全局组件会存在以下问题:一是全局定义导致每个组件的命名不得重复;二是字符串模版缺乏语法高亮;三是不支持css;四是没有构建步骤。

文件扩展名为.vue的单文件组件可以解决以上所有问题,并且可以使用webpack等构建工具。

npm install -g @vue/cli    // 全局安装脚手架cli
vue create my-project   //创建项目

eslint就像一个有洁癖的人,每次代码写的稍微不合乎规范就会报错。

lintOnSave:false    //在vue.config.js中加上这一句关闭eslint

单文件组件示例:(分为三部分html、css、js,最少有html)

//html部分
<template>
  <div class="about">
    Hello --- {{ myname }}
    <ul>
      <li>111</li>
      <li>222</li>
      <li>333</li>
    </ul>
    <navbar></navbar>
  </div>
</template>
​
//js部分
<script>
//模块化开发 在哪里用在哪里引
import Vue from 'vue'
import navbar from '../components/Navbar.vue'
//单文件组件只是将大对象分为三个部分 但是需要引用时仍需要定义组件
Vue.component('navbar',navbar)
// ES6导出规范  -babel将es6转换为es5
export default {
  components: { navbar },
  data(){
    return {
      myname:"wxm"
    }
  }
}
</script>
​
//css部分
<style lang="scss">
  $width:300px;
  ul{
    li{
      background: yellow;
      width:$width
    }
  }
</style>

19、反向代理Proxy。

对于跨域问题,可以使用反向代理proxy解决。

  //配置反向代理proxy
  devServer:{
    //记得凡是更改vue.config.js均要重启
    proxy:{
      //向ajax发送请求的转换为向猫眼请求
      '/ajax':{
        //原本是拼接为https://m.maoyan.com/ajax...
        target:'https://m.maoyan.com',
        changeOrigin:true,
        //现在是拼接为https://m.maoyan.com/...
        pathRewrite:{
          '^/ajax':''
        }
      }
    }
  }
反向代理的基本原理如下:
​
客户端向反向代理服务器发起请求。
​
反向代理服务器接收到请求后,将请求转发给后端目标服务器。
​
后端目标服务器处理请求并生成响应。
​
反向代理服务器接收到响应后,将响应返回给客户端。
正向代理的基本原理如下:
​
客户端向正向代理服务器发起请求。
​
正向代理服务器接收到请求后,将请求转发给目标服务器。
​
目标服务器处理请求并生成响应。
​
正向代理服务器接收到响应后,将响应返回给客户端。

正向代理:客户端知道代理服务器的存在,主动将请求发送给代理服务器,并通过代理服务器与目标服务器通信。反向代理:客户端不知道代理服务器的存在,将请求发送给反向代理服务器,反向代理服务器根据配置将请求转发给目标服务器并返回响应给客户端。

@别名是src文件夹的绝对路径。

import navbar from '../components/Navbar.vue'    //无别名
import navbar from '@/components/Navbar.vue'     //含别名

20、路由。

单页面应用(Single Page Application,SPA)是一种 Web 应用程序的架构模式,其核心思想是在加载初始 HTML 页面后,通过使用 JavaScript 动态地更新页面的内容,而无需重新加载整个页面。(与多页面应用对比)

import Vue from 'vue'    //导入Vue
import VueRouter from 'vue-router'   //导入VueRouter
import HomeView from '../views/HomeView.vue'
import Films from '../views/Films.vue'
import Cinemas from '../views/Cinemas.vue'
import Center from '../views/Center.vue'Vue.use(VueRouter)  //注册组件const routes = [   //配置组件
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/films',
    name: 'films',
    component: Films
  },
  {
    path: '/cinemas',
    name: 'cinemas',
    component: Cinemas
  },
  {
    path: '/center',
    name: 'center',
    component: Center
  },
  // 万能匹配重定向
  {
    path: '*',
    redirect:'/'
  },
]
​
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})
​
export default router

注意,router-view类似于插槽,当url被更新后,其会在当前路由下切换到对应内容,而router-link类似于超链接,当url被更新后,其会跳转到对应内容。

<template>
  <div id="app">
    <nav>
      <!--使用router-link实现声明式导航 类似于超链接-->
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
      <router-link to="/films">Films</router-link>
      <router-link to="/cinemas">Cinemas</router-link>
      <router-link to="/center">Center</router-link>
    </nav>
    <!--使用router-view实现类似于插槽 url更新则在当前路由下切换到对应内容-->
    <router-view/>
  </div>
</template>

在使用vue-cli创建的项目中,一般将单页面应用.vue文件写在views文件夹下,然后在router文件夹的index.js中编写路由匹配,最后在主页面文件App.vue中设置声明式导航。

redirect:'/'
redirect:{
    name:'home'
}

二级路由如下:使用一个children数组存放子路由。

{
  path: '/films',
  name: 'films',
  component: Films,
  children:[
    {
      path: '/films/nowplaying',
      name: 'nowplaying',
      component: NowPlaying,
    },
    {
      path: '/films/futureplaying',
      name: 'futureplaying',
      component: FuturePlaying,
    },
  ]
}

注意,二级路由对应的页面一般是,views文件夹下编写xxx.vue文件,再在components文件夹下新建一个xxx文件夹,用于存放xxx.vue的子组件。

动态路由:/detail/:id。this.router拿到整个路由对象,this.router拿到整个路由对象,this.route拿到当前匹配路由。

this.$router.push(`/detail/${id}`)

命名路由:path即为路径路由,name即为命名路由,这样后续跳转既可以通过路径跳转,也可以通过命名跳转。(重定向也是同理)

this.$router.push({
    name:'films',
    params:{
        id
    }
})

路由模式:history模式和hash模式。其中hash模式通过在url中添加井号#来表示前端路由。

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

路由拦截:在路由跳转之前验证是否合法(登录验证)。路由拦截又分为全局拦截和局部拦截。

localStorage.setItem("token","wxm")   //在控制台模拟设置token字段
  {
    path: '/cinemas',
    name: 'cinemas',
    meta:{
      isRequired:true   //在需要验证的路由部分设置meta对应的isRequired字段
    },
    component: Cinemas
  }
//全局拦截 to去往哪里 from从哪里去 next表示下一步处理
router.beforeEach((to,from,next)=>{
  console.log(to)
  console.log(to.fullPath)
  // 判断是否有该字段
  if(to.meta.isRequired)  //有该字段拦截
  {
    //判断本地存储中是否有token
    if(localStorage.getItem('token'))  //有则跳转
      next()
    else    //没有则使用next跳转
      next('/')
  }
  else   //没有该字段放行
    next()
})

在使用next跳转时,也可以给其传递参数,这样就算被拦截,也可以在完成处理后再跳转到原来想跳转的地址。

/?redirect=%2Fcinemas
      next({
        path:'/',
        query:{
          redirect:to.fullPath  //传递参数
        }
      })

全局拦截是写在router上,而局部拦截是写在路由内。

  {
    path: '/center',
    name: 'center',
    component: Center,
    //局部拦截
    beforeEnter:(to,from,next)=>{
      if(localStorage.getItem('token'))
        next()
      else
        next('/')
    }
  },

注意,全局拦截是router.beforeEach,而局部拦截是beforeEnter:,但是其主体逻辑是类似的。

路由懒加载:当打包构建应用时,js包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应组件,这样就更加高效。

  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  }

注意,vue-cli创建项目,后续开发就变成了模块化开发,即不再是使用script引入,而是使用npm i下载,如果不太熟练,可以去npm对应网站或者对应模块官方网站上查看对应模块的用法。

21、组件库。

swiper:轮播图 npm i swiper --save --legacy-peer-deps (与eslint冲突)

element-ui:pc端 npm i element-ui -S --legacy-peer-deps (与eslint冲突)

vant:移动端 npm i vant@latest-v2 -S --legacy-peer-deps (与eslint冲突)

组件库最难的不是如何使用,而是如何将自己的业务逻辑加上去,这就需要去查官方文档啦。

22、vuex。

vuex主要用于管理公共状态(即某一变量在多个组件中均会被用到),以解决不同组件间状态共享和通信问题。

export default new Vuex.Store({
  //公共状态
  state: {
  },
  //获取公共状态
  getters: {
  },
  //改变公共状态的方法 被devtools实时监控 方便前端调试 同步修改数据
  mutations: {
  },
  //异步修改数据
  actions: {
  },
  //公共状态分块
  modules: {
  }
})

vuex主要是在store文件夹中。