Vue入门教程

108 阅读28分钟

Vue2 学习

基础入门

调用vue

加入js代码

Vue 是一个封装好的js类,里面有许多的方法,vue 可以根据数据动态修改显示,也可以根据实际样式修改数据,一个 vue实例对应一个容器

{{}} 是插值语法,相当于一个容器 主要用于解析标签体里的内容

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
<div id="app">Hello,{{name}},{{address}},{{Date.now()}}</div>
    <script type="text/javascript">
        Vue.config.productionTip=false
        // 创建一个Vue实例,输入框里面放配置对象
        const x=new Vue({
            el:"#app",// 指定需要绑定的容器,可以写选择器
            data:{
                name:'尚硅谷',
                address:'北京'
            }
        })
    </script>

指令语法

v-bind:用于哪些与原生标签类似的变量,键名,会将v-bind 转化为表达式执行,v-bind中间不能有空格

可以解析标签(包括:标签属性,标签内容,绑定事件)

js 里面的数据属性可以使用多级结构

   <div id="app">Hello,{{name}},{{address}},{{Date.now()}}
        <a v-bind:href="url">百度</a>
        <a v-bind:href="bili">Bilibili</a>
    </div>
    <!-- <div id="links">
        
    </div> -->
    <script type="text/javascript">
        Vue.config.productionTip=false
        // 创建一个Vue实例,输入框里面放配置对象
        const x=new Vue({
            el:"#app",// 指定需要绑定的容器,可以写选择器
            data:{
                name:'尚硅谷',
                address:'北京',
                url:'https://www.baidu.com/',
                bili:'https://www.bilibili.com/'
            }
        })
        </script>

数据绑定

v-bind 是单向绑定,只能是数据影响显示,不呢是显示影响数据

v-model 是双向绑定,既可以实现显示控制数据,也可以实现数据影响显示

v-model 只能用于表单类元素

   单向数据 <input type="text" v-bind:value="name"><br> // v-bind 可以直接简写为: 
   双向数据 <input type="text" v-model:value="name"> // v-model 可以直接省略掉冒号
       

灵活使用容器

$mount 属性可以动态绑定标签对象

const y=new Vue({
            // el:"#root",
            data:{
                name:'尚硅谷'
            }
        })
        // 使用mount绑定标签
        v.$mount('#root')

data 的灵活写法,使用函数式的写法,对象式后边不使用,函数式不要使用箭头函数形式

data:function(){
    return{
        name:'尚硅谷'
    }
}

MVVM

Model:对于的data数据 js文件

View: 模板,宏观一点是dom html结构标签

ViewModel:Vue 实例对象 (dom listener ,data bind)

Vue 原型的东西可以直接在插入里面用

image-20221026221542779转存失败,建议直接上传图片文件

数据代理

用到里 Object.defineproperty 方法定义对象

let person={
        name:'张三',
        sex:'男'
    }
    // 给一个对象新增属性,新增的属性只能枚举不能遍历,增加 enumerable 才能遍历到,不能外部修改 需要增加writable 控制属性是否可以删除,默认false
    Object.defineProperty(person,'age',{value:18})
    console.log(Object.keys(person))
    console.log(person)
//(2) ['name', 'sex']
//{name: '张三', sex: '男', age: 18}
Object.defineProperty(person,'age',{
    value:18,
      enumerable: true,
            writable: true,
            configurable: true
})
    console.log(Object.keys(person))
    console.log(person)

使数据时刻与变量一起变化,只有有人读取绑定的新属性就动态变化 使用get set 方法 实现动态更新

let number=18;
        let person = {
            name: '张三',
            sex: '男'
        }
        get:function(){
                return number;
            },
            set:function(value){
                console.log('有人修改值就会触发')
                number=value;
            }
// 因此可以使用一个数据代理
 let obj= {x:100};
        let obj2={y:200};
        Object.defineProperty(obj2,'x',{
            get(){
               return obj.x;
            },
            set(value){
                obj.x=value;
            }
        })
        obj2.x=300
        console.log(obj.x)

Vue 数据代理

通过vm对象来代理data对象中属性的操作(读写)

Vue中数据代理的好处

更加方便操作data中的数据

通过实例 属性data 都设置了get,set 方法 ,可以通过 data 里的属性名直接调用

Vue 事件绑定

使用v-on:绑定click 等事件, 可以简写为 @

Vue对象里的属性 method 不需要增加 function,尽量不要用箭头函数

方法不做数据代理,只有放在data属性里的才做数据代理

可以传递参数 同时可以传递按键对象 $event

  <div id="root">

        <h2>欢迎光临</h2>
        <button v-on:click="showInfo">请点击</button>

    </div>



const roots=new Vue({
            el:"#root",
            methods:{
               showInfo(){
                alert('同学你好')
               }
            }
        })
v-on:click.prevent="showInfo"  //prevent 可以阻止跳转,但是会触发函数 是一种事件修饰符,可以多个修饰符
常用的实际修饰符包括:
.prevent 阻止默认实际
stop 阻止冒泡
once 事件只触发一次
capture 使用事件捕捉事件
self 只有event.target 只有单前操作的元素才能触发
passive 事件的默认行为立即执行,无需等待回调函数 

下面是可能出现事件冒泡的情况(内外都触发)image-20221027145558082转存失败,建议直接上传图片文件

只需要在内层的标签上加一个. stop

事件里的别名

可以启动事件之前的操作,比如说是输入完数据后的回车操作 @keyup.esc ="showInfo" 按下esc 键触发showInfo 事件

系统的修饰键用法比较特殊 : ctrl 、alt 、shift、meta,需要配合其他按键才能触发使用,最好不使用编码命名按键

回车=》 enter

删除=》delete

退出=》esc

空格=》space

换行 =》tab 切走聚焦

上 =》 up

下 =》down

左 =》left

右 =》right

计算属性与监视

{{}}中间也可以加入函数的返回值 {{ fullName()}}

  <h2>欢迎光临</h2>
        <label for="last-name">姓:</label>    
        <input type="text" id="last-name" v-model="lastname">
        <label for="first-name">名:</label>    
        <input type="text" id="first-name" v-model="firstname">
       全名:<span id="allname">{{fullname()}}</span>
...
 methods:{
                fullname(){
                    return this.lastname+'-'+this.firstname
                }
            }

这里Vue有一个计算属性可以写 必须写一个get 只有读取时才会计算

建立的是数据属性,不是一个方法属性,初次调用的时候会触发 get,所依赖的数据发生变化时


...
computed:{
    fullName:{
        get(){
            return this.firstname+'-'+this.lastname
        }
    }
}
// 
computed: {
                fullName: {
                    get() {
                        return this.firstname + '-' + this.lastname
                    },
                  // 也可加入set方法
                    set(value){
                       const arr= value.split('-');
                       this.firstname=arr[0];
                       this.lastname=arr[1];
                    }
                }
            }

计算属性的简单写法

只需要get 不需要set 就可以进行简写

// 可以简写为
computed: {
                fullName(){ 
                        return this.firstname + '-' + this.lastname
                    }
            }

天气切换

 <span>这里的天气是{{change}}</span><br>
 <button @click="show">点击切换</button>

const rot=new Vue({
            el:"#root",
            data: {
                isChange:true
            },
            computed:{
                change(){
                    return this.isChange ?'炎热':'凉爽';
                }
            },
            methods: {
                show(){
                   this.isChange=!this.isChange
                }
            }
        })

监视属性

Vue 属性实现监视 需要赋值一个对象,形式与计算属性类似

watch:{

  需要监视的属性:{
      immediate:true,//  初始的时候自动启动
          handler(newValue,oldValue){
         // newValue 表示新值,oldValue表示旧值 
      }

}
}
// 也可使用实例的方法
rot.$watch('监视的属性',{
      immediate:true,//  初始的时候自动启动
          handler(newValue,oldValue){
         // newValue 表示新值,oldValue表示旧值 
      })

深度监视:需要开启一个属性,用于监视一个属性对象里的数据改变,一般默认不监测内部值的改变

deep:true

简写形式: 不需要 immediate 和deep 时候可以使用

watch:{
    '监视属性'(newValue,oldvalue){
        函数体
    }
}

rot.$watch('监控的属性',function(newValue,oldValue){
    
})

绑定类属性

使用某一字符串的切换来控制切换类,用于需要的类名不确定

  <div id="root">
        <div class="basic" :class="add" @click="changeclass">这里是测试</div>
    </div>
    <script>
        const vm=new Vue({
            el:'#root',
            data:{
               add:'adds1'
            },
            methods: {
               changeclass(){
                 this.add='adds2'
               } 
            }
        })

多个样式绑定样式只需要将附加样式与一个数组绑定,数组里包含所有的样式,用户可以根据需求用 事件函数修改这个数组, 用push,pop,shift等方法

add:['adds1','adds2','adds3']

绑定的样式个数和样式都确定 可以使用 绑定一个对象,用于切换样式

add:{
adds1:false;
adds2:true;}
const vm=new Vue({
            el:'#root',
            data:{
               add:['adds3'],
               addObj:{
                adds2:false,
                adds3:true
               }

            },
            methods: {
               changeclass(){
                //  this.add.push('adds2');
                if(this.addObj.adds2==true){
                    this.addObj.adds2=false;
                    this.addObj.adds3=true;
                }
                else{
                    this.addObj.adds2=true;
                    this.addObj.adds3=false;
                }
                
               } 
            }
        })

还可以使用内联样式来修改样式

不过要加冒号 :style=“{fontSize:fsize+’px‘}” 里面的样式要写成对象形式 fsize是里面的一个数据属性,也可直接在data 里面定义一个 object 设置样式

:style=“arr” style对象里面可以放对象这种情况比较少

条件渲染

v-show=“布尔值或者属性” ,原理是使用 display:none (可以用于高频切换) v-if=“布尔值” 原理是增加和删除标签,效率不行 v-elseif v-else 是同 v-if类似 (也有选择逻辑)不可以打断使用 if elseif 。。 else

可以使用bool值规定是否渲染某一个标签

<h2 id="root" v-show="enable">欢迎观光</h2>
    <script>
        const vm=new Vue({
            el:"#root",
            data:{
                enable:true
            }
        })

模板

如果有同样的标签,就需要设置temple包裹,而不是使用div 包裹绑定 但是temp 要被 vue实例对应的标签绑定

可以使用 标签包裹 不能使用 v-show ,temp 类似于虚拟的div

 <h2 id="root" >欢迎观光
        <template id="roots" v-if="enable">
            <h2>你好</h2>
            <h2>世界</h2>  
        </template>
    </h2>

列表遍历显示

v-for =“(p,index) in persons” :key="p.id" (用遍历的主键元素)最好写上key

{{p.name}}--{{p.age}} 对象里的属性 可以循环创建并放入数据

也可以遍历对象里的键值对,字符串(不常见)

 <h2 id="root" >欢迎观光
        <template id="roots" v-if="enable">
            <h2>你好</h2>
            <h2>世界</h2>  
        </template>

        <ul>
            <li v-for="(p,index) in lis" :key="p.id">{{p.id}}-{{p.name}}-{{index}}</li>
        </ul>
        <ul>
            <li v-for="(value,key) in cars" :key="key">{{key}}-{{value}}</li>
        </ul>
    </h2>
  
    <script>
        const vm=new Vue({
            el:"#root",
            data:{
                enable:true,
                lis:[{id:1,name:'vans'},{id:2,name:'two'},{id:3,name:'three'}],
                cars:{
                    name:'audi',
                    price:'70w',
                    color:'black'
                }
            }
        })
    </script>

遍历列表时的Key的作用

虚拟dom对比算法 是相等的节点保留,不相同的更新因此根据KEY 遍历就会出现子节点错乱,如果使用内置的id属性就可以稳定显示

image-20221030085256393转存失败,建议直接上传图片文件

因此只要是虚拟dom发生变化的时候,因此增加节点需要注意遍历的变量

没有逆序添加的功能 ,可以使用index 索引功能

列表查询

升序、降序、模糊搜索

<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
    <div id="root">
        <h2>人员列表</h2>
        <input type="text" placeholder="请输入名字" v-model="values">
        <button @click="upsort()">升序排列</button> 
        <button @click="downsort()">降序排列</button> 
        <button @click="orisort()">原序排列</button> 
        <div id="result">
            <ul>
                <li v-for="(p,index) in fileter" :key="index">{{p.name}}--{{p.age}}岁</li>
            </ul>
        </div>
    </div>
    <script>
        const vm= new Vue({
            el:'#root',
            data:{
               sortflag:0,
               persons:[
                {name:'周杰伦',age:40},
                {name:'周杰',age:50},
                {name:'周冬雨',age:28},
                {name:'马冬梅',age:36},
                {name:'马化腾',age:55}
               ],
               values:'',
              
            },
            computed:{
                fileter(){
                     const arr=this.persons.filter((p)=>{
                        return p.name.indexOf(this.values)!=-1;
                     })
                     // 判断排序
                     if(this.sortflag!=0){
                        arr.sort((p1,p2)=>{
                         return  this.sortflag===1 ? p1.age-p2.age:p2.age-p1.age
                        })
                     }
                     return arr
                   
                 }
            },
            methods: {
                upsort(){
                    this.sortflag=1;
                },
                downsort(){
                    this.sortflag=2;
                },
                orisort(){
                    this.sortflag=0;
                }
            }
        })
</script>

image-20221030105334290转存失败,建议直接上传图片文件

Vue 检测数据改变的原理

Vue 的监视 data 与 watch 的底层监视原理类似

1.程序定义的 data 要进行加工 得到 _data 对象 ( _data ;里面有get set 方法)

2.加工之后可以进行响应式的数据

后面添加数据不具有响应式,因此使用 Vue的 API Vue.set(vm.data.student,'sex','男') ----- 只能给里面 _data 已有的属性增加内容

Vue.$set() methods内部可以写 this.set 等写法

对于数组数据的操作可以使用响应式的方法:arr.push pop shift unshift splice sort reverse 等

Vue 对 array 数据重写了 上述 arrary 方法 可以实现响应式

总结案例

 <div id="root">
        <div id="btns">
            <button @click="student.age++">年龄+1</button> <br/>
            <button @click="addsex">添加性别属性,默认值:男</button> <br/>
            <button @click=" addfriend">在列表的第一位添加一个朋友的名字</button> <br/>
            <button @click="addhobby">添加一个爱好</button> <br/>
            <button @click="updatehobby">修改一个爱好为:开车</button> <br/>
        </div>
        <div id="message">
            <h3 >姓名:{{student.name}}</h3>
            <h3>年龄:{{student.age}}</h3>
            <h3>性别:{{student.sex}}</h3>
            <h3>爱好:</h3>
            <ul>
                <li  v-for="(h,key) in student.hobby" :key="key">
                    {{h}}
                </li>
            </ul>
            <h3>朋友们:</h3>
            <ul>
                <li v-for="(v,k) in student.friends" :key="k">
                    {{v.name}}--{{v.age}}
                </li>
            </ul>
        </div>
    </div>
    <script>
        const vm= new Vue({
            el:'#root',
            data:{
                student:{name:'张三',
                age:'23',
                hobby:['抽烟','喝酒','烫头'],
                friends:[{name:'tom',age:'25'},{name:'jerry',age:'26'}]}
            },
            methods: {
                addsex(){
                    Vue.set(this.student,'sex','男')
                },
                addfriend(){
                    this.student.friends.unshift({name:'lucy',age:'22'});
                },
                addhobby(){
                    this.student.hobby.unshift('钓鱼')
                },
                updatehobby(){
                    // this.student.hobby.splice(0,1,'开车')
                    this.$set(this.student.hobby,0,'开车')
                }
            },
        })
    </script>

内容总结:

对数组类的方法调用,进行 只是增删内容:使用 arr.push pop shift unshift splice sort reverse 等方法

增加 vue 属性使用 Vue.set() 方法 (要在已有属性上加)

替换时数组可以使用 filter concat slice 等方法

v-model 的应用

表单提交案例

 <div id="root">
        <form @submit.prevent="updata"> 
            账号:<input type="text" v-model.trim="infos.user"><br>
            密码:<input type="password" v-model="infos.passcode"><br>
            性别:<input type="radio" v-model="infos.sex" name="sex" value="male">男
            <input type="radio" v-model="infos.sex" name="sex" value="female"> 女<br>
            年龄: <input type="number" v-model.number="infos.age"><br><br>
            爱好:<input type="checkbox" v-model="infos.hobby" value="study">学习
            <input type="checkbox" v-model="infos.hobby" value="game">游戏
            <input type="checkbox" v-model="infos.hobby" value="hair">烫头 <br><br>
            所属校区:<select v-model="infos.city">
                <option value="">请选择城市</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="wuhan">武汉</option>
                <option value="shenzhen">深圳</option>
            </select><br><br>
            其他信息:<textarea v-model.lazy="infos.text"></textarea> <br>
            <input type="checkbox" v-model="infos.readed">阅读并接受<a href="https://www.baidu.com">《用户协议》</a>
            <button>提交</button>
        </form>
    </div>
        <script>
            const vm = new Vue({
                el: '#root',
                data: {
                    infos: {
                        user: '',
                        passcode: '',
                        sex: 'female',
                        age: 18,
                        hobby: [],
                        city: '',
                        text: '',
                        readed: false,
                    }
                },
                methods: {
                    updata(){
                      if(!this.infos.readed){
                        alert('请勾选阅读协议');
                      }
                      else{
                        console.log(JSON.stringify(this.infos));
                      }
                    }
                }

            })
        </script>
<form @submit.prevent="updata"> //表单提交时可以进行判断,可以阻止跳转
 年龄: <input type="number" v-model.number="infos.age"><br><br> // 固定为数字选则
      爱好:<input type="checkbox" v-model="infos.hobby" value="study">学习
            <input type="checkbox" v-model="infos.hobby" value="game">游戏
            <input type="checkbox" v-model="infos.hobby" value="hair">烫头 <br><br>
                  hobby: [],  // 多项采用数组形式

过滤器

可以认为是一种特殊 computed 计算属性 ,比如 后端传入了一个 时间戳数据,需要 根据用需求转化为一定的格式(这里使用了一种 时间处理的api dayjs 可以使用 movement js 也行)

  <!-- 一种轻量化的时间戳处理工具 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.6/dayjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script>
    <div id="root">
        <h2>时间格式转化</h2>
    <!-- v-text是直接将某个数据变为 innertext -->
    <h3>现在的时间戳:</h3>
    <h3 v-text="timer"></h3>
    <h3>转化为标准格式:{{timer|timeproceser('YYYY年MM月DD日')|proceseer}}年</h3>
    </div>
    
  <script>
    const vm=new Vue({
        el:'#root',
        data:{
            timer:1667134227771
        },
        filters:{
            timeproceser(value,str='YYYY年MM月DD日 HH:mm:ss'){
                return dayjs(value).format(str)
            },
            proceseer(value){
                return value.slice(0,4)
            }
        }
    })
  </script>
<h3>转化为标准格式:{{timer|timeproceser('YYYY年MM月DD日')|proceseer}}年</h3>
第一个参数是传入的数据,每个过滤器都会默认使用的实参 ,第二个是过滤器调用,过滤器可以串联,一个接一个的使用将上一个过滤的结果作为下一个过滤器的输入
这个整体过滤器的结果是 输出年份

v-html 就是 innerHTML 的作用

安全性问题

使用 cookie

image-20221030212110302转存失败,建议直接上传图片文件

提交登录信息之后,服务器会返回cookie(具有一定信息的键值对字符串),方便下次登录服务器的时候免登陆(有的是直接给所有cookie,有的是慢慢给)

浏览器会分别存cookie 不能跨浏览器,除非直接使用

image-20221030213730443转存失败,建议直接上传图片文件

永远不要在用户提交的内容上提交使用 v-html

v-cloak

当网速较慢的时候,未经解析的模板和插值就会隐藏,不显示,vue 加载出来后会 删掉该属性

v-once

只显示某一值的初始值,即使这个值发生了变化

v-pre

跳过vue 解析直接进行显示,可以用于没有vue 语法的部分,加快编译

自定义指令

自定义指令不是通过return 来实现结果输出,而是修改样式值 如果是函数式(1,初始解析的时候会进行调用,2同一模板的数据变化时会调用)

函数式里的第一个参数是默认参数,就是元素对象本身,第二个产生是绑定的数据 要通过 value 输出值

自定义指令函数 this指向 window

在vm中加入

directives{

自定义指令名:{

键值对

}或者使用函数式

自定义函数名(){

}

}

// 自定义放大的函数
<div id="root">
        <button @click="n++">点击加1</button>
        <h2 >当前的n值是:</h2>
        <span v-text="n"></span>
        <h2>放大十倍之后的值是:</h2>
        <span v-big="n"></span>
    </div>

    <script>
        const vm=new Vue({
            el:'#root',
            data:{
                n:1
            },
            method:{
             
            },
            directives:{
                big(element,binding){
                  element.innerText=binding.value*10;
                   
                }
            }
        })
    </script>

函数式中途加入元素就会出现没有绑定的情况,要写成对象式 ,定义函数名的时候要全用小写

big:{
    //一上来指令与之绑定的时候
    bind(element,binding){
          element.innerText=binding.value*10;
    },
        // 指令所有的元素被插入页码时
        inserted(element,binding){
              element.innerText=binding.value*10;
        },
            //模板被重新解析
           update(element,binding){
                 element.innerText=binding.value*10;
           }
}

局部指令:

new Vue({

directives:{ 指令名:配置对象} 或 new Vue({指令名:回调函数})

})

全局指令:

Vue.directive(指令,配置对象) 或者 Vue.directive(指令名,回调函数)

指令定义的时候 不加 v 使用的时候要加 v

指令名如果是多个单词,要用横线连接而不是使用驼峰式

挂载后的函数

挂载后函数是一种生命周期函数(生命周期函数钩子函数) this 指向 vm 或者是组件式对象

mounted(){

}

就是vue 解析完触发的函数

Vue 创建的完整的生命周期

总共有4组钩子

1, 在数据监测与数据代理创建 前后分为 beforeCreate created

2 , 挂载之前与挂载之后 beforeMount mounted

  1. ​ 数据更新前后 beforeUpdate updated
  2. ​ 实例消除前后 beforeDestroy destroyed

常用的生命周期钩子

1,mounted 挂载了dom 之后调用 用于 ajax 请求,启动定时器,绑定自定义事件,订阅消息 [初始化操作]

2,beforeDestroy :清除定时器、解绑自定义事件、取消订阅消息 等收尾操作

销毁后的借助 Vue 看不到任何消息

原生的js事件依然有效,beforeDestroy 有用

组件

组件,就是用于功能的代码与资源的集合

image-20221031150100084转存失败,建议直接上传图片文件

image-20221031150222125转存失败,建议直接上传图片文件

模块:式 向外提供特定功能的js程序,一般是一个js文件,以功能划分的js代码文件群 组件和模块不一样

非单文件组件:

一个文件中有n个组件

单文件组件:

一个文件中 只有一个组件

下面介绍的是一个局部注册事件

<div id="root">
     <!-- 第三步:调用模板标签 -->
     <schools></schools>
     <hr>
    <students></students>
    </div>
    <script>
        // 第一步:创建组件配置数据与标签
        const school = Vue.extend({
            // 这里不需要创建el绑定,使用模板标签
            template: `
            <div>
               <h2>学校名字:{{schoolname}}</h2>
               <h2>校龄:{{age}}</h2>
            </div>`,
            //这里最好使用函数式
            data(){
                return{schoolname:'清华',
                 age:118}
            }
        })
        const student= Vue.extend({
            template:` <div>
               <h2>学生名字:{{studentname}}</h2>
               <h2>学生年龄:{{age}}</h2>
            </div>`,
            data(){
                return{
                    studentname:'kiri',
                    age:22
                }
            }
        })

        // 第二步:创建VM 把数据模板放入
        new Vue({
            el:'#root',
            components:{
                // 键值是模板标签名,值是模板的名字
                schools:school,
                students:student
            }
        })
    </script>
Vue.component('students',student)  加入后可以将一个模板变为全局组件,之后每一个vm实例都可使用

组件名一个单词 纯小写或者是首字母大写,多单词命名用小写加横杆连接或者是 每个单词 都大写首字母(只有脚手架才能识别)

可以设置一个 name属性来更改组件在开发者工具中的名字

自闭合标签可以在脚手架的情况下使用

简便定义:

const student={
    name:'stu',
    template:`..`,
    data(){}
}

组件嵌套

组件嵌套是先定义子组件,然后使用父组件调用 (和vm 调用一样 写 components 和 相应的标签)

组件式设计是 vm 是 app的父组件,app 又是其他模块的父组件 vm里面甚至可以写一个 template 装 app 标签

组件本质

1 school 组件的本质是一个 VueComponent 的构造函数,且不是程序定义的,是 Vue.extend 生成的 VueComponent是一个类

2 只要写 school 的标签就会生成对应的 VueComponent 实例对象 new 了一个类实例对象

3 每次调用 Vue.extend 返回的都是 一个全新的VueComponent 实例

组件的里的配置

data methods watch computed this都是指向 VueComponent 实例 组件的实例对象简称 vc vm管理若干个vc

Vue实例 与组件实例

组件是可以复用的Vue实例 ,底层的很多成员对象和属性是类似的 配置项大致相当

vc和vm区别

vm 可以绑定 el vc不可以,vm能决定给哪个容器使用,vc 不行

vc的 data必须是函数式

image-20221031200232251转存失败,建议直接上传图片文件

VueComponent.prototype.proto=== Vue.prototype

Vue 的原型实例相当于父类,VueComponent 没有的 方法属性会往上追 Vue.prototype 里面的属性

vc 是 小型的vm

Vue cli 介绍

如何使用脚手架

1 (只需要执行一次):全局安装 @vue/cli

npm install -g @vue/cli

2 在你的项目目录下 cmd 里 vue create 项目名(无需后缀)

脚手架使用的是 render函数 解析 app.vue 不需要打包完整的解析器

创建vm 的时候这样书写:

new Vue({

render(createelement){

return createelement(‘标签名’,标签内容) //如果是组件就可以不用加 单引号

}

})

Vue 的 ref 标签

1用于标签或者子组件标签的引用信息

可以放在标签里当 id 来用 用ref 可以得到 组件的vc 便于通讯

在js 代码使用 this.refs获取所有带ref的标签refs 获取所有带 ref的标签 $ this refs.设置的名字 来直接获取对应的标签 和组件 vm

vue props 配置

在template 里配置数据

props对于对象的部分值进行修改的时候是允许的(但是最好别这样做),对整体赋值修改是 不允许的

<SchoolName name="北大" age="120"></SchoolName>
:age="120" 可以用 v-bind 将数据视为一个表达式而不是 字符串

js配置方法

1. 用数组写键名
props:[name,age]
2. 限制类型
props:{
    name:String,
    age:Number
}
3.限制所有的形式
props:{
    name:{type:string,
         required: true,
         或者是 default'老王'}
}
    

混合方法

局部混合方法

有一些共有的配置对象可以写到公共的js 里方便多个组件共同使用

要用里面的配置时只需要进行先引入 import {hunhe,hunhe2} from ‘../mixin’

使用的时候只需要 使用 mixins:[hunhe,huenhe2]在各个组件的配置里 混合配置里的东西,只会增加原有的配置不会替换配置

export const huenhe={
    methods:{
        showName(){
            alert(this.name)
        }
    } 
}
export const huenhe2={
     methods:{
        showAge(){
            alert(this.age)
        }
    } 
}

全局混合

在入口main.js 引入 import {hunhe,hunhe2} from ‘../mixin’

写上 Vue.mixin(hunhe) Vue.mixin(hunhe2)

不需要在组件里一一配置引用每个 vm vc 里就会自动配置上

插件

用于一些附加的功能,比如给全局的组件增加过滤器,或者是全局指令

插件是一个js文件 暴露一个对象,对象里可以写各种全局方法

这里接受的形参就是Vue这个类

export default{
    install(Vue){
        Vue.filter()
        Vue.directive()
        Vue.minxin()
    }
}

使用的时候只需要写上

import plugins from ‘./plugins’

Vue.use(plugins) 表示应用插件

scoped标记

是用于标记组件里面style 标签 规定 stye 样式仅用于本组件,不会用于其他组件

如果是app 使用了scoped 就只会是app 申明的标签会受影响,其他的子组件不会受影响

用于使用形式

style 标签里面加 lang=“less” 就可以写成解析less语言

<stlye lang="less”>

组件化编程案例 ---TODOLIST

建立一个可以vue 组件化的 编程案例

整体分析

image-20221102102046164转存失败,建议直接上传图片文件

静态实现

先建立静态代码,再进行动态功能添加

因此这里需要建立 Footer List Item Header App 等组件

第一步建立好各个组件的静态代码并且写好彼此的嵌套关系

App.vue

<template>
 <div id="app">
  
  <div class="todo-container">
    <h2>况志勤的每日计划!</h2>
    <div class="todo-wrap">
      <UserHeader :addtodo="addtodo"/>
      <UserList :todos="todos"/>
     <UserFooter/>

    </div>
  </div>
 </div>
</template>

<script>
import UserHeader from './components/UserHeader.vue'
import UserList from './components/UserList.vue'
import UserFooter from './components/UserFooter.vue'

export default {
  name: 'App',
  components: {
    UserHeader,UserList,UserFooter
  },
  // 数组里面套对象
  data(){
    return {
      todos:[
        {id:'001',title:'抽烟',done:true},
        {id:'002',title:'喝酒',done:true},
        {id:'003',title:'烫头',done:true}
      ]
    }
  },
  methods: {
    addtodo(x){
     this.todos.unshift(x)
    }
  },
}
</script>

<style>
body{
  background: #fff;
}
.btn{
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  border: 1px solid #ddd;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}
.btn-danger{
  color:#fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}
.btn:hover{
  color:#fff;
  background-color:#bd362f;
}
.btn:focus{
  outline: none;
}
.todo-container{
  width: 600px;
  margin:200px auto;
}
.todo-container .todo-wrap{
  padding:10px;
  border:1px solid #ddd;
  border-radius: 5px;
}
</style>

UserHeader.vue

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务,按回车键确认"  @keyup.enter="add">
  </div>
</template>

<script>
  import{nanoid} from 'nanoid'
export default {

  data(){
    return{
      title:''
    }
  },
  props:['addtodo'],
methods: {
  add(e){
    const todoObj={id:nanoid(),title:e.target.value,done:false} 
    this.addtodo(todoObj)
    e.target.value=''
  }
},
}
</script>

<style scoped>
.todo-header input{
  width: 560px;
  height: 28px;
  font-size: 14px;
  border:1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus{
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(82, 168, 236, 0.8);
}
</style>

UserList.vue

<template>
  
    <ul class="todo-main">
     <UserItem v-for="todo in todos" :key="todo.id" :todata="todo"></UserItem>
</ul>

  
</template>



<script>
import UserItem from './UserItem.vue'
export default {
  name:'UserList',
  components:{
    UserItem 
  },
  props:['todos']

}
</script>

<style scoped>
/* list */
.todo-main{
    margin-left: 0px;
    border:1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}
/* .todo-empty{
    height: 40px;
    line-height: 40px;
    border:1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top:10px;
} */

</style>

UserItem.vue

<template>
  <li>
        <label >
            <input type="checkbox" :checked="todata.done">
            <span>{{todata.title}}</span>
        </label>
        <button class="btn btn-danger" style="display:none">删除</button>
    </li>
</template>

<script>
export default {
   name:'UserItem',
   props:['todata']

}
</script>

<style scoped>
 li{
        list-style:none;
        height: 36px;
        line-height: 36px;
        padding: 0 5px;
        border-bottom: 1px solid #ddd;
    }
li label{
    float:left;
    cursor:pointer;
}
li label li input{
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top:-1px;
}
li button{
    float: right;
    display: none;
    margin-top:3px;
}
li:before{
    content: initial;
}
li:last-child{
    border-bottom: none;
}
</style>

UserFooter.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox">
    </label>
    <span>已完成0</span>
    <button class="btn btn-danger">清除已经完成的任务</button>
  </div>
</template>

<script>
export default {
name:'UserFooter'
}
</script>

<style>

.todo-footer {
 height: 40px;
 line-height: 40px;
 padding-left: 6px;
 margin-top:5px;
}
.todo-footer label{
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}
.todo-footer label input{
 position:relative;
 top:-1px;
}
.todo-footer label input:focus{
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(82, 168, 236, 0.8);
}
.todo-footer label input:focus{
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(82, 168, 236, 0.8);
}
.todo-footer button{
  float:right;
}
</style>

组件数据的通讯

将数据传递给 UserItem 显示

数据data 放在最外面 App 通过v-bind传递给 UserList

v-bind 传递给子组件
<UserList :todos="todos"/>

UserList 通过 props 接受数据 同样的方法传递给子组件 UserItem

<UserItem v-for="todo in todos" :key="todo.id" :todata="todo"></UserItem>
 props:['todos']
//传递给item
<UserItem v-for="todo in todos" :key="todo.id" :todata="todo"></UserItem>
   props:['todata']

通过 UserHeader 数据函数实参传递给 App

image-20221102211201507转存失败,建议直接上传图片文件

app里面的

 <UserHeader :addtodo="addtodo"/>
  methods: {
    addtodo(x){
     this.todos.unshift(x)
    }
  },

UserHeader.vue 里面的

props:['addtodo'],
methods: {
  add(e){
    const todoObj={id:nanoid(),title:e.target.value,done:false} 
    this.addtodo(todoObj)
    e.target.value=''
  }

功能实现:勾选的确定与取消

使用 App 传递组件函数 或者直接 v-model (可以实现但是不推荐)

同样是 props 传递 函数 ,用实参回传修改数据todos.done

App.vue

<UserList :todos="todos" :checktodo="checktodo"/>

checktodo(x){
    //  遍历修改
    this.todos.forEach(todo => {
      if (todo.id==x) todo.done=!todo.done;
    });
    }

UserList.vue

:checktodo="checktodo"
props:['todos','checktodo']

UserItem.vue

 @change="changecheck(todata.id)
 props:['todata','checktodo'],
 methods: {
    changecheck(id){
        this.checktodo(id)
    }
 },

功能实现:统计选择和总数

 <span>已完成 {{donetotal}}</span> / 全部 {{total}}
total(){
      return this.todos.length
  },
  donetotal(){
    //统计被勾选的数量 pre 是将上次迭代结果作为下一次的输入
    return this.todos.reduce((pre,todo)=> pre+(todo.done?1:0),0)
  },

功能实现:全选与全不选

主要是分两步:获取 UserFooter 中 checkbox 状态,通过函数实参将数据传回去修改全局数据

全选按钮只要是每个item的状态都是 true 就会自动勾选上,如果不是就不勾选

这个按钮即提供线索,又可以进行操作 因此在对应的标签上标记 v-model 用一个computed属性进行处理 该属性最好是 既有get 也有set

<div class="todo-footer" v-show="total">
    
    
    totalchange:{
     get() {
        return this.donetotal===this.total && this.total>0
      },
      set(value) {
       this.changeAll(value)
      }
  }

App.vue 通过传递一个函数给 UserFooter修改 data

changeAll(x){
    //遍历修改状态
    this.todos.forEach(todo => todo.done=x)
    },

功能实现:删除所有已选项

App.vue

 clearDone(){
       this.todos=this.todos.filter((todo) => {
          return !todo.done
       })
    }

UserFooter.vue

 <button class="btn btn-danger" @click="clearall()">清除已经完成的任务</button>

clearall(){
    if(confirm('你是否删除所有已经完成的任务?'))
    this.clearDone()
  }

关键总结:

1) 一般的把数据放在 App 组件里(最顶级的父组件里)

2) 子组件传递数据给是通过 父组件传递下来的函数,通过实参传递

3)父组件传递数据函数是通过 props实现

本地储存与组件通讯

本地储存被广泛应用于搜索历史的存储,就是将键值对保存到电脑硬盘里,即使关闭浏览器依然存在

location 方法

写入 localStorage.setItem('键名''键值')    键值会自动将输入的 Number类型会自动转化为字符串 ,键值输入对象时 要先使用 Json.stringgify(p) 处理
读取localStorage.getItem('键名')   根据键名获得键值
删除一个值  localStorage.removeItem('键名')  根据键名删除键值对
清空键值对  localStorage.clear()  清空

案例样式

<!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>
    <button onclick="saveData()">添加数据</button>
    <button onclick="readData()">显示数据</button>
    <button onclick="deleteData()">删除数据</button>
    <button onclick="clearData()">清空数据</button>
    <script type="text/javascript">
        let p={name:'张三',age:18}
        function saveData(){
            localStorage.setItem('msg','姓名')
            localStorage.setItem('obj',JSON.stringify(p))
            localStorage.setItem('num',666)
        }
        function readData(){
           console.log(localStorage.getItem('msg'))
           console.log(localStorage.getItem('obj')) 
           console.log(localStorage.getItem('num')) 
        }
        function deleteData(){
            localStorage.removeItem('msg')
            console.log(localStorage.getItem('msg'))
        }
        function clearData(){
            localStorage.clear()
        }

    </script>
</body>
</html>

session 方法

会话方法,浏览器一关闭就就会清空数据

Session 与 location 上的API 是一样的 直接代替就行

写入 sessionStorage.setItem('键名''键值')    键值会自动将输入的 Number类型会自动转化为字符串 ,键值输入对象时 要先使用 Json.stringgify(p) 处理
读取 sessionStorage.getItem('键名')   根据键名获得键值
删除一个值 sessionStorage.removeItem('键名')  根据键名删除键值对
清空键值对  sessionStorage.clear()  清空

可以说使用 localStorage 完善 todolist的功能,保存数据

组件的自定义事件

组件的自定义事件可以实现子组件向父组件传递数据 与之前的传递函数类似

先要在 App.vue 绑定自定义触发事件 这里自定义的事件名是 activedIssue

 <StudentName @activedIssue="getStudent"></StudentName>
 getStudent(x,...para){
      console.log('学生的名字是',x,para)
     },

在到 StudentName 里触发自定义事件,这里是用一个点击事件触发 这个自定义事件

this.$emit('activedIssue',this.name) 第一参数是触发事件,之后的参数是返回值,可以返回普通类型和对象,可以可以返回多个也可以传递函数

<button @click="sendName">输出姓名</button>
methods: {
    sendName(){
        this.$emit('activedIssue',this.name,this.address)
    }
},

在进行 App.vue 中也可以使用 ref标签 ref 相当于id 标签 获取vc

<StudentName ref="student"></StudentName>

mounted() {
     this.$refs.student.$once('activedIssue',this.getStudent)
     },

自定义事件的解绑

与 $ emit 类似

this.$off('定义事件名') 解绑单个事件

this.$off(['定义事件名','定义事件名']) 解绑多个事件

this.$off() 解绑所有

this.$destroy() 销毁 实例会自动自动解绑,原生事件不会受影响

组件也可以绑定原生的事件不过要加后缀修饰,比如 @click.native =" " 系统会把绑定事件放到组件的最外层标签上

todolist 升级 父给子传递函数的的地方都可以替换位自定义事件

全局事件总线(GlobalEventBus)

可以实现任意组件之间之间的通信 ---原理就是自定义组件事件 数据传递方法

首先要安装事件总线在 vm 创建的时候将 Vue 原型对象的 bus 规定为 this

使用事件总线 1.接受数据 A 组件想要接受数据,则在A数组中给 $bus绑定自适应事件,A事件回调函数写在A 组件里(这里的A组件像父子通讯中的父组件)

在 vm 中进行声明

new Vue({
beforeCreate(){
 Vue.prototype.$bus=this
}
})

在A组件中进行设置回调

mounted(){

    this. $ bus . $on( '自定义事件名',回调函数) 回调函数不需要形参

}
beforeDestroy(){  
this.$bus.$off('自定义事件')//vc销毁后解绑自定义事件
}

2.自定义事件 这里更像是一个子组件的声明

this.$bus.$emit('自定义事件',数据)

这里同样可以优化todolist

订阅发布方式

这里的订阅发布 是指 一种语法糖 原理是 A组件订阅某一个组件(类似于自定义事件)的数据 B组件定义(自定义事件)传递数据,vue deeptools 看不到

这里需要依赖 订阅发布的 第三方js这里使用一个 pubsub 包 用到里面方法的组件都要引用,无论是订阅方还是发布方 ,自定义事件名一般与回调函数重名

npm i pubsub-js // 导入库
import pusub from pubsub-js //引用

订阅方 程序

mounted(){
  this.pubId=pubsub.subscribe('自定义事件',回调函数)  这里的回调函数第一个返回值是 自定义事件名,回调函数第一个形参可以使用下划线 顶替这个参数
}    
beforeDestroy(){
 pubsub.unsubscribe(this.pubId)
}

发布方

pubsub.publish('自定义事件',数据)

this.nextTick方法

// 唯一输入设置,更新dom之后再执行当改变数据后,根据改变后的数据进行操作就需要这个,里面放回调函数
        this.$nextTick(
            function(){
                this.$refs.inputTitle.focus()
            }
        )

vue的动画

Vue 自带动画效果切换逻辑 ,也可使用第三方库

Vue 中的 transition标签(只能包裹一个整体的标签),包裹的标签会用于动画标签 name属性用于标记动画,不同的动画要用 name 区分 appear 是用于加载页面时触发 要包裹多个标签使用 transition-group 标签 (里面的标签要标上 key+数字) 这里使用 v-show作为动画触发的实际

 <transition name="hello" appear>
      <h2 class="box" v-show="isshow">你好啊!</h2>
 </transition>
    <transition-group name="hello2" appear>
        <h2 class="box" v-show="isshow" key="1">结束了!</h2>
        <h2 class="box" v-show="isshow" key="2">可以回家了吗!</h2>
     </transition-group>


export default {
    data(){
    return{
      isshow:true
    }
 }
}

.hello-enter,.hello-leave-to{
     transform: translateX(-100%);
}
.hello-enter-to,.hello-leave{
    transform: translateX(0);
}

第三方库方法

导入并且引用

npm install animate.css --save
import 'animate.css';
<button @click="isshow=!isshow">点击切换</button>
       <transition appear name="animate__animated animate__bounce"
        enter-active-class="animate__wobble"
        leave-active-class="animate__backOutRight">
       <h2  v-show="isshow">下班了!</h2>
       </transition>

这里需要使用 transition标签 name="animate__animated animate__bounce" 是不变的

想要修改 动画就可以修改 enter-active-class 里面的内容就可以了

Vue 的异步请请求

Vue的异步请求是在 axois ,promise ,ajax 的基础上进行封装。由于通讯或者是读取数据的限制程序不可能同时运行,有部分程序会在某些数据获取之后运行因此为了防止(回调地狱是回调函数不断嵌套回调函数导致的阅读调试不便)

Promise基础

Promise 是一种异步处理构造函数(就是一种类)通过实例化对象来实现异步操作 ,promise 会异步返回的数据进行处理,分为有数据和没有数据两种一般写为 成功的回调函数 resolve(形参) 失败的回调函数 reject(形参)之后在实例对象里书写得到数据和没得到数据的形参

案例一 :异步抽奖

异步通讯是一个定时器 :

<!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>
    <button id="btn">点击抽取</button>
</body>
<script>
    function rand(m, n) {
        return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
    }
    const btn = document.querySelector('#btn');
    console.log(btn);
    btn.addEventListener('click', function () {
        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                let n = rand(1, 100);
                if (n < 30) {
                    resolve(n);
                }
                else {
                    reject(n)
                }
            }, 100)
        }, 1000);
        // 调用 then 方法
        p.then((value) => {
            alert('恭喜获得最后的大奖,你的中奖号码是:'+value)
        }, (reason) => {
            alert('再接再厉!你的号码是:'+reason)
        });
    });
</script>
</html>

案例2 :读取 文件

const fs=require('fs')
// 传统的写法
// fs.readFile('../content.txt',(err,data)=>{
//     if(err) throw err;
//     console.log(data.toString());
// })
//使用promise 的形式进行封装
let p =new Promise((resolve,reject)=>{
    fs.readFile('../content.txt',(err,data)=>{
        // 如果出错
        if(err){reject(err);} 
        else{resolve(data);}
        // 如果成功
    })
});
p.then(value=>{console.log(value.toString())},
reason=>{console.log(reason)})

Promise 实例对象

对象属性的改变 pending 变为 resolved ,pending 变为 rejected 实例对象中的一个属性 PromiseState:pending ,resolved ,rejected

promise 的基本流程

image-20221106204132972转存失败,建议直接上传图片文件

Promise 构造函数构造 :Promise(excutor ){ }

excutor 是一个函数: 执行器 (resolve ,reject) =>{ }

resolve 函数:内部定义成功时我们定义的函数 value =>{ }

reject 函数: 内部定义失败时 我们调用的函数 reason =>{ }

resolve 和 reject 需要同步调用

Promise.prototype.then 方法 (onResolved,onRejected)=>{ }

onResolved 表示成功的回调函数,onRejected 表示失败的回调函数

Promise.prototype.catch (onRejected) =>{ } 失败的回调函数

p.catch(reason=>{console.log(reason)})

Promise.resolve ,reject (相当于类型转化类型),promise .all 方法

Promise.resolve(value) // 成功的的数据或者 promise 对象 返回的是一个成功的Promise对象 如果传递的函数是一个非 Promise 类型的对象就会直接返回 成功的Promise对象
Promise.reject(value) // 失败的原因  或者是promise 对象 返回的是一个失败的Promise对象,无论传入什么样的数值都会返回失败的Promise
(promise)=>{} // 包含n 个promise的数组 只要所有的Promise 都成功才是成功,只要有一个失败就是失败
Promise.race 方法 (Promise)=>{}                Promise.race([p1,p2,p3])

改变状态对象的方法有以下几个

let p =new Promise((resolve,reject) =>{
       resolve('ok') // 可以直接将 p 变为一个成功的对象
       reject("error") //可以将 p 变为失败的对象
        trow '失败内容' // 可以将 p 变为失败的对象    
})
指定多个成功或者失败的回调函数,都会调用吗
当 promise 改变为对应状态时会进行调用
如果写了多个 p.then( value =>{
alert(value);
}) 有几个就会响应几个

改变 promise 状态和指定回调函数谁先谁后 是指回调的时候谁先谁后,有可能是内部的先执行,也有可能先外部执行

使用 代理进行跨域

nginx 和 vue-cli 开启代理服务器

Vue 插槽

在组件里面定义 slot 标签和默认的内容,在父组件里定义具体需要加入的内容

组件就像预制件,但是实际情况,又有不同的需求,需要在在组件里面加入 slot 标签定义一些不同的标签

匿名插槽: slot 标签里面没有 name属性 需求简单就可以使用

具名插槽: slot 里面定义 name 属性, 调用的时候 加入 slot=“插槽名” ,多个标签 用个 template 包裹 (#插槽名)加入 v-slot:插槽名 或者加入 slot=“插槽名”

v-slot 是

父组件调用

<template>
  <div class="container">
    <CatLog title="美食" :listData="foods">
      <img slot="food" src="./assets/hotplot.jpg" >
      <template slot="video">
        <a  href="#">点击进入</a>
        <a  href="#">More</a>
      </template>
     
    </CatLog>
    <CatLog title="游戏" :listData="games"/>
    <CatLog title="电影" :listData="films">
     <video slot="video" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    </CatLog>
  </div>
</template>

<script>
import CatLog from './components/CatLog.vue'
export default {
  name: 'App',
  components: {
     CatLog
  },
  data(){
    return{
      foods:['火锅','烧烤','牛排'],
      games:['红警','穿越火线','劲舞团'],
      films:['教父','肖申克的救赎','阿甘正传']
    }
  }
}
</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;
} */
.container{
  display: flex;
  justify-content:  space-around;
}
</style>

子组件的声明:

<template>
  <div class="category">
    <h3>{{title}}分类</h3>
   <slot name="food">
    <ul>
        <li v-for="(item,index) in listData" :key="index">{{item}}</li>
    </ul>
   </slot>
   <slot name="video">
   </slot>
  </div>
</template>

<script>
export default {
   name:'CatLog',
   props:['title','listData']
}
</script>

<style>
.category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    margin-top: 20px;
    background-color: orange;
}
img{
    width: 100%;
}
video{
    width: 100%;
}

</style>

image-20221122114429935转存失败,建议直接上传图片文件

作用域 插槽

数据在子组件里面,自定义的样式在 父样式中修改

template 里的 slot-scope 是关键操作 传递的是一个对象

<template>
  <div id="app" class="container">
    <CateGory title="游戏">
       <template slot-scope="athere">
        <ul>
          <li v-for="(g,index) in athere.games" :key="index">{{g}}</li>
        </ul>
       </template>
    </CateGory>

    <CateGory title="游戏">
       <template slot-scope="athere">
        <ol>
          <li v-for="(g,index) in athere.games" :key="index">{{g}}</li>
        </ol>
       </template>
    </CateGory>
    <CateGory title="游戏">
       <template slot-scope="athere">
        <ol>
          <li style="color:red" v-for="(g,index) in athere.games" :key="index">{{g}}</li>
        </ol>
       </template>
    </CateGory>
    <!-- <CatLog title="美食" :listData="foods">
      <img slot="food" src="./assets/hotplot.jpg" >
      <template slot="video">
        <a  href="#">点击进入</a>
        <a  href="#">More</a>
      </template>
     
    </CatLog>
    <CatLog title="游戏" :listData="games"/>
    <CatLog title="电影" :listData="films">
     <video slot="video" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> -->
    <!-- </CatLog> -->
  </div>
</template>

<script>
// import CatLog from './components/CatLog.vue'
import CateGory from './components/CateGory.vue'
export default {
  name: 'App',
  components: {
    CateGory
  },
}
</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;
} */
.container{
  display: flex;
  justify-content:  space-around;
}
</style>

子组件里用 v-bind 绑定要传递的数据

<template>
  <div class="category">
    <h3>{{title}}</h3>
    <slot :games="games">这里是一些默认值</slot>
  </div>
</template>

<script>


export default {
name:'CateGory',
props:['title'],
data(){
    return {
        games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
    }
}
}
</script>

<style scoped>
.category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
}
h3{
    text-align: center;
    margin-top: 20px;
    background-color: orange;
}
img{
    width: 100%;
}

</style>

vuex 插件

多个组件共享一个数据的时候就要容易出现复杂的传递关系因此就需要一个共享数据的读取方式

实际过程中可以跳过dispatch 直接进行Mutation操作

image-20221122205621136转存失败,建议直接上传图片文件

Vue 的组件模板会根据 使用 Dispatch 操作作 Actions 传递 要处理的全局事件(红烧、清蒸),并且传入参数(食材) (这个过程相当于在前台点菜)

actions 定义 要处理的函数 (如何处理食材)形参是 传入的参数和公共数据(食材和配料)处理结果

State 定义公共数据

actions 里面也可以使用 context.dispatch('全局事件名',value) 串联好各个全局函数 ,contex包含可能用到的属性

实际案例

1先要在引入包 npm install vuex@3.0 vue2 比较适配

2 在 assets 文件夹下创建一个 store 文件夹 再创建 index.js 公共的全局事件写 在 这个js里

3、在使用的 组件里进行disipatch 因为调用的时候可以使用 公共的变量

4、在vm创建的时候加入store 变量 (main) 函数

main.js

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

import store from './store'
//  引入store 

Vue.config.productionTip = false;

const vm= new Vue({
  el:'#app',
  render: h=> h(App),
  store,
  beforeCreate(){
    Vue.prototype.$bus=this
  }
})
//console.log(vm);

index.js

import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
Vue.use(Vuex)
// 这里创建 store
// action 用于响应组件中的动作
const actions={
   jia(context,value){
     console.log('action 中的jia 被调用了 ');
     context.commit('JIA',value);
   },
   jian(context,value){
     console.log('action 中的jian 被调用了 ');
     context.commit('JIAN',value);
   },
   odd(context,value){
      console.log('action 中的odd 被调用了');
      context.commit('ODD',value);
   },
   wait(context,value){
    console.log('action 中的wait被调用了');
      context.commit('WAIT',value);
   }

}
// 准备mutation 响应组件中的动作
const mutations={
    JIA(state,value){
       state.sum+=value;
    },
    JIAN(state,value){
        state.sum-=value;
    },
    ODD(state,value){
        if(state.sum % 2){
            state.sum+=value;
        }
    },
    WAIT(state,value){
    setTimeout(() => {
        state.sum+=value;
    }, 500);
    }

}
// 准备state -- 用于操作数据,存储数据
const state={
    sum:0
}


//创建store
export default new Vuex.Store({
    actions,
    mutations,
    state
})

计算组件 CountNumber

<template>
  <div>
      <h1>当前求和为:{{$store.state.sum}}</h1>
      <select v-model.number="n">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
      <button @click="incrementOdd">当前求和为奇数再加</button>
      <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
export default {
  name:'CountNumber',
   data(){
    return{
        n:1,
        sum:0
    }
   },
   methods:{
      increment(){
       this.$store.dispatch('jia',this.n)
      },
      decrement(){
        this.$store.dispatch('jian',this.n)
      },
      incrementOdd(){
          this.$store.dispatch('odd',this.n)
      },
      incrementWait(){
          this.$store.dispatch('wait',this.n)
      }
   },

}
</script>

<style>
button{
    margin-left: 20px;
}
</style>

vuex _getters 配置

计算共享数据, 类似于 computed 计算属性

// 配置getter属性 
const getters={
  bigSum(state){
    return state.sum*10
  }
}
//在暴露的时候添加进去
//创建store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})

在组件里可以调用的这个计算属性

this.$store.getters.bigSum

多组件共享数据

index.js

import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
Vue.use(Vuex)
// 这里创建 store
 // 求和相关配置

    // action 用于响应组件中的动作
const actions={
  jia(context,value){
    console.log('action 中的jia 被调用了 ');
    context.commit('JIA',value);
  },
  jian(context,value){
    console.log('action 中的jian 被调用了 ');
    context.commit('JIAN',value);
  },
  odd(context,value){
     console.log('action 中的odd 被调用了');
     context.commit('ODD',value);
  },
  wait(context,value){
   console.log('action 中的wait被调用了');
     context.commit('WAIT',value);
  }

}
// 准备mutation 响应组件中的动作
const mutations={
   JIA(state,value){
      state.sum+=value;
   },
   JIAN(state,value){
       state.sum-=value;
   },
   ODD(state,value){
       if(state.sum % 2){
           state.sum+=value;
       }
   },
   ADDPERSON(state,value){
         state.person.unshift(value);
   },
   WAIT(state,value){
   setTimeout(() => {
       state.sum+=value;
   }, 500);
   }

}
// 准备state -- 用于操作数据,存储数据
const state={
   sum:0,
   school:'here',
   subject:'front-end',
   person:[{id:'001',name:'张三'}]
}
// 配置getter属性 
const getters={
 bigSum(state){
   return state.sum*10
 }
}

 

//创建store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
})


添加数据 与数据绑定

mapxxx 为映射绑定只要名字对上就可以将 组件与 index 里面的 参数对上

mapState 是 vuex 的数据绑定中常用的 与state 里的进行绑定直接

还有mapgetter 用于绑定计算属性 getter

  //   常规写法
   // person(){
    //     return this.$store.state.person;
    // }
  // 对象写法
    // ...mapState({sum:'sum',school:'school',subject:'subject'}),
    // 数组写法
    ...mapState(['sum','school','subject','person']),  
        // state 的里的名字和 组件里面的名字一致就可以用
    // ********************************************************
          ...mapGetters(['bigSum'])

mapMutations, mapActions 用于绑定 store 里面的 mutations 和acitons

...mapMutations({increment:'JIA',decrement:'JIAN'}),
    ...mapActions({incrementOdd:'odd',incrementWait:'wait'}) 
      // increment(){
      //  this.$store.dispatch('jia',this.n)
      // },  常规方法使用 dispatch 和 commit

addperson 组件

<template>
   <div>
    <h1>人员列表</h1>
    <h3 style="color:red">count组件求和为:{{this.$store.state.sum}}</h3>
    <input type="text" placeholder="请输入名字" v-model="name"> <button @click="addPerson">添加</button>
    <ul>
        <li v-for="p in person" :key="p.id" >
          {{p.name}}
        </li>
    </ul>
   </div>
</template>

<script>
import { mapState } from 'vuex'
import {nanoid} from 'nanoid'

export default {
   name:'AddPerson',
   data(){
    return {
        name:''
    }
   },
   computed:{
    // person(){
    //     return this.$store.state.person;
    // }
    ...mapState(['sum','person'])
   },
   methods:{
     addPerson(){
        const newperson={id:nanoid(),name:this.name};
        this. $store.commit('ADDPERSON',newperson);
        // console.log(newperson);
        this.name='';
     }
   }
}
</script>

<style>

</style>

countNumber 组件

<template>
  <div>
      
      <h1>当前求和为:{{sum}}</h1>
      <h3>当前的放大之后的数是:{{bigSum}}</h3>
      <h3> 我在{{school}} 学习 {{subject}}</h3>
       <h3>下方加入的人数是 {{person.length}}</h3>
      <select v-model.number="n">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="increment(n)">+</button>
      <button @click="decrement(n)">-</button>
      <button @click="incrementOdd(n)">当前求和为奇数再加</button>
      <button @click="incrementWait(n)">等一等再加</button>
      <hr>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions} from 'vuex'

export default {
  name:'CountNumber',
   data(){
    return{
        n:1,
 
    }
   },
   methods:{
    // 可以使用mapMutaton 来批量绑定方法 直接提交 跳过dispatch
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    ...mapActions({incrementOdd:'odd',incrementWait:'wait'})
      // increment(){
      //  this.$store.dispatch('jia',this.n)
      // },
      // decrement(){
      //   this.$store.dispatch('jian',this.n)
      // },
      // incrementOdd(){
      //     this.$store.dispatch('odd',this.n)
      // },
      // incrementWait(){
      //     this.$store.dispatch('wait',this.n)
      // }
   },
   computed:{
     // 对象写法
    // ...mapState({sum:'sum',school:'school',subject:'subject'}),
    // 数组写法
    ...mapState(['sum','school','subject','person']),
    // ********************************************************
    ...mapGetters(['bigSum'])
  
   },
   mounted() {
    console.log(this.$store.getters.bigSum);
   },

}
</script>

<style>
button{
    margin-left: 20px;
}
</style>

模块化方式

index.js

根据使用的模块进行切分 index 分为 addoptions countOptions

import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
Vue.use(Vuex)
// 这里创建 store
 // 求和组件的配置
const countOptions={
  namespaced:true,
  // 开启命名空间,方便后面快绑定
  actions:{
    jia(context,value){
      console.log('action 中的jia 被调用了 ');
      context.commit('JIA',value);
    },
    jian(context,value){
      console.log('action 中的jian 被调用了 ');
      context.commit('JIAN',value);
    },
    odd(context,value){
       console.log('action 中的odd 被调用了');
       context.commit('ODD',value);
    },
    wait(context,value){
     console.log('action 中的wait被调用了');
       context.commit('WAIT',value);
    }
  },
  mutations:{
    JIA(state,value){
      state.sum+=value;
   },
   JIAN(state,value){
       state.sum-=value;
   },
   ODD(state,value){
       if(state.sum % 2){
           state.sum+=value;
       }
   },
   WAIT(state,value){
    setTimeout(() => {
        state.sum+=value;
    }, 500);
    }
  },
  state:{
   sum:0,
   school:'here',
   subject:'front-end',
  },
  getters:{
    bigSum(state){
      return state.sum*10
    }
  }
}
 //添加名字组件的形式
// action 用于响应组件中的动作
const addOptions={
  namespaced:true,
  actions:{
  },
  mutations:{
    ADDPERSON(state,value){
      state.person.unshift(value);
    },
  },
  state:{
    person:[{id:'001',name:'张三'}]
  },
  getters:{

  }
}



 

//创建store
export default new Vuex.Store({
   modules:{
    countAbout:countOptions,
    addAbout:addOptions
   }
})


模块化之后再组件里调用 要加模块名

// 传统的方法
this.$store.addAbout.xxx
this. $store.commit('addAbout/ADDPERSON',newperson);
// 要加入模块名
...mapState('countAbout',['sum']),
      ...mapState('addAbout',['person'])

index 里面的 模块也可以写到 js 文件里同时暴露出来使用 export default addOptions

只需在 index 导入就行 import countOptions from './count'

外接接口的操作

actions 进行异步操作 这里使用axios

person.js

import axios from 'axios'
import { nanoid } from 'nanoid'
    const addOptions={
        namespaced:true,
        actions:{
          //actions  commit 到 ADDPERSON
          addnet(context){
          axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
            response=>{
              context.commit('ADDPERSON',{id:nanoid(),name:response.data})
            },error=>{
              console.log('请求错误',error.message);
            }
          )
          }
        },
        mutations:{
          ADDPERSON(state,value){
            state.person.unshift(value);
          },
        },
        state:{
          person:[{id:'001',name:'张三'}]
        },
        getters:{
      
        }
      }
      export default addOptions

组件使用

 addnet(){
        this.$store.dispatch('addAbout/addnet')
     }

Vue routor 插件

该插件用于 多个页面 信息放在 一个页面 单页面应用(SPA) 就是页签应用 ,具体的操作就是用组件切换技术不断切换组件,而组件来自路径API

router 是根据 根据路径规则调用组件,管理组件

路由规则的是映射关系 (key-value)

key 为路径 value 是 function 和 component

前端路由 value 是function,工作过程是根据请求路径找到匹配的函数来处理请求

后端路由: 根据路由请求确定返回体

安装插件 npm i vue-router@3

创建一个 index.js 定义路由规则放在 router 文件夹下

在mian.js 里面 use router 里面的index 路由

main.js

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

new Vue({
  el:'#app',
  render: h => h(App),
  router:router
})

import VueRouter from 'vue-router'
// 引入组件
import  HomePage from '../routers/HomePage.vue'
import  DemoPage from '../routers/DemoPage.vue'
export default new   VueRouter({

    routes:[
    {path:'/about',
     component:DemoPage
    },{
        path:'/home',
        component:HomePage
    }
    ]
})

index.js 单层嵌套规则

组件调用的时候使用 router-link 该标签会转换为 a 标签 作为标签页

   <router-link class="list-group-item " active-class="active" to="/home">home</router-link>
    <router-link class="list-group-item" active-class="active" to="/about">demo</router-link>
//  使用 router-view 做为组件插入的位置
<router-view></router-view>

用于切换根据路由切换组件

切换的component 放在 一个单独的文件夹里便于管理

多级路由嵌套

在路由规则里 加入子规则 children ,注意这里路径不要 斜杠 n 级嵌套也是如此 加入 children 配置规则

// 该文件专门应用于整个路由器
// 引入组件
import VueRouter from 'vue-router'
// 引入组件
import  HomePage from '../routers/HomePage.vue'
import  DemoPage from '../routers/DemoPage.vue'
import HomeContent from '../routers/HomeContent.vue'
import HomeNews from '../routers/HomeNews.vue'
export default new   VueRouter({

    routes:[
    {path:'/about',
     component:DemoPage,
    
    },{
        path:'/home',
        component:HomePage,
        children:[{
            path:'homemessage',
            component:HomeContent
         },{
            path:'homenews',
            component:HomeNews
         }
            
         ]
    }
    ]
})

嵌套组件也单独写

嵌套的父组件写法这里的路径要写杠

<li ><router-link href="#" class="list-group-item" active-class="active" to="/home/homemessage">Message</router-link></li>
  <li ><router-link href="#" class="list-group-item" active-class="active" to="/home/homenews">News</router-link></li>

路由插槽还是不变

路由传参 和 路径命名

多层路由中 :子组件中的数据来自父组件的标签页 这里可以在 to 属性里面添加数据进行数据穿透

子组件 NewsDetail 的数据接受

<template>
  <div>
     <h3>新闻编号:{{$route.query.id}}</h3>
     <h3>新闻标题:{{$route.query.title}}</h3>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

父组件的设置 query

<template>
    <div>
        <ul>
    <li v-for="item in items" :key="item.id">
       <router-link :to="{path:'/home/homenews/detail',query:{id:item.id,title:item.title}}">{{item.title}}</router-link>
    </li>
  </ul>
   <router-view></router-view>
    </div>
  
</template>

<script>


export default {
name:'HomeNews',
data(){
    return{
      items:[
        {id:'01',title:'标题1'},
        {id:'02',title:'标题2'},
    ]
    }
}
}
</script>
<style>
</style>

多级路由 路径比较长时可以添加路径名

在index .js的路径规则里写入 name 属性

// 该文件专门应用于整个路由器
// 引入组件与插件
import VueRouter from 'vue-router'
import  HomePage from '../routers/HomePage.vue'
import  DemoPage from '../routers/DemoPage.vue'
import HomeContent from '../routers/HomeContent.vue'
import HomeNews from '../routers/HomeNews.vue'
import NewsDetail  from '../routers/NewsDetail.vue'
export default new   VueRouter({

    routes:[
    {path:'/about',
     component:DemoPage,
    
    },{
        path:'/home',
        component:HomePage,
        children:[{
            path:'homemessage',
            component:HomeContent
         },{
            path:'homenews',
            component:HomeNews,
            children:[
                {   name:'details',
                    path:'detail',
                    component:NewsDetail
                }
            ]
         }
            
         ]
    }
    ]
})

父组件 to 属性进行修改的时候路径改为 name

<router-link :to="{name:'details',query:{id:item.id,title:item.title}}">{{item.title}}</router-link>

path 只能和 query 属性一起使用,不能和params ,name属性可以和谁都可以使用

params 参数

和 query 一样用于传递参数

在路由规则里面要在path 属性里写入参数占位符 index.js

// 该文件专门应用于整个路由器
// 引入组件
import VueRouter from 'vue-router'
// 引入组件
import  HomePage from '../routers/HomePage.vue'
import  DemoPage from '../routers/DemoPage.vue'
import HomeContent from '../routers/HomeContent.vue'
import HomeNews from '../routers/HomeNews.vue'
import NewsDetail  from '../routers/NewsDetail.vue'
export default new   VueRouter({

    routes:[
    {path:'/about',
     component:DemoPage,
    
    },{
        path:'/home',
        component:HomePage,
        children:[{
            path:'homemessage',
            component:HomeContent
         },{
            path:'homenews',
            component:HomeNews,
            children:[
                {   name:'details',
                    path:'detail/:id/:title',
                    component:NewsDetail
                }
            ]
         }
            
         ]
    }
    ]
})

在子组件里用params 调用

  <h3>新闻编号:{{$route.params.id}}</h3>
     <h3>新闻标题:{{$route.params.title}}</h3>

在父组件里和query 一样调用

  <h3>新闻编号:{{$route.params.id}}</h3>
     <h3>新闻标题:{{$route.params.title}}</h3>

props 配置

在 index.js 里面配置数据传递,如果使用params时

在路由规则里面只要加入 props:true 即可

在子组件里加入 props 的配置即可

<template>
  <div>
     <h3>新闻编号:{{id}}</h3>
     <h3>新闻标题:{{title}}</h3>
  </div>
</template>

<script>
export default {
name:'NewsDetail',
props:['id','title']
}
</script>

<style>

</style>

如果使用 query 传递参数时,在路由规则配置数据传递 子组件和上面一致

  path:'detail',
  props($route){
      return{
      id:$route.query.id,
      title:$route.query.title
}

router 与页面前进后退

router-link 便签默认的是一个栈 push 模式 ,保存路由可回退 路由 ,如果不希望保存路由的操作就应该 在 router-link 里面写replace 关键字 这样不会保存

多个嵌套时 ,都要加replace 才会有效

<router-link replace class="list-group-item " active-class="active" to="/home">home</router-link>

编程式路由导航

不借助 router-link 但是实现 router-link 的功能 比如 用button实现 路由切换

在methods 里面写

// 使用push模式
函数名(传递的数据对象){
    this.$router.push({
    name:'组件名',
    params:{
        组件参数
    }
})
}
,
//使用replace模式
函数名(传递的数据对象){
    this.$router.replace({
    name:'组件名',
    params:{
        组件参数
    }
})
}
同时也可以替换为
this.$router.forward()//前进单个路由记录
this.$router.back() // 后退单个路由记录
this.$router.go(n) // n 为正数前进,负数为后退


缓存路由

路由组件切换之后会刷新输入,因此需要保持以前的输入就要在 router-view 标签外面包一个 keep-alive 标签 include 后面是缓存的组件名

<keep-alive include="组件名" ><router-view></router-view></keep-alive>
    //或者使用数组
<keep-alive :include=["组件1","组件2"] ><router-view></router-view></keep-alive>

路由组件的激活与失活

不是使用 beforeDestroy,或者 mounted

使用 actived(){} 或者 deactived(){} 可以用于路由组件之后的事件处理

路由守卫

根据权限切换路由,比如登录才能打开的页面,在路由规则里面设置 indeex.js设置

在路由规则外面写一个守卫判断权限 切换路由和初始化前进行调用

next ()放行 to 目的路由 from 当前路由

可以调用 to.meta 里的信息判断是否进行放行

// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
   
} )


在路由规则里面加入 是否增加守卫的元信息 用于判断是否进行权限校验

meta:{isAuth:fallse,'title':'名字' }

// 全局后置路由
router.afterEach(to,from)=>{
    有些信息的显示要再切换路由后进行
}

独享路由守卫 直接写在路由规则里面的守卫,针对某一个路由组件进行权限判断

路由规则里面加入

// 前置路由守卫
beforeEnter:(to,from,next)=>{
    ...  
}
// 没有后置路由守卫,可以配合全局后置路由守卫实现功能

组件内路由守卫

组件里面的路由守卫进入该组件时调用 mounted 同级 这里都是前后 是在调用的过程中

beforeRouteEnter(to,from,next){

} to 是定死的

// 离开路由规则,离开该组件时被调用

beforeRouteLeave(to,from,next){

} from 是定死的

vue 路由的模式

hash模式和 history 模式

hash 模式 路径的一部分不会在发送请求的时候发送给后端服务器 前端路由切换不会影响后端(默认,兼容性好) 路径里面有 # 会自动区分前端路由和后端路由

history 要在 路由规则之前 添加 mode:‘history’ 设置为 历史模式 路径里面没有# 容易引起请求错误404

如果用node 搭建的话可以使用 connect-history-api-fallback 解决 history 的问题 nginx 也就进行判断 前端路由和后端路由

打包开发文件 npm run build

得到的 dist 放在服务器上进行部署

建立一个在 node 里面安装一个 static 文件夹 dist 文件夹里的文件

image-20221125193825988转存失败,建议直接上传图片文件

UI 组件库

自定义的需求不高直接用组件库

移动端的组件库:

Vant , cubeUI , Mint UI NutU

PC 端常用的UI 组件库

  1. Element UI
  2. Iview UI
  3. bootstrap

babel-plugin-component element UI 组件按需引入,具有更小的文档 这里直接看文档

npm  install babel-plugin-component -D

在 cli 文件夹中的 babel.config.js 加入 plugins:[ ] 配置数组 ,新的脚手架要配置 preset

image-20221125200940565转存失败,建议直接上传图片文件

按需在 main.js 里面引入组件

Vue 3 学习

vue 3 的cli 安装与 vue2 类似

vite 创建

vite 是一种新一代前端构建工具,原来都用webpack

浏览器中的开发工具

下载 vue的 开发者工具插件最新版的 beta 同时禁用 旧版

组合式 API

setup

是vue3 新的配置项,值是一个函数 ,用于配置原来的键值配置 data methods mounted 最好不要混用vue2 的语法

组件里的 数据方法均要配置到 setup里 vue 组件里的 data 和methods 配置写在setup 里 可以直接把配置堆在里面 setupp 是一个函数

setup 返回对象和渲染函数,下面的代码是非响应式

<template>
  <div id="app">
<h1>学生信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄{{age}}</h2>
<button @click="print">输出</button>
  </div>

</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
// import {h} from 'vue'
export default {
  name: 'App',
  setup(){
    let name='kiri';
    let age=18;

    //方法
    function print(){
      console.log(name);
    }
    // 返回渲染函数 return ()=> h('h1' ,'便签内容')
      
    return{name,age,print}

  }
}
</script>

<style>
</style>

ref函数

需要加入ref 函数加工才能变为 有效的对象 RefImpl 引用实例对象 将基本类型数据变为响应式

在使用变量的时候才要对象形式调用 要用里面的值就要使用 .value 对象类底层使用 proxy-》reactive 函数

<template>
  <div id="app">
<h1>学生信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄{{age}}</h2>
<button @click="print">输出</button>
<button @click="change">改变</button>
  </div>

</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
export default {
  name: 'App',
  setup(){
    // data 数据
    let name= ref('kiri');
    let age=ref(18);

    //方法
    function print(){
      console.log(name);
    }
    function change(){
      name.value='kiri2';
      age.value=25;
    }    
    return{name,age,print,change}

  }
}
</script>

<style>
</style>

reactive 函数

对象类型变为响应式, reacttive 可以深层次的将数据变为响应式 ,普通类型用不了 该函数 使用对象的值时不需加 value 可以直接处理数据

把普通数据包裹在对象里处理效率较高

<template>
  <div id="app">
<h1>学生信息</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄{{age}}</h2>
<button @click="print">输出</button>
<button @click="change">改变</button>
  </div>

</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
import {reactive, ref} from 'vue'
export default {
  name: 'App',
  setup(){
    // data 数据
    let name= ref('kiri');
    let age=ref(18);
    let group=reactive({
      school:'ucla',
      address:'usa'
    })

    //方法
    function print(){
      // console.log(name);
      console.log(group.school);
      console.log(group.address);
    }
    function change(){
      name.value='kiri2';
      age.value=25;
      group.address='la'
    }    
    return{name,age,group,print,change}

  }
}
</script>

<style>
</style>

原理讲解

vue2 原理 defineProperty 里面 get set 方法修改对象 新增属性界面不会更新 ,修改数组下标不会更新( 要另外加 Vue.set 或者this.$set 添加,delete方法)

Vue3 原理

![(C:\Users\haha\AppData\Roaming\Typora\typora-user-images\image-20221127122058563.png)

image-20221127122455087转存失败,建议直接上传图片文件

Vue 电商项目

初始化项目

  1. 在工程文件夹下 cmd vue create shopapp(根目录名)

文件夹详解:

​ node_modules 文件夹:项目依赖文件夹

​ public 文件夹:一般放置一些静态资源 html 或者是图片

src 文件夹(程序原代码文件夹):

assets 文件夹:多个组件公用的静态资源图标等

components: 非路由组件

app.vue 根组件

main.js 打包的路口文件

babel.config.js :配置文件(babel配置文件)

package.json文件: 项目的身份证 项目具有什么依赖

package-lock.json 缓存性文件

2.让浏览器打开 快捷指令设置

在 package.json

"serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "start":" npm  run serve"
// npm  start   快捷打开开发者模式

在vue.config.js 中加入

lintOnSave:false

1.路由拆分,通过分为非路由组件,路由组件来

不变的是非路由组件: Header、Footer 【在首页、搜索页】 但是在登录页面没有

路由组件: Home 首页路由组件 、Search 路由组件、logoin 登录路由、Refister 注册路由,非路由组件

完成非路由组件Header 与 Footer 业务

在项目中,不在以 html+ css 为主 主要是业务与逻辑

2.编辑好静态样式

这里使用less loader 输入指令 npm install --save less less-loader