Vue2学习笔记

1.安装手脚架

npm install -g @vue/cli

2.模板语法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>hello,{{name}}</h1>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            name:"你好"
        }
    })
</script>
</html>

3.数据绑定

  • 单向绑定 bind 数据只能从data流向页面
  • 双向绑定 v-model 数据不仅能从data流向页面,还能从页面流向data
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>插值语法</h1>
        <h2>hello,{{name}}</h2>
        <h1>单向数据绑定</h1>
        <input type="text" v-bind:value="str">
        <!-- 简写 -->
        <!-- <input type="text" v-bind:value="str"> -->
        <h1>双向数据绑定</h1>
        <input type="text" v-model:value="str">
        <!-- 简写 -->
        <!-- <input type="text" v-model="str"> -->
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const v = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            name:"你好",
            str:''
        }
    })
    console.log(v)
</script>
</html>

4.MVVM模型

  • M:模型(model):对应data中的数据
  • V:视图(View):模板
  • Vm:视图模型(ViewModel):Vue实例对象

5.数据代理的实现

vue中的数据放在实例_data上

_data相当于下面例子中的number

<!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>
</body>
<script type="text/javascript">
    let number = 18
    let person = {
        name:'zhangsan',
        sex:'男'
    }
    Object.defineProperty(person,'age',{
        // value:18,
        // enumerable:true,//是否可枚举,默认为false
        // writable:true,//是否可被修改,默认为false
        // configurable:true,//是否可被删除,默认为false
        // 当有人读取age时调用get函数,且age的值为返回值
        get(){
            console.log('读取age')
            return number
        },
        // 当有人修改age时会调用set函数且会收到修改值
        set(value){
            console.log('修改age')
            number = value
        }
    })
    console.log(person)
    console.log(Object.keys(person))
</script>
</html>

6.事件

6.1事件处理

  • 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名

  • 事件的回调需要配置在methods对象中,最终会在vm上

  • methods中配置的函数,都是被Vue管理的函数,this的指向是vm或组件实例对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>hello,{{name}}</h1>
        <button @click="showInfo(123)">按钮1</button>
        <button v-on:click="showInfo2(123,$event)">按钮2</button>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            name:"你好"
        },
        methods: {
            showInfo(number){
                console.log(number);
            },
            showInfo2(number,event){
                console.log(number,event)
            }
        },
    })
</script>
</html>

6.2事件修饰符

  • prevent:阻止默认事件

  • stop:阻止事件冒泡

  • once:事件只触发一次

  • capture:使用事件的捕获模式

  • self:只有event.target是当前操作的元素时才触发事件

  • passive:事件的默认行为立即执行,无需等待事件回调执行完成

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<style>
    .demo{
        background: red;
    }
    .demo1{
        margin-top: 20px;
        margin-bottom: 20px;
    }
</style>
<body>
    <div id="root">
        <h1>hello,{{name}}</h1>
        <!-- 阻止默认事件 -->
        <a href="https://www.baidu.com/" @click.prevent="func1">阻止默认事件</a>
        <!-- 阻止事件冒泡 -->
        <div class="demo" @click="func2">
            <button class="demo1" @click.stop="func2">阻止事件冒泡</button>
        </div>
        <!-- 事件只触发一次 -->
        <button @click.once="func3">事件只触发一次</button>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            name:"你好"
        },
        methods: {
            func1(){
                alert('阻止默认事件');
            },
            func2(){
                alert('阻止事件冒泡');
            },
            func3(){
                alert('事件只触发一次');
            }
        },
    })
</script>
</html>

6.3键盘事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>hello,{{name}}</h1>
        <input type="text" placeholder="按下回车提示输入" @keyup="showInfo"> 
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        el:"#root",
        data:{
            name:"你好"
        },
        methods: {
            showInfo(event){
                if(event.keyCode == 13){
                    console.log(event.target.value)
                }
            }
        },
    })
</script>
</html>

7.计算属性

  • 要用的属性不存在,需要通过计算已有的属性得到
  • 底层通过Object.defineProperty()实现
  • 与methods实现相比,内部有缓存机制,效率更高
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <input type="text" placeholder="" v-model:value="firstName">
        <input type="text" placeholder="" v-model:value="lastName">
        <p>全名:{{fullName}}</p>
        <button @click="func">点我修改全名</button>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        el:"#root",
        data:{
            firstName:'',
            lastName:''
        },
        methods: {
            func(){
                this.fullName = 'j-zn';
            }
        },
        computed:{
            fullName:{
                get(){
                    return this.firstName+this.lastName;
                },
                set(value){
                    console.log(value);
                    var arr = value.split('-');
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                }
            }
            // 简写
            // fullName(){
            //     return this.firstName+this.lastName;
            // }
        }
    })
</script>
</html>

8.监视属性

  • 当被监视的属性变化时,回调函数自动调用

  • 监视属性必须存在,才能进行监视

  • 监视的两种写法:(1)new Vue时传入watch配置(2)$watch监视

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>今天天气很{{info}}</h1>
        <button @click="change">点我切换天气</button>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            isHot:true
        },
        methods: {
            change(){
                this.isHot = !this.isHot;
            }
        },
        computed:{
            info(){
                return this.isHot?'炎热':'凉爽';
            }
        },
        watch:{
            isHot:{
                // immediate:true,// 初始化的时候调用一次handler
                // deep:true,// 深度监视
                handler(newValue,oldValue){
                    console.log('isHot',newValue,oldValue);
                }
            }
        }
    })
</script>
</html>

9.深度监视

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>a:{{numbers.a}}</h1>
        <button @click="add">点我a++</button>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            numbers:{
                a:0,
                b:1
            }
        },
        methods: {
            add(){
                this.numbers.a++;
            }
        },
        watch:{
            numbers:{
                deep:true,
                handler(newValue,oldValue){
                    console.log('numbers',newValue,oldValue);
                }
            }
        }
    })
</script>
</html>

10.绑定class

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<style>
    .class1 {
        width: 300px;
        height: 300px;
        background: red;
    }
    .class2 {
        width: 300px;
        height: 300px;
        background: blue;
    }
</style>
<body>
    <div id="root" :class="str" @click="change">
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            str:"class1"
        },
        methods: {
            change(){
                this.str = this.str=='class1'? 'class2':'class1';
            }
        },
    })
</script>
</html>

11.条件渲染

v-if

  • 适用于切换频率较低的场景,不展示的DOM元素会被直接移除
  • v-if,v-else-if,v-else一起使用时结构不能被打断

v-show

  • 适用于切换频率较高的场景
  • 不展示的DOM不会被移除,使用样式display:none隐藏
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1 v-if="n==0">0</h1>
        <h1 v-else-if="n==1">1</h1>
        <h1 v-else>2</h1>
        <h1 v-show="isShow">show</h1>
        <button @click="change">change</button>
        <button @click="show">show</button>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        //el用于指定容器,容器和vue实例的关系的一一对应
        el:"#root",
        //存储数据
        data:{
            n:0,
            isShow:true
        },
        methods: {
            change(){
                this.n++;
                if(this.n>=3){
                    this.n=0;
                }
            },
            show(){
                this.isShow = !this.isShow;
            }
        },
    })
</script>
</html>

12.列表渲染

v-for中的key就可以是index,但最好是被遍历对象的唯一标识

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <!-- 遍历数组 -->
        <ul>
            <li v-for="(person,index) of persons" :key="person.id">
                {{index}}:{{person.id}}-{{person.name}}-{{person.age}}
            </li>
        </ul>
        <!-- 遍历对象 -->
        <ul>
            <li v-for="(value,key) of lrc" :key="key">
                {{key}}:{{value}}
            </li>
        </ul>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            persons:[
                {id:1,name:'zhangsan',age:20},
                {id:2,name:'lrc',age:22},
                {id:3,name:'ryh',age:21},
            ],
            lrc:{
                id:0,
                name:'黎汝聪',
                age:20,
            }
        }
    })
</script>
</html>

13.列表过滤

当一个对象为计算属性时。在其方法中,使用的变量发生改变了,就会触发这个计算函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>人员列表</h1>
        <input type="text" v-model:value="keyWord">
        <!-- 遍历数组 -->
        <ul>
            <li v-for="(person,index) of filterPersons" :key="person.id">
                {{index}}:{{person.id}}-{{person.name}}-{{person.age}}
            </li>
        </ul>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            keyWord:'',
            persons:[
                {id:1,name:'张三',age:20},
                {id:2,name:'李四',age:22},
                {id:3,name:'王四',age:21},
                {id:4,name:'张五',age:22},
                {id:5,name:'王五',age:21},
            ],
        },
        computed:{
            filterPersons(){
                return this.persons.filter((p)=>{
                    return p.name.indexOf(this.keyWord) !=-1;
                })
            }
        }
    })
</script>
</html>

14.更新(Vue.set())

Vue通过setter实现监控,且要在new Vue()时就传入要监测的数据

对象创建后追加的属性,默认不做响应式处理

如需给后添加的属性做响应式,可用Vue.set()

Vue.set(target,propertyName/index,value)

vm.$set(target,propertyName/index,value)

在Vue修改数组中的元素一定要用如下方法

push(),pop(),unshift(),shift(),splice(),sort(),reverse()这几个方法被Vue重写了

Vue.set和vm.$set不能给vm或vm的根数据(data)添加属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>人员列表</h1>
        <button @click.once="change">修改</button>
        <!-- 遍历数组 -->
        <ul>
            <li v-for="(person,index) of persons" :key="person.id">
                {{index}}:{{person.id}}-{{person.name}}-{{person.age}}
            </li>
        </ul>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    //创建vue实例
    const x = new Vue({
        el:"#root",
        data:{
            persons:[
                {id:1,name:'张三',age:20},
                {id:2,name:'李四',age:22},
                {id:3,name:'王四',age:21},
                {id:4,name:'张五',age:22},
                {id:5,name:'王五',age:21},
            ],
        },
        methods: {
            change(){
                // this.persons[0] = {id:1,name:'zhangsan',age:20}; // 无效
                this.$set(this.persons,0,{id:1,name:'zhangsan',age:20})
                this.persons.push({id:6,name:'zhangsan',age:20})
            }
        },
    })
</script>
</html>

15.收集表单数据

v-model默认收集value值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <form>
            帐号:<input type="text" v-model="userInfo.account">
            <br/>
            密码:<input type="password" v-model="userInfo.password">
            <br/><input type="radio" name="sex" value="male" v-model="userInfo.sex"><input type="radio" name="sex" value="female" v-model="userInfo.sex">
            <br/>
            爱好:
            学习<input type="checkbox" value="study" v-model="userInfo.hobby">
            打游戏<input type="checkbox" value="game" v-model="userInfo.hobby">
            吃饭<input type="checkbox" value="eat" v-model="userInfo.hobby">
            <br/>
            地址:
            <select v-model="userInfo.address">
                <option value="">请选择</option>
                <option value="jdz">景德镇</option>
                <option value="xian">西安</option>
            </select>
            <br/>
            <button @click.prevent="submit">提交</button>
        </form>
    </div>
</body>
<script type="text/javascript">
    //阻止生成生产提示
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            userInfo:{
                account:'',
                password:'',
                sex:'',
                hobby:[],
                address:'',
            }
        },
        methods: {
            submit(){
                console.log(this.userInfo);
            }
        },
    })
</script>
</html>

16.Vue内置指令

之前用过的指令

  • v-bind 单向绑定解析表达式,可简写为:
  • v-model 双向数据绑定
  • v-for 遍历数组 / 对象 / 字符串
  • v-on 绑定事件监听,可简写为@
  • v-show 条件渲染 (动态控制节点是否展示)
  • v-if 条件渲染(动态控制节点是否存存在)
  • v-else-if 条件渲染(动态控制节点是否存存在)
  • v-else 条件渲染(动态控制节点是否存存在)

16.1v-text

向其所在的节点中渲染文本内容,会替换节点中的内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1 v-text="name">hello</h1>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            name:"你好"
        }
    })
</script>
</html>

16.2v-html

v-html会替换掉节点中所有的内容,可以识别html结构

在网站上动态渲染任意html是非常危险的,容易导致 XSS 攻击,不要用在用户提交的内容上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1 v-html="str">hello</h1>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            str:"<a href='https://www.baidu.com/'>百度</a>"
        }
    })
</script>
</html>

16.3v-cloak

本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性

当网速过慢的时候不让未经解析的模板展示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<style>
    [v-cloak] {
      display:none;
    }
  </style>
<body>
    <div id="root">
        <h1 v-cloak>{{name}}</h1>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            name:'你好'
        }
    })
</script>
</html>

16.4v-once

v-once所在节点在初次动态渲染后,就视为静态内容了,之后数据改变不会更新

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<style>
    [v-cloak] {
      display:none;
    }
  </style>
<body>
    <div id="root">
        <h2 v-once>初始化的n值是: {{n}}</h2>
        <h2>当前的n值是: {{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            n:1
        }
    })
</script>
</html>

16.5v-pre

跳过v-pre所在节点的编译过程

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<style>
    [v-cloak] {
      display:none;
    }
  </style>
<body>
    <div id="root">
        <h2 v-pre>当前的n值是: {{n}}</h2>
        <button @click="n++">点我n+1</button>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false
    const x = new Vue({
        el:"#root",
        data:{
            n:1
        }
    })
</script>
</html>

17.生命周期

  • Vue在关键时刻帮我们调用的一些特殊名称的函数,又名生命周期回调函数、生命周期函数、生命周期钩子
  • 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  • 生命周期函数中的 this 指向是vm或组件实例对象
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h2 :style="{opacity}">欢迎学习Vue</h2>
        <button @click="stop">点我停止变换</button>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el: '#root',
        data: {
        opacity: 1
    },
    methods: {
        stop() {
            this.$destroy()
        }
    },
    // 数据代理还未开始,无法访问data和methods
    beforeCreate() {
        console.log('beforeCreate');
    },
    // 可以访问data和methods
    created() {
        console.log('created');
    },
    // 页面呈现的是未经Vue编译的DOM结构,此时对DOM的操作最终都不会奏效
    beforeMount() {
        console.log('beforeMount');
    },
    // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
    mounted() {
        console.log('mounted')
        this.timer = setInterval(() => {
            this.opacity -= 0.01
            if (this.opacity <= 0) {
                this.opacity = 1
            }
        }, 16)
    },
    // 此时数据是新的,但页面是旧的
    beforeUpdate() {
        console.log('beforeUpdate');
    },
    // 此时数据和页面都是新的
    updated() {
        console.log('updated');
    },
    // 马上执行销毁过程,data、methods,指令等均可用,此时一般执行关闭定时器,取消订阅消息等操作
    beforeDestroy() {
        console.log('beforeDestroy');
        clearInterval(this.timer);
    },
    destroyed() {
        console.log('destroyed')
    },
})
</script>
</html>

18.组件

组件名

一个单词组成

  • 第一种写法(首字母小写):school
  • 第二种写法(首字母大写):School

多个单词组成

  • 第一种写法(kebab-case 命名):my-school

  • 第二种写法(CamelCase 命名):MySchool(需要Vue脚手架支持)

可以使用name配置项指定组件在开发者工具中呈现的名字。

​ 组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,而是 Vue.extend() 生成的。Vue 解析时会帮我们创建组件的实例对象,即Vue帮我们执行的new VueComponent(options)。每次调用Vue.extend,返回的都是一个全新的VueComponent,即不同组件是不同的对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.html</title>
    <!-- 引入vue -->
    <script type="text/javascript" src="./js/vue.js"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
</head>
<body>
    <div id="root">
        <h1>hello,{{name}}</h1>
        <school></school>
        <student></student>
    </div>
</body>
<script type="text/javascript">
    Vue.config.productionTip = false;
    // 定义组件
    const school = Vue.extend({
        template:`
        <div>
            <h1>学校名称:{{name}}</h1>
            <h1>学校地址:{{address}}</h1>
            <button @click="show">show</button>
        </div>`,
        data(){
            return{
                name:'景德镇一中',
                address:'新厂',
            }
        },
        methods: {
            show(){
                alert(this.name);
            }
        },
    });
    const student = Vue.extend({
        template:`
        <div>
            <h1>学生姓名:{{name}}</h1>
            <h1>学生年龄:{{age}}</h1>
        </div>`,
        data(){
            return{
                name:'黎汝聪',
                age:21,
            }
        }
    });
    // 全局注册组件
    Vue.component('school',school);
    const x = new Vue({
        el:"#root",
        data:{
            name:"你好"
        },
        // 注册组件
        components:{
            student
        }
    })
</script>
</html>

19.脚手架

使用命令

vue create projectName

脚手架文件结构

.文件目录
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件

组件在脚手架中的使用

School.vue

<template>
    <div>
        <h1>学校名称:{{name}}</h1>
        <h1>学校地址:{{address}}</h1>
        <button @click="show">show</button>
    </div>
</template>
<script>
export default {
    data(){
        return {
            name:'景德镇一中',
            address:'新厂',
        }
    },
    methods: {
        show(){
            alert(this.name);
        }
    }
}
</script>

Student.vue

<template>
    <div>
        <h1>学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age}}</h1>
    </div>
</template>

<script>
export default {
    data(){
        return{
            name:'黎汝聪',
            age:21,
        }
    }
}
</script>

App.vue

<template>
  <div id="app">
    <School></School>
    <Student></Student>
  </div>
</template>

<script>
import School from './components/School.vue';
import Student from './components/Student.vue';

export default {
  name: 'App',
  components: {
    School,
    Student
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

20.render函数

import Vue from 'vue’引入的是dist/vue.runtime.esm.js,只包含核心功能,没有模板解析器。因为vue.runtime.esm.js没有模板解析器,所以要使用render函数接收到的createElement函数去指定具体内容。

render: h => h(App)
// 完整形式
render(createElement){
	return createElement(App);
}

21.vue.config.js配置

/* 引入打包分析插件 */
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
/* 引入压缩插件 但是导致项目启动的比较慢*/
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

let isProduction = process.env.NODE_ENV === 'production'; // 判断是否是生产环境
let isTest = process.env.NODE_ENV === 'test'; // 判断是否是测试环境

const path = require('path')
const webpack = require('webpack')
function resolve(dir) {
  return path.join(__dirname, dir)
}
// 服务端ip  -- Uat环境
const api = 'http://10.1.12.181:26341';


module.exports = {

  /* 打包后的文件夹名字及路径  根目录下 */
  outputDir: 'video',

  /* 生产的项目名  可以用三元运算符*/
  publicPath: process.env.NODE_ENV === 'production' ? '/video/' : '/videoDev/',

  /* 保存时是否校验 */
  lintOnSave: false,

  /* 开发环境跨域情况的代理配置 */
  devServer: {
    // https: true, // 链接是否使用https访问 但可能在微信中不能打开(安全性问题) 不写此行配置默认 使用http协议打开
    port: '31001', 	// 指定要监听请求的端口号
    open: true, 	// 是否运行好直接在默认浏览器打开
    inline: true, 	// 用于设置代码保存时是否自动刷新页面。
    hot: true,		// 用于设置代码保存时是否进行热更新(局部刷新,不刷新整个页面)

    disableHostCheck: true, //可以被外网访问

    /* 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层。默认禁用。两种写法*/
    // overlay: true, // 第一种
    overlay: {
      //第二种
      warnings: false, //是否警告
      errors: false,
    },

    /* 接口代理器设置 可以配置多个*/
    proxy: {
      '/backend': {
        // 实际访问的服务器地址
        target: api,
        // 控制服务器收到的请求头中Host字段的值  host就是主机名加端口号  实际上就是欺骗后端服务器
        changeOrigin: true,
        // 是否启用websockets
        ws: false,
        // 重写请求路径  开头是/api的替换为 空串
        pathRewrite: { '/api': '' },
      },
    },
  },

  /* css相关设置 */
  css: {
    extract: false, // 是否使用css分离插件 ExtractTextPlugin  开启extract后,组件样式以内部样式表的形式加载的, 打包的结果会多出一个 css 文件夹以及css 文件。
    sourceMap: true, // 开启 CSS source maps?

    /* 向所有 Sass/Less 样式传入共享的全局变量 */
    loaderOptions: {
      //注意:在 sass-loader v8 中,这个选项名是 "prependData"  global.scss这里面定义的是一些全局变量
      scss: {
        prependData: '@import "~@/assets/scss/global.scss";',
      },
    },
  },

  /* webpack相关配置
   *该对象将会被 webpack-merge 合并入最终的 webpack 配置
   */
  configureWebpack: (config) => {
     // 为生产环境修改配置...
    if (process.env.NODE_ENV === 'production') {
    // 打包可视化分析
       config.plugins.push(new BundleAnalyzerPlugin());
    	/* js 压缩 */
      config.plugins.push(new UglifyJsPlugin({
       uglifyOptions: {
         uglifyOptions: {
           compress: {
             drop_debugger: true,
             drop_console: true // 生产环境自动删除console
           },
           warnings: false
         },
         sourceMap: false,
         parallel: true // 使用多进程并行运行来提高构建速度。默认并发运行数:os.cpus().length - 1。
       }
     }))
    } else {
      // 为开发环境修改配置...
    }
  },

  /* 对内部的 webpack 配置(比如修改、增加Loader选项)(链式操作) */
  chainWebpack: (config) => {
    //添加别名
    config.resolve.alias
      .set('@', resolve('src'))
      .set('assets', resolve('src/assets'))
      .set('components', resolve('src/components'))
      .set('layout', resolve('src/layout'))
      .set('base', resolve('src/base'))
      .set('static', resolve('src/static'));
    // 压缩图片
    config.module
      .rule('images')
      /* 需要下载这个图片压缩loader  cnpm install --save-dev image-webpack-loader */
      .use('image-webpack-loader')
      .loader('image-webpack-loader')
      .options({
        mozjpeg: { progressive: true, quality: 65 },
        optipng: { enabled: false },
        pngquant: { quality: [0.8,0.9], speed: 4 },
        gifsicle: { interlaced: false },
        webp: { quality: 75 },
      });
  },

  /* 第三方插件配置 */
  pluginOptions: {
    'process.env': {
      NODE_ENV: '"development"',
    },
    // 我这里用的是 vue-cli-plugin-mock 插件;用来开发前期模拟后端的请求
    // debug 为true时 vscode的控制台会打印接口日志
    mock: { entry: './mock/index.js', debug: true },
  },

};

22.ref

类似于id,应用在html标签上获取的是真实DOM元素,应用在组件上获取的是组件实例对象

获取组件

<template>
  <div id="app">
    <Student ref="student"></Student>
    <button @click="show">打印组件</button>
  </div>
</template>

<script>
import Student from './components/Student.vue';

export default {
  name: 'App',
  components: {
    Student
  },
  methods:{
    show(){
      console.log(this.$refs.student);
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

获取真实DOM

<template>
    <div>
        <h1 ref="name">学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age}}</h1>
        <button @click="show">打印DOM</button>
    </div>
</template>

<script>
export default {
    data(){
        return{
            name:'黎汝聪',
            age:21,
        }
    },
    methods:{
        show(){
            console.log(this.$refs.name);
        }
    }
}
</script>

<style>

</style>

23.props

让组件接收外部传过来的数据

props是只读的,且优先级高于data

<template>
    <div>
        <h1>学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age+1}}</h1>
    </div>
</template>

<script>
export default {
    data(){
        return{
        }
    },
    // 简单接收
    // props:['name','age'],
    // 限制类型
    // props:{
    //     name:String,
    //     age:Number
    // },
    // 限制类型、必要性、默认值
    props:{
        name:{
            type:String,
            require:true,
            default:'less'
        },
        age:{
            type:Number,
            require:false,
            default:99
        }
    }
}
</script>

传参

<template>
  <div id="app">
    <Student name="黎汝聪" :age="18"></Student>
    <Student name="菜妈"></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue';

export default {
  name: 'App',
  components: {
    Student
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

24.本地存储

存储内容大小一般支持 5MB 左右,浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制。

  • xxxStorage.setItem(‘key’, ‘value’)该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值

  • xxxStorage.getItem(‘key’)该方法接受一个键名作为参数,返回键名对应的值

  • xxxStorage.removeItem(‘key’)该方法接受一个键名作为参数,并把该键名从存储中删除

  • xxxStorage.clear()该方法会清空存储中的所有数据

  • SessionStorage存储的内容会随着浏览器窗口关闭而消失

  • LocalStorage存储的内容,需要手动清除才会消失

  • xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null

<!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>
    <h2>localStorage</h2>
    <button onclick="saveDate()">点我保存数据</button><br/>
    <button onclick="readDate()">点我读数据</button><br/>
    <button onclick="deleteDate()">点我删除数据</button><br/>
    <button onclick="deleteAllDate()">点我清空数据</button><br/>
</body>

<script>
  let person = {name:"黎汝聪",age:20}

  function saveDate(){
    localStorage.setItem('msg','localStorage')
    localStorage.setItem('person',JSON.stringify(person))
  }
  function readDate(){
    console.log(localStorage.getItem('msg'))
    const person = localStorage.getItem('person')
    console.log(JSON.parse(person))
  }
  function deleteDate(){
    localStorage.removeItem('msg')
    localStorage.removeItem('person')
  }
  function deleteAllDate(){
    localStorage.clear()
  }
</script>
</html>

25.自定义事件

一种组件间通信方式,适用于子组件传参数给父组件

  • 第一种方式,在父组件中<Child @事件名=“方法”/>或<Demo v-on:事件名=“回调方法”/>
  • 第二种方式,在父组件中this. r e f s . d e m o . refs.demo. refs.demo.on(‘事件名’,回调方法)
  • 触发自定义事件this.$emit(‘事件名’,数据)
  • 解绑自定义事件this.$off(‘事件名’)
  • 注意:通过this. r e f s . x x x . refs.xxx. refs.xxx.on(‘事件名’,回调函数)绑定自定义事件时,回调函数要么配置在methods中,要么用箭头函数,否则 this 指向会出问题

App.vue

<template>
  <div id="app">
    <!-- 通过给子组件绑定一个自定义事件实现子组件给父组件传参 -->
    <Student @send="printStudentName"></Student>
    <!-- 通过ref绑定自定义事件实现子组件给父组件传参 -->
    <Student ref="student"></Student>
    <!-- 通过props传函数参数的方式实现子组件给父组件传参 -->
    <School :printSchoolName='printSchoolName'></School>
  </div>
</template>

<script>
import Student from './components/Student.vue';
import School from './components/School.vue';

export default {
  name: 'App',
  components: {
    Student,
    School
  },
  methods:{
    printSchoolName(name){
      console.log('Apps收到了schoolName:'+name);
    },
    printStudentName(name){
      console.log('Apps收到了studentName:'+name);
    }
  },
  mounted(){
    // 绑定send事件和对应的回调函数
    this.$refs.student.$on('send',this.printStudentName);
  }
}
</script>

<style>
</style>

Student.vue

<template>
    <div>
        <h1>学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age}}</h1>
        <button @click="send">send学生名</button>
    </div>
</template>

<script>
export default {
    data(){
        return{
            name:'黎汝聪',
            age:21
        }
    },
    methods:{
        send(){
            // 触发Student实例身上的send事件
            this.$emit("send",this.name);
        }
    }
}
</script>

School.vue

<template>
    <div>
        <h1>学校名称:{{name}}</h1>
        <h1>学校地址:{{address}}</h1>
        <button @click="send">send学校名</button>
    </div>
</template>

<script>
export default {
    data(){
        return {
            name:'景德镇一中',
            address:'新厂',
        }
    },
    methods: {
        send(){
            this.printSchoolName(this.name);
        }
    },
    props:['printSchoolName']
}
</script>

26.全局事件总线

一种可以在任意组件间通信的方式,本质上就是一个对象,它必须满足以下条件

  • 所有的组件对象都必须能看见他
  • 这个对象必须能够使用 o n on onemit$off方法去绑定、触发和解绑事件

main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render(createElement){
    return createElement(App);
  },
  beforeCreate(){
    // 安装全局事件总线,$bus就是当前应用的vm
    Vue.prototype.$bus = this
  }
}).$mount('#app')

Student.vue(发送消息的组件)

<template>
    <div>
        <h1>学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age}}</h1>
        <button @click="send">send学生名</button>
    </div>
</template>

<script>
export default {
    data(){
        return{
            name:'黎汝聪',
            age:21
        }
    },
    methods:{
        send(){
            // 触发事件
            this.$bus.$emit('getStudentName',this.name);
        }
    },
    mounted(){
    }
}
</script>

School.vue(接收消息的组件)

<template>
    <div>
        <h1>学校名称:{{name}}</h1>
        <h1>学校地址:{{address}}</h1>
    </div>
</template>

<script>
export default {
    data(){
        return {
            name:'景德镇一中',
            address:'新厂',
        }
    },
    methods: {
        printStudentName(name){
            console.log('studentName:',name);
        }
    },
    mounted(){
        // 绑定事件,接收学生名
         this.$bus.$on('getStudentName',this.printStudentName);
    },
    beforeDestroy(){
        // 销毁前解绑事件
        this.$bus.$off('getStudentName');
    }
}
</script>

27.消息订阅和发布的使用

一种可以在任意组件间通信的方式

安装pubsub-js

npm i pubsub-js

Student.vue(发布消息的组件)

<template>
    <div>
        <h1>学生姓名:{{name}}</h1>
        <h1>学生年龄:{{age}}</h1>
        <button @click="send">发布学生名</button>
    </div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
    data(){
        return{
            name:'黎汝聪',
            age:21
        }
    },
    methods:{
        send(){
            // 发布消息
            pubsub.publish('getStudentName',this.name);
        }
    },
    mounted(){
    }
}
</script>

School.vue(接收消息的组件)

<template>
    <div>
        <h1>学校名称:{{name}}</h1>
        <h1>学校地址:{{address}}</h1>
    </div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
    data(){
        return {
            name:'景德镇一中',
            address:'新厂',
            pubId:0
        }
    },
    methods: {
        printStudentName(messageName,name){
            console.log('有人发布了消息');
            console.log('messageId:',this.pubId);
            console.log('messageName:',messageName);
            console.log('studentName:',name);
        }
    },
    mounted(){
        // 订阅消息
        this.pubId = pubsub.subscribe('getStudentName',this.printStudentName);
    },
    beforeDestroy(){
        // 取消订阅
        pubsub.unsubscribe(this.pubId);
    }
}
</script>

28.$nextTick

  • 这是一个生命周期钩子,this.$nextTick(回调函数)在下一次DOM更新结束后执行其指定的回调
  • 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
<template>
  <div id="app">
    <!-- <Student/>
    <School/> -->
    <h1>{{n}}</h1>
    <button @click="add">n++</button>
  </div>
</template>

<script>

export default {
  name: 'App',
  data(){
    return {
      n:1
    }
  },
  methods:{
    add(){
      if(this.n<5){
        this.$nextTick(()=>{
          console.log('update',this.n);
        });
      }
      this.n++;
    }
  }
}
</script>

29.配置代理

vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  // 关闭语法检查
  lintOnSave:false,
  publicPath:'./',
  devServer: {
    port: 8080,
    // proxy:'http://127.0.0.1:8001',
    proxy:{
      '/proxy':{
        target:'http://127.0.0.1:8001',
        pathRewrite:{'/proxy':'/proxy'},
        // ws: true, //用于支持websocket,默认值为true
        // changeOrigin: true //用于控制请求头中的host值,默认值为true,服务器收到的请求头中的host值为target中的值
      }
    }
},
})

App.vue

<template>
  <div>
    <button @click="get1">获取1</button>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
    get1(){
      axios.get('http://localhost:8080/proxy/').then(response =>{
        console.log(response.data);
      },error=>{
        console.log(error.message);
      })
    }
  },
  components: {
  }
}
</script>

nodejs

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");

//创建web服务器
let app = express();
// app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
const port = 8001;
//设置监听端口和启动成功回调函数
app.listen(port, () => {
    console.log('run at http://127.0.0.1:' + port);
});
//托管静态资源.可以访问public目录中的所有文件,路径中不含public
app.get("/proxy", function(req, res) {
    //在命令行中查看传递过来的参数
    console.log('1');
    res.send("proxyTest");
});

30.插槽

让父组件向子组件中的指定位置插入html结构

30.1默认插槽

App.vue(父组件)

<template>
  <div>
    <Test>
      <h1>你好</h1>
    </Test>
    <Test>
      <button @click="show">点击弹窗</button>
    </Test>
  </div>
</template>

<script>
import Test from './components/Test.vue'
export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
    show(){
      alert('hello!');
    }
  },
  components: {
    Test
  }
}
</script>

Test.vue(子组件)

<template>
  <div>
      <span>哈哈</span>
      <slot></slot>
  </div>
</template>

<script>
export default {
    data(){
        return {
            isShow:true
        }
    },
    methods:{

    }
}
</script>

<style scoped>
h1{
    background-color: orange;
}
</style>

30.2具名插槽

使用name指定填入的插槽

App.vue(父组件)

<template>
  <div>
    <Test>
      <h1 slot="center">你好</h1>
      <a href="https://www.baidu.com/" slot="footer">百度</a>
    </Test>
  </div>
</template>

<script>
import Test from './components/Test.vue'
export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
    show(){
      alert('hello!');
    }
  },
  components: {
    Test
  }
}
</script>

Test.vue(子组件)

<template>
  <div>
      <span>哈哈</span>
      <slot name="center"></slot>
      <slot name="footer"></slot>
  </div>
</template>

<script>
export default {
    data(){
        return {
            isShow:true
        }
    },
    methods:{

    }
}
</script>

<style scoped>
h1{
    background-color: orange;
}
</style>

30.3作用域插槽

将插槽所在组件的数据传给插槽使用者

App.vue(父组件)

<template>
  <div>
    <Test>
      <template slot-scope="data">
        <ul>
          <li v-for="(student,index) of data.students" :key="index">{{student}}</li>
        </ul>
      </template>
    </Test>
  </div>
</template>

<script>
import Test from './components/Test.vue'
export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
  },
  components: {
    Test
  }
}
</script>

Test.vue(子组件)

<template>
  <div>
      <slot :students="students"></slot>
  </div>
</template>

<script>
export default {
    data(){
        return {
            students:['黎汝聪','刘人恺','饶以恒','饶伟']
        }
    },
    methods:{

    }
}
</script>

<style scoped>
h1{
    background-color: orange;
}
</style>

31.Vuex

专门在Vuez实现集中式数据管理的一个Vue插件

安装vuex

vue2只能用vuex的3版本,vue3中只能用vuex的4版本

npm install vuex@3

使用vuex

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store/index'

Vue.config.productionTip = false

new Vue({
  store:store,
  render(createElement){
    return createElement(App);
  }
}).$mount('#app')

创建store/index.js

// 该文件用于创建Vuexz最为核心的store

import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
Vue.use(Vuex);
// 用于响应组件中的动作
const actions = {}
// 用于操作数据
const mutations = {}
// 用于存储数据
const state = {}

// 创建store
const store = new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state
})

export default store

App.vue

<template>
  <div>
    <h1>{{$store.state.number}}</h1> 
    <button @click="add">add</button>
    <button @click="sub">sub</button>
  </div>
</template>

<script>
import Test from './components/Test.vue'
export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
    add(){
      this.$store.dispatch('add',1);
    },
    sub(){
      this.$store.commit('SUB',1);
    }
  },
  mounted(){
    console.log(this.$store)
  }
}
</script>

32.路由

路由即一组映射关系,key为路径,value可能是function或component

安装

vue-router4只能在vue3中使用,vue-router3才能在vue2中使用

npm i vue-router@3

32.1基本使用

编写路由配置,src/router/index.js

// 该文件用于创建整个项目的路由
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
import News from '../components/News'
import Message from '../components/Message'


export default new VueRouter({
    routes:[
        {
            path:'/about',
            component:About
        },
        {
            path:'/Home',
            component:Home,
            // 多级路由
            children: [
                {
                    path:'news',
                    component:News
                },
                {
                    path:'message',
                    component:Message
                }
            ],
        }
    ]
})

main.js

import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
Vue.use(VueRouter)
Vue.config.productionTip = false

new Vue({
  render(createElement){
    return createElement(App);
  },
  router:router
}).$mount('#app')

App.vue

<template>
  <div>
    <h1>Vue router demo</h1>
    <div class="container clearfix">
      <div class="left">
        <router-link to="/about" active-class="active">About</router-link>
        <router-link to="/home" active-class="active">Home</router-link>
      </div>
      <div class="right">
        <!-- 指定组件的呈现位置 -->
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
    }
  },
  methods:{
  },
  mounted(){
  }
}
</script>

<style scoped>
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

.right{
  float: left;
  margin-left: 20px;
}

.left{
  float: left;
  border: #A3A4A3 1px solid;
  padding: 0;
}
.left > a{
  display: block;
  text-decoration:none;
  padding-top: 20px;
  padding-bottom: 20px;
  padding-left: 50px;
  padding-right: 50px;
}
.left > a:hover,.active{
  background: #2471BA;
  color: #FFFFFF;
}
</style>

About.vue

<template>
  <div>
      <ul>
          <li v-for="(student,index) of students" :key="index">{{student}}</li>
      </ul>
  </div>
</template>

<script>
export default {
    data(){
        return {
            students:['黎汝聪','刘人恺','饶以恒','饶伟炎']
        }
    },
    methods:{

    }
}
</script>

<style scoped>
h1{
    background-color: orange;
}
</style>

Home.vue

<template>
    <div>
        <h1>I'm Home!!!</h1>
        <router-link to="/home/news" active-class="active">News</router-link>
        <router-link to="/home/message" active-class="active">Message</router-link>
        <router-view></router-view>
    </div>
</template>

<script>
export default {

}
</script>

<style>
a{
  display: inline-block;
  text-decoration:none;
  padding-top: 10px;
  padding-bottom: 10px;
  padding-left: 20px;
  padding-right: 20px;
}

a:hover,.active{
  background: #2471BA;
  color: #FFFFFF;
}
</style>

News.vue

<template>
  <div>
      <ul>
          <li>news001</li>
          <li>news002</li>
          <li>news003</li>
      </ul>
  </div>
</template>

<script>
export default {

}
</script>

Message.vue

<template>
  <div>
      <ul>
          <li>message001</li>
          <li>message002</li>
          <li>message003</li>
      </ul>
  </div>
</template>

<script>
export default {

}
</script>

32.2注意事项

  • 通过切换“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  • 每个组件都有自己的$route属性,里面存储着自己的路由信息
  • 整个应用只有一个router,可以通过组件的$router属性获

32.3跳转传参

32.3.1query传参

App.vue(父组件)

<template>
  <div>
    <h1>Vue router demo</h1>
    <div class="container clearfix">
      <div class="left">
        <!-- 路由跳转 -->
        <router-link :to="{
          path:'/about',
          query:{
            data:queryData
          },
        }" active-class="active">About</router-link>
        <router-link to="/home" active-class="active">Home</router-link>
      </div>
      <div class="right">
        <!-- 指定组件的呈现位置 -->
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
      queryData:'query参数',
    }
  },
  methods:{
  },
  mounted(){
  }
}
</script>

<style scoped>
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

.right{
  float: left;
  margin-left: 20px;
}

.left{
  float: left;
  border: #A3A4A3 1px solid;
  padding: 0;
}
.left > a{
  display: block;
  text-decoration:none;
  padding-top: 20px;
  padding-bottom: 20px;
  padding-left: 50px;
  padding-right: 50px;
}
.left > a:hover,.active{
  background: #2471BA;
  color: #FFFFFF;
}
</style>

About.vue(子组件)

<template>
  <div>
      <h1>{{$route.query.data}}</h1>
  </div>
</template>

<script>
export default {
    data(){
        return {
        }
    },
    methods:{

    },
    mounted(){
        console.log(this.$route);
    }
}
</script>

32.3.2params传参

必须使用命名路由name,并在path中给参数占位

router/index.js

// 该文件用于创建整个项目的路由
import VueRouter from 'vue-router'
import About from '../components/About'


export default new VueRouter({
    routes:[
        {
            name:'guanyu',
            path:'/about/:data',
            component:About
        }
    ]
})

App.vue(父组件)

<template>
  <div>
    <h1>Vue router demo</h1>
    <div class="container clearfix">
      <div class="left">
        <!-- 路由跳转 -->
        <router-link :to="{
          name:'guanyu',
          params:{
            data:paramData
          }
        }" active-class="active">About</router-link>
      </div>
      <div class="right">
        <!-- 指定组件的呈现位置 -->
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
      queryData:'query参数',
      paramData:'params参数'
    }
  },
  methods:{
  },
  mounted(){
  }
}
</script>

<style scoped>
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

.right{
  float: left;
  margin-left: 20px;
}

.left{
  float: left;
  border: #A3A4A3 1px solid;
  padding: 0;
}
.left > a{
  display: block;
  text-decoration:none;
  padding-top: 20px;
  padding-bottom: 20px;
  padding-left: 50px;
  padding-right: 50px;
}
.left > a:hover,.active{
  background: #2471BA;
  color: #FFFFFF;
}
</style>

About.vue(子组件)

<template>
  <div>
      <h1>{{$route.params.data}}</h1>
  </div>
</template>

<script>
export default {
    data(){
        return {
        }
    },
    methods:{

    },
    mounted(){
        console.log(this.$route);
    }
}
</script>

32.3.3props配置

router/index.js

// 该文件用于创建整个项目的路由
import VueRouter from 'vue-router'
import About from '../components/About'


export default new VueRouter({
    routes:[
        {
            name:'guanyu',
            path:'/about/:data',
            component:About,
            // 第一种写法,该对象中所有的key-value会通过props传给About组件
            // props:{a:1,b:'lrc'}
            // 第二种写法:props值为函数,该函数返回的对象中的key-value都会通过props传给Detail组件
            // props($route){
            //     return {
            //         id: $route.query.id,
            //         title: $route.query.title
            //     }
            // }
            // 第三种写法,props为布尔值,当为true时,路由受到的所有参数都会通过props传给About组件
            props:true
        },
    ]
})

App.vue(父组件)

<template>
  <div>
    <h1>Vue router demo</h1>
    <div class="container clearfix">
      <div class="left">
        <!-- 路由跳转 -->
        <router-link :to="{
          name:'guanyu',
          params:{
            data:paramData
          }
        }" active-class="active">About</router-link>
        <router-link to="/home" active-class="active">Home</router-link>
      </div>
      <div class="right">
        <!-- 指定组件的呈现位置 -->
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data(){
    return {
      paramData:'params参数'
    }
  },
  methods:{
  },
  mounted(){
  }
}
</script>

<style scoped>
.clearfix::before,
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

.right{
  float: left;
  margin-left: 20px;
}

.left{
  float: left;
  border: #A3A4A3 1px solid;
  padding: 0;
}
.left > a{
  display: block;
  text-decoration:none;
  padding-top: 20px;
  padding-bottom: 20px;
  padding-left: 50px;
  padding-right: 50px;
}
.left > a:hover,.active{
  background: #2471BA;
  color: #FFFFFF;
}
</style>

About.vue(子组件)

<template>
  <div>
      <h1>{{$route.params.data}}</h1>
      <h1>{{data}}</h1>
  </div>
</template>

<script>
export default {
    data(){
        return {
        }
    },
    methods:{

    },
    mounted(){
        console.log(this.$route);
    },
    props:['data']
}
</script>

32.4replace

  • 作用:控制路由跳转时操作浏览器历史记录的模式
  • 浏览器的历史记录有两种写入方式:push和replace
  • push是追加历史记录
  • replace是替换当前记录,路由跳转时候默认为push方式
  • 开启replace模式
<router-link :replace="true" ...>News</router-link>
简写
<router-link replace ...>News</router-link>

总结:浏览记录本质是一个栈,默认push,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace就是不追加,而将栈顶地址替换

32.5编程式路由跳转

  • 作用:不借助实现路由跳转,让路由跳转更加灵活
  • this.$router.push({}) 内传的对象与中的to相同
  • this.$router.replace({})
  • this.$router.forward() 前进
  • this.$router.back() 后退
  • this.$router.go(n) 可前进也可后退,n为正数前进n,为负数后退

32.6缓存路由组件

让不展示的路由不被销毁

// 缓存一个路由组件
<keep-alive include="News"> // include中写想要缓存的组件名,不写表示全部缓存
    <router-view></router-view>
</keep-alive>

// 缓存多个路由组件
<keep-alive :include="['News','Message']"> 
    <router-view></router-view>
</keep-alive>

32.7路由守卫

32.7.1全局路由守卫

// 该文件用于创建整个项目的路由
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
import News from '../components/News'
import Message from '../components/Message'


const router = new VueRouter({
    routes:[
        {
            name:'guanyu',
            path:'/about',
            component:About,
            meta:{isAuth:true,title:'关于'}
        },
        {
            path:'/Home',
            component:Home,
            // 多级路由
            children: [
                {
                    path:'news',
                    component:News
                },
                {
                    path:'message',
                    component:Message
                }
            ],
        }
    ]
})

// 全局前置路由守卫,初始化的时候,每次路由切换前被调用
router.beforeEach((to, from, next) => {
    console.log('全局前置路由守卫')
    console.log('from',from);
    console.log('to',to)
    next();
})

// 全局前置路由守卫,初始化的时候,每次路由切换后被调用
router.afterEach((to,from)=>{
    console.log('全局后置路由守卫')
    console.log('from',from);
    console.log('to',to)
    // 修改页面标题
    if(to.meta.isAuth){
        document.title = to.meta.title;
    }
})

export default router

32.7.2独享路由守卫

// 该文件用于创建整个项目的路由
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
import News from '../components/News'
import Message from '../components/Message'


const router = new VueRouter({
    routes:[
        {
            name:'guanyu',
            path:'/about',
            component:About,
            meta:{isAuth:true,title:'关于'}
        },
        {
            path:'/Home',
            component:Home,
            // 多级路由
            children: [
                {
                    path:'news',
                    component:News,
                    beforeEnter:(to,from,next)=>{
                        console.log('News独享前置路由守卫')
                        console.log('from',from);
                        console.log('to',to)
                        next();
                    },
                },
                {
                    path:'message',
                    component:Message
                }
            ],
        }
    ]
})
export default router

32.7.3组件内路由守卫

<template>
  <div>
      <ul>
          <li>news001</li>
          <li>news002</li>
          <li>news003</li>
      </ul>
  </div>
</template>

<script>
export default {
  // 通过路由规则进入组件前调用
  beforeRouteEnter(to,from,next){
    console.log('beforeRouteEnter');
    console.log('to',to);
    console.log('from',from)
    next();
  },
  // 通过路由规则离开该组件时被调用
  beforeRouteLeave(to,from,next){
    console.log('beforeRouteLeave');
    console.log('to',to);
    console.log('from',from)
    next();
  }
}
</script>

32.8路由的两种工作模式

对于一个url来说,及其后面的内容就是hash值
hash值不会包含在HTTP请求中,即:hash值不会带给服务器
hash模式

  • 地址中永远带着#号,不美观
  • 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
  • 兼容性较好

history模式

  • 地址干净,美观

  • 兼容性和hash模式相比略差

  • 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题

    
    const router =  new VueRouter({
    	mode:'history',
    	routes:[...]
    })
    
    export default router
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黎汝聪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值