Vue阶段总结

Vue总结

vue的简介

Vue的特点和Web开发中常见的高级功能:

​ 1、解耦视图和数据

​ 2、双向数据绑定

​ 3、可复用的组件

​ 4、前端路由技术

​ 5、状态管理

​ 6、虚拟DOM(js对象)

学习vue的注意点

注意:

学习Vue框架,必须严格遵循他的语法规则,Vue代码的书写思路和以前的JQuery完全不一样。所以,在项目中使用Vue之前,必须先学习Vue的基本语法规则。

Vue 安装使用

方式一:直接CDN引入

<!-- 开发环境版本,包含了有帮助的命令行警告 --> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

方式二:下载和引入

// 开发环境 https://vuejs.org/js/vue.js
// 生产环境 https://vuejs.org/js/vue.min.js

方式三:NPM安装(今天先不掌握)

​ 后续通过Vue-Cli4(脚手架)方式引入,我们使用该方式

创建Vue对象

  1. 先看js代码,会发现创建了一个Vue对象

  2. 创建Vue对象的时候,传入了一个对象:{}

    2.1 {}中的el属性:该属性决定了这个Vue对象挂载到哪一个元素上,很明显,我们这里挂载到了id为app的元素上。

    2.2 {}中包含了data属性:该属性中通常会存储一些数据,好像上面例子中的str1就是直接定义出来的数据

<script>
    var vm = new Vue({    //这个Vue对象用来控制某一个标签里面的数据
        el:"#app",   //要控制的标签
        data:{
            str1:"hello vue",   //定义一个数据,在id为app的标签内部去使用
        }
    })
</script>

可以在里面填入正则表达式,变量,可以进行加减乘除等的运算或者字符串拼接

定义Vue对象基本格式

	var vm = new Vue({
        el: "要绑定的标签",
        data: {
            变量名1:值1,
            变量名2:值2,
            变量名3:值3,
            ...

        },
        methods: {
            方法名1: function(){

            },
            方法名2: function(){

            },
            方法名3: function(){

            }
        }
    });

Vue常见的语法格式(重点)

插值

{{ 变量key值 }}

v-bind控制标签属性

        <a v-bind:href="bd">百度</a>
        <a :href="tb">淘宝</a>
        
        可以使用简写  ‘:’

v-on事件格式

        <button v-on:click="num+=1">点我数字增加</button>
        
        <button v-on:click="add">点我数字加5</button>
        <button @click="add2(10)">点我数字加10</button>
        
        简写方法 “@”
        
        如果使用函数,必须在创建的Vue对象里面,添加methods
       methods:{
          add:function(){
               this.num += 5
                  },
          add2(){
               this.num += 10
                }

vue控制类名

	<div id="app">
        <!--控制标签的类名 :class= 的值可以是 js对象 {类名:布尔值} -->
        <div class="box" :class="{box1:bool1, box2:bool2}"></div>

        <!--控制标签的类名 :class= 的值可以是 数组 [类名1,类名2] -->
        可以是多个的类名
        <div class="box" :class="['box3', 'box4']"></div>
        
        <!--掌握-->
        <div class="box" :class=" bool3 ? 'current':'' "></div><!--通过控制bool3位真还是假,来控制这个盒子有还是没有current这个类-->
      

style样式(动态控制类名)

        <div :style="{fontSize:'40px' }">文字</div>
        
        可以使用对象
        <div :style="{ fontSize:false?'40px' : '50px'}">文字</div>
        
        可以使用数组
        <div :style="[{fontSize:false?'40px' : '50px'},{background:'pink'}]">文字</div>
    </div>
<!-- 第一种绑定class -->
<div :class="['classA', 'classB']"></div>

<!-- 第二种绑定class -->
<div :class="{'classA': true, 'classB' : false}"></div>

<!-- 第一种绑定style -->
<div :style="{fontSize: '16px', color: 'red'}"></div>
:style="{'background-image': 'url('+userInfoFn.headImg变量+')'}"

<!-- 第二种绑定style -->
<div :style="[{fontSize: '16px', color: 'red'}]"></div>


三元表达式:
:class="['iconfont样式名',item.checked变量()? 'true走这边': 'false走这边']"

控制标签是否显示

v-show

v-show是对样式层面的控制

例如

<p v-show = 'turn'>新闻</p>  在页面中的显示是 <p style="display: none;">新闻</p>

style="display: none

v-if

v-if是对dom节点的控制

例如: v-if指令的作用: 控制标签是否展示,不展示则删除

常与 v-eles 一起用(中间可以插入别的,但是一般识别距离最近的 v-if ; v-eles)


<!--<div v-if="bool1">11111</div>-->
<!--<div v-else>222222</div>-->
	bool1的值为true 第一个盒子被保留,删除第二个盒子,
	为false的话,第2个盒子保留,删除第1个盒子

两者的具体应用

v-show 一般用于频繁切换操作,否则可以使用v-if

列表的渲染(v-for)

语法:v-for="(item, index) in 需要遍历的数组数组"
			元素     索引

具体用法: <li v-for="i in list1">{{ i }}</li>

还可以做数组的添加和删除
unshift   >   在前面新增
push      >   在后面新增
shift     >   在前面删除
pop       >   在后面删除

对象的渲染(v-for)

语法:v-for="(value, key) in 需要遍历的对象"
		value值       key值
		
具体的用法:<!--i是my_obj对象中的值 j是my_obj对象中的键名-->
           <li v-for="(i, j) in my_obj">{{i}}  {{j}}</li>
           

表单数据绑定(双向数据绑定)

v-model的值是,vue接收表单元素的值的变量名, 即v1的值就是用户填写的内容
(甚至可以在data中设置v1的值,从而设置表单元素的默认值)

可以设置初始值,捕获改变后的值(最新的值)

可以做tab栏的切换

	<div id="app">
        <input type="text" v-model="txt1">  <!-- v-model表示了用户输入的这个数据-->
        <p>{{ txt1 }}</p>

        <select v-model="sel1">
            <option value="0">北京</option>
            <option value="1">上海</option>
            <option value="2">广州</option>
        </select>
    </div>
    <script>
        var vm = new Vue({
            el:"#app",
            data:{
                //通过改变txt1或者sel1,来使对应的表单元素的v-model的值发生了变化,所以这个表单元素的value就变化了(v-model看成我们之前将的value)
                txt1: '默认值',
                sel1: 0
            }
        })
    </script>

模板语法的插值操作(其他不常用指令)

v-html 往标签内部插入html文本

            data:{
                htmlText:"<p>你好!世界!</p>"
            }
            
            <p v-html = "htmlText"></p>   
            显示:你好!世界!

v-text 往标签内部插入普通文本(解析不了标签)

<p v-text = "htmlText"></p>
显示:<p>你好!世界!</p>

v-pre 在界面上直接展示胡子语法

<p v-pre>{{htmlText}}</p>
显示:{{htmlText}}

v-cloak 隐藏数据渲染到页面之前,胡子语法在界面上的展示

<p v-cloak>{{htmlText}}</p>

搭配 style 隐藏 使用
    <style>
        [v-cloak]{
            display: none;
        }
    </style>
    
如果不使用隐藏,在加载完成之前,显示:{{htmlText}} 加载完成之后,显示:<p>你好!世界!</p>

与加载的网络有关

数组方法

常用方法使用

var arr = [1, 2, 3]
// 往数组最后一位添加一个数字
arr.push(4) // [1, 2, 3, 4]
// 删除数组最后一个数字
arr.pop()   // [1, 2, 3]
console.log(arr)
// 往数组第一位添加一个数字
arr.unshift(0)
console.log(arr)
// 删除数组第一个元素
arr.shift()
console.log(arr)
// splice
// 删除第一个元素
arr.splice(1, 2) 
console.log(arr)
arr.splice(1, 2, 2, 4, 5) 
console.log(arr)
// 合并数组
console.log([1, 6].concat([5, 7]))

push(返回数组长度)、unshift(返回数组长度)、shift(返回删除的值)、pop(返回删除的值)、splice、concat(返回新数组)

filter方法

过滤

        let arr = [1, 2, 3, 4, 5, 6]

        // filter() 过滤  ,如果是true 则保留,如果是false 则过滤
        // 不会修改原数组
        let new1 = arr.filter((item, index) => {
            return item > 3
        })
        console.log(new1, arr);// [4, 5, 6]    [1, 2, 3, 4, 5, 6]

map方法

        // map() 修改数组里面的每一个元素,并且新的数组和旧的数组长度是一样的
        // 不会改变原数组
        let new2 = arr.map((item, index) => {
            return item + 2
        })
        console.log(new2);//[3, 4, 5, 6, 7, 8]

        let new3 = arr.map((item, index) => {
            return {id:item+2}
        })
        console.log(new3);// [{…}, {…}, {…}, {…}, {…}, {…}]

reduce方法

利用reduce方法遍历数组的每一个元素,reduce()调用结果最后返回一个最终值(最后一次return值)。

    //数组名.reduce(回调函数,pre的初始值)
    arr.reduce(function(pre, current){
        // reduce这个方法被调用时,会遍历arr这个数组的每一个元素,每遍历一个元素,就执行一次这里的代码
        // current表示当前正在遍历的这个元素
        // pre 是上一次的这个函数return的值
        // !!!因为第一次遍历没有上一个return值,所以,交给了第二个参数,设置pre的初始值
        console.log(pre, current)
        return 10
    },0)

	//!!!并且reduce方法最终会返回最后一次的return值


每一次,都返回pre

        // reduce: 遍历数组,不会改变原数组

        /*
        特点:
            1、reduce 第二个参数如果没有,name第一次的prev取数组的第一个元素,回调函数是从数组的第二个元素开始
                如果有第二个参数,name第一次的prev的值用的就是传入reduce的第二个参数,回调函数从数组的第一个元素开始遍历
            2、reduce里面的prev的值,第一次是由reduce第二个参数决定的,其他的情况下是上一次回调函数的返回值所决定的。
                reduce 返回值就是最后一次回调函数的返回值。
            */


用处:可以做计算,汇总……
语法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

(total  必填;初始值或者计算结束后的返回值(后面没有初始值,默认初始值为数组的第一个数 prev的第一次等于数组的第一个数)
(currentValue  必填  当前元素)
(currentIndex  可填  当前元素的索引)
(arr  可选  当前元素所属的数组对象)

(initialValue  可选。传递给函数的初始值 不填,默认是数组的第一个数,然后从第二数开始遍历)



其余prev 都是undefined 因为没有返回 return

数组去重

	var arr2 = [1, 2, 3, 1, 6, 2, 3]

    //ES6
    consoloe.log([...new Set(arr2)])
    console.log(Array.from(new Set(arr2)))

    var newArray = [];
    for(var i=0; i<arr2.length; i++){
        if(newArray.indexOf(arr2[i])==-1){
            newArray.push(arr2[i])
        }
    }
    console.log(newArray)

    var newArray2 = [];
    var obj = {};
    for(var i=0; i<arr2.length; i++){
        if(!obj[arr2[i]]){ //如果不在obj中,就表示不重复的数据,就在对象中添加键值对

            obj[arr2[i]] = arr2[i]
            newArray2.push(arr2[i])
        }
    }
    console.log(newArray2)

Vue的计算属性computed 的使用

            computed:{
                //computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
                ***函数 无法传入参数
                total(){
                    var a = this.arr.reduce(function(pre, current){

                        console.log(pre, current)

                        // var total = 当前这次的 price*count + 上一次的total
                        var total = current.price*current.count + pre
                        return total
                    },0)
                    return a
                }
            }

computed内部方法有缓存的作用

函数调用了三遍,却只执行了1遍,这是computed内部方法的缓存起了作用

computed内部方法的另一种写法(知道有get和 set的格式)

		....
		computed:{
                //computed里面的方法必须有返回值!这个return值将来在视图中被{{total}}引用
                total:{
                    get(){
                        console.log("total_get")
                        var a = this.arr.reduce(function(pre, current){

                            // var total = 当前这次的 price*count + 上一次的total
                            var total = current.price*current.count + pre
                                return total
                            },0)
                            return a
                    },
                    set(){
                        console.log("total_set")
                    },
                }
            }
	...
    

vm.total = 3   //触发调用set方法

使用:value和@input代替v-model

v-model 本质上包含了两个操作:

  1. v-bind 绑定input元素的value属性
  2. v-on 指令绑定input元素的input事件
<input type="text" v-model="textVal"/>
<!-- 等同于 -->
<input type="text" v-bind:value="textVal" v-on:input="textVal = $event.target.value"/>

本地存储

localStorage永久存储

语法:

// 添加数据;setItem的value值是字符串类型的数据
先对数组或对象进行字符串转换------JSON.stringify(数组或对象);

localStorage.setItem('name','张三')// 获取数据
取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
	例子:
        let time = localStorage.getItem('time');
        console.log(JSON.parse(time));

localStorage.getItem('name'); // 张三

// 删除(time)某一项数据的值
localStorage.removeItem('time')

// 清空
localStorage.clear();

注意事项:

  1. 永久存储的,除非是主动删除,不然是不会自动删除的;删除浏览器不会被删除,删除只能调用clear方法
  2. 一般浏览器存储的大小是5M
  3. 存储的值必须是字符串(先对数组,对象进行字符串转换,JSON.stringify)

5M = 1024 * 5kb

sessionStorage临时会话存储

语法:

// 添加数据;setItem的value值是字符串类型的数据
存储的值必须是字符串(先对数组,对象进行字符串转换,JSON.stringify)
JSON.stringify(数组或对象);

sessionStorage.setItem('name','张三')// 获取数据
取值如果是对象,数组字符串的,那么要进行转换,JSON.parse
	例子:
        let time = localStorage.getItem('time');
        console.log(JSON.parse(time));
sessionStorage.getItem('name'); // 张三

// 删除(time)某一项数据的值
sessionStorage.removeItem('time')

// 清空
sessionStorage.clear();

注意事项:

  1. 临时存储,关闭浏览器会自动清空数据
  2. 一般浏览器存储的大小是5M

cookie

网站中,http请求是无状态的。也就是第一次登陆成功(发送请求),第二次请求服务器依然不知道是哪一个用户。这时候的cookie就是解决这个问题的,第一次登陆后服务器返回数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求,浏览器自动会把上次请求存储的cookie数据自动带上给服务器,服务器根据客户端的cookie来判断当前是哪一个用户。cookie存储有大小限制,不同浏览器不一样,一般是4kb,所以cookie只能存储小量数据。

token: 用户登录凭证,服务端返回给浏览器(前后端分离项目,基本都是发送ajax请求)

session

session和cookie的作用有点类似,也是存储用户相关信息。不同的是cookie存储在浏览器,而session存储在服务器。

Vue过滤器

语法:

   过滤器    filters: {
      函数        toFix2(price参数变量名) {
                    return price.toFixed(2)(toFixed 把数字转换为字符串,结果的小数点后有指定位数的数字)
									(用法:toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。)
              	  },
                }

Vue指令

深入双向数据绑定原理

vue双向数据绑定原理:

借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定

vue的双向数据绑定原理是什么?

vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的。
vue是通过Object.defineProperty()来实现数据劫持,其中会有getter()和setter方法;当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;

语法:

Object.defineProperty(obj, prop, desc)

1. `obj` 需要定义属性的当前对象
2. `prop` 当前需要定义的属性名
3. `desc` 属性描述符

数据属性:

通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:

  1. value 表示它的默认值
  2. writable 如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)
  3. configurable 描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)
  4. enumerable 描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)

例子:

        var p = {};
        // defineProperty(对象, 属性名, {value: 值}) === p.name = '张三'
        Object.defineProperty(p, 'name', {
            value: '张三',
            writable: true, // writable: 设置属性(name)是否可以修改,默认值: false(不可修改),true(可以修改)
            configurable: true, // configurable: 控制属性是否可以删除,默认值:false(不可以删除), true(可以删除)
            enumerable: true, // enumerable: 控制属性是否可以被遍历,默认值:false(不可以遍历), true(可以遍历)
        })
        console.log(p.name);
        p.name = '李四'//不可以直接修改,要先设置属性
        console.log(p.name);
        // delete p.name;//不可以直接删除,要先设置属性
        // console.log(p);
        for (let key in p) {
            console.log(key)//不可以直接遍历,要先设置属性
        }
        


另一种方法:
        let num = 0;
        // 定义p对象上age属性; get和set方法分别对age属性的获取和赋值进行拦截,get方法的返回值就是age属性对应的值
        Object.defineProperty(p, 'age', {
            // value: 20,
            get() {//监听读取操作
            获取数据的时候 本身是没有值的 值是有另外一个函数 return 出来的
            get就是在读取name属性这个值触发的函数
                console.log('age上的get方法')
                // document.getElementById()
                return num;
            },
            set(val) {//监听写入操作
            改变数据的时候 进入的set 里面
            set就是在设置name属性这个值触发的函数
                num += (val + 100)
                console.log('age上的set方法', val)
            }
        })
        p.age = 30;
        console.log(p.age);
        

自定义指令

除了一些内置的制定(v-model和v-show…),Vue也允许注册自定义指令。

全局自定义指令格式:

// 注册一个全局自定义指令 v-demo
Vue.directive('demo(指定名)', {
	inserted: function (el, binding) {
		
		console.log(el, binding);
	},
    update(el, binding){}
})

局部指令

// 组件中注册局部指令
new Vue({
	el: '#app',
	data: {},
	directives: {
		demo(指令名): {
			bind(el, binding){
			
			},
			inserted: function (el, binding) {
				cosnole.log(el, binding);
			},
			update(el, binding){
			
			},
		}
	}
})

钩子函数:

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。

参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-demo="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值
    • expression:字符串形式的指令表达式。例如 v--demo="1 + 1" 中,表达式为 "1 + 1"
    • modifiers:一个包含修饰符的对象。例如:v-demo.foo.bar 中,修饰符对象为 { foo: true, bar: true }

Vue组件化开发

组件化的思想

  1. 如果我们实现一个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
  2. 但如果我们这时候把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。

组件化开发的优势:可维护性高 可复用性高

组件化思想的应用开发:

  1. 有了组件化的思想,我们在之后的开发中就要充分的利用它。
  2. 尽可能的将页面拆分成一个个小的、可复用的组件。
  3. 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

局部组件

通过 Vue.component 方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

    <template id="tmp1">
        <!-- 组件必须只有一个根标签 -->
        <div>
            <header>头部 num: {{num}}</header>
            <div>123</div>
        </div>
    </template>
    
    
        new Vue({
            el: "#app",
            components: {
                // key: 组件名称, value: 组件内容
                aheader: {//任意命名
                //template标签------实际上 元素是被当做一个不可见的包裹元素,主要用于分组的条件判断和列表渲染。
                // 只能用这个标签包裹
                template : '#tmp1',
                    // 组件内的data必须是一个函数
                    data() {
                        return {
                            num: 1
                        }
                    }
                }
            }
        })

全局组件

通过Vue CLI 进行Vue全局组件

1、下载安装包

npm install -g @vue/cli
# OR
yarn global add @vue/cli

2、检查版本,并确认是否安装成功

vue --version

3、如需升级全局的 Vue CLI 包,请运行:

npm update -g @vue/cli

# 或者
yarn global upgrade --latest @vue/cli

4、创建一个项目

vue create 自定义文件名(英文)
例如
vue create hello-world

5、下载所需要的包工具

会自动识别文件package-lock 里面所需要的工具包

npm i

6、开发模式运行

npm run serve

生产模式运行

npm run build

7、删除项目

del 文件名

8、转换文件夹名

cd 文件路径
例如:
cd ./文件名

9、清空

cls   清空

父组件和子组件

组件和组件之间存在层级关系,而其中一种最重要的关系就是父子组件关系。

data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据

    data() {//函数
      return {//返回一个对象
        title: 'zujianbiaoti'
      }
    }

为什么data在组件中必须是一个函数呢?

原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。

父子组件间的通讯

父级向子级传递

在组件中,使用选项props来声明需要从父级接收到的数据。

props的值有两种方式:

  1. 字符串数组,数组中的字符串就是传递时的名称。
  2. 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
父级
<名 :任意名 = 需要传入子级的数据对象名></>

子级
  props: {
    需要传入子级的数据对象名(wrapperDate): {
      type: Object,
      default: {},//默认值
      required:false/true  //必填,一定要传值
    }
    
    
   三种写法,另外另种
   一、
   props: {
           num: Number,
           }
           
  二、
  props :['num']

 然后子级就可以直接调用
 {{wrapperDate.cancelTxt}}
子级向父级传递

子组件向父组件传递数据,通过自定义事件

子级
<div @click="函数名(submitFn)(参数)"></div>

  methods: {
    cancelFn(参数) {
      this.$emit("canceler自定义事件名" 参数);
    },
  },
  
  父级
  <Wrapper @canceler自定义事件名='fn函数名' :wrapperDate = 'textObj'></Wrapper>
  
    methods:{
    fn(){
      this.clickResult = this.textObj.cancelTxt
    },
  },

多层父子组件之间的通讯

例如祖父和孙子之间或者更多层

使用:Vue提供的更高阶方法:provide/inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

inject 选项应该是:

一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
在可用的注入内容中搜索用的 key (字符串或 Symbol),或
一个对象,该对象的:
from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
default property 是降级情况下使用的 value
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

具体用法:
// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}


兄弟组件间的传递

方法一:bus

EventBus中央事件总线

1、新建一个bus.js的文件,在文件中写入

import Vue from 'vue'
export default  new Vue

2、在需要进行操作的组件中,引入bus.js

import bus from "../../bus/bus"

3、传参的组件

    事件
    showFn() {
    //用bus.$emit(第一个参数代表事件名称,第二个参数代表需要传过去的参数,第二个参数代表需要传过去的第二个参数……可以传入多个参数)
      bus.$emit("showninfo", this.showin);
    },

4、接收参数的组件

通过$on监听事件。

//挂载
   mounted(){
   
    bus.$on('showninfo',val=>{
      console.log(val);
      this.showuin = val
    })

this.bus. e m i t 和 t h i s . b u s . emit 和this.bus. emitthis.bus.on来进行跨组件通信了

方法二:Vuex 提供的功能
变更vuex中的数据或者状态
this.$store.commit('increment')
第一个是方法名,第二个是参数


拿到vuex中的数据
访问数据用:$store
methods: {
  increment() {
    this.$store.commit('increment')
    console.log(this.$store.state.count)
  }
}

通常放到计算属性中
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}
方法三:v-model

v-model 的本质就是绑定一个属性和事件

// 父组件
<aa class="abc" v-model="test" ></aa> 

// aa子组件实现一:
<template>
  <div>
    <ul>
      <li>{{'里面的值:'+ msg}}</li>
      <button @click="fn2">里面改变外面</button>
    </ul>
  </div>
</template>

<script>
  export default {
    model: {    // 使用model
      prop: 'msg', //prop属性将msg作为该组件被使用时(此处为aa组件被父组件调用)v-model能取到的值,
      event: 'cc' // event中的cc就是父组件上的自定义事件,用来更新父组件上的test值
    },
    props: {
      msg: ''
    },
    methods: {
      fn2 () {
        this.$emit('cc', this.msg+2)
      }
    }
  }
</script>

// aa子组件实现方法二:
<template>
 <div>
    <ul>
      <li>{{'里面的值:'+ value}}</li> // value会被赋值为v-model绑定的test的值。
      <button @click="fn2">里面改变外面</button>
    </ul>
  </div>
</template>

<script>
  export default {
    props: {
      value: { // 必须要使用value
     default: '',
    },
    },

    methods: {
      fn2 () {
        this.$emit('input', this.value+2) // 这儿必须用input 发送数据,发送的数据会被父级v-model=“test”接受到,再被value=test传回来。
      }
    }
  }

插槽slot

为什么要使用插槽?

slot翻译为插槽,组件的插槽:

  1. 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  2. 让使用者可以决定组件内容的一些内容到底展示什么。

如何封装合适呢?抽取共性,保留不同

  1. 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
  2. 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
  3. 是搜索框,还是文字,还是菜单。由调用者自己来决定。

匿名插槽

如何使用slot?

  1. 在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。
  2. 该插槽插入什么内容取决于父组件如何使用。
使用
<slot>我是插槽中的默认内容!!</slot>
<Todolist>被替换的内容</Todolist>

父组件

    <Todolist>
        <a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
    </Todolist>
    
 子组件
  <slot></slot>
  
当组件渲染的时候,<slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:

具名插槽

当子组件的功能复杂时,子组件的插槽可能并非是一个。

  1. 比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
  2. 那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
  3. 这个时候,我们就需要给插槽起一个名字

如何使用具名插槽呢?

  1. 非常简单,只要给slot元素一个name属性即可
  2. <slot name='myslot'></slot>
父组件

    <Todolist>
    //用<template></template>标签将内容包起来,并在标签上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称,向具名插槽提供内容。
      <template v-slot:btn>
        <a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
      </template>
    </Todolist>
    
 子组件
  <slot name="btn"></slot>
  
当组件渲染的时候,name相等的 <slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML:

任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。

注意 v-slot 只能添加在 <template> 上(有一种情况例外)

作用域插槽

默认情况下,父组件使用子组件,插槽数据默认是拿父组件的数据,而不是子组件拿数据。

作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。

​ 作用域插槽,作用:可以拿到子组件slot标签上自定义属性的集合


父组件

    <Todolist>
      <template v-slot:btn={idx}>
        <a href="javascript:;" class="del" @click="reMove(idx)">删除</a>
      </template>
    </Todolist>
    
 子组件
      <template v-slot:default="{$index}">
        <slot name="btn" :idx="$index"></slot>
      </template>
      
      {idx}是Es6的解构语法
      $index 自定义的命名
      
      default意思是默认,下一个子组件不需要给name属性,直接可以渲染替换<slot></slot>里面的内容
  孙子组件
  		<slot :$index="i"></slot>
  
当组件渲染的时候,<slot></slot> 将会被替换为“a标签”(<Todolist>标签里面的内容)。插槽内可以包含任何模板代码,包括 HTML
多种写法
// 1、基本写法
<one-comp>
  <button slot="btn" slot-scope="scope">按钮{{scope.msg}}</button>
</one-comp>

// 2、基本写法之模板写法
<one-comp>
  <template slot="btn" slot-scope="scope">
    <button>按钮{{scope.msg}}</button>
  </template>
</one-comp>

// 3、指令写法
<one-comp v-slot:btn="scope">
  <button>按钮{{scope.msg}}</button>
</one-comp>

// 4、指令写法之模板写法
<one-comp>
  <template v-slot:btn="scope">
    <button>按钮{{scope.msg}}</button>
  </template>
</one-comp>

FileZilla

FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本

Webpack

Webpack(自动化 模块化 前端开发构建工具)

1. gulp和webpack

Gulp侧重于前端开发的 整个过程 的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。

1. 生成项目依赖文件
// 执行后生成package.json文件
npm init -y
2. 安装依赖(node环境在12.10.0下!)

nvm install 12.10.0

nvm use 12.10.0

npm i webpack@4.44.1 webpack-cli@3.3.12 -g

// 最后的参数-D是安装到package.json的开发依赖devDependencies(开发环境)对象里,也可以用 --save-dev代替
npm install webpack@4.44.1 webpack-cli@3.3.12 -D

// 全局安装webpack和webpack-cli
npm i webpack@4.44.1 webpack-cli@3.3.12 -g

// -S是--save的简写,这样安装的话,会安装到dependencies(生产环境)对象里,也可以用 --save代替
npm install jquery -S
// package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.40.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "jquery": "^3.4.1"
  }
}

devDependencies与dependencies的区别:

在发布npm包的时候,本身dependencies下的模块会作为依赖,一起被下载;devDependencies下面的模块就不会自动下载了;但对于项目而言,npm install 会自动下载devDependencies和dependencies下面的模块。

3.创建入口文件
`index.html`

引入js文件:<script src="./index.js"></script>

 js文件
 import $ from 'jquery'
$('ul li:even').css({background: 'red'})
$('ul li:odd').css({background: 'green'})

浏览器打开会出现报错,因为浏览器并不兼容import引入模块这种方式,所以我们要用到webpack打包

4、通过webpack打包
// 执行命令  output输出
webpack index.js -o dist/bundle.js

出现 “无法将webpack 识别为……的报错

出现这个报错,这是因为命令行执行时候会找全局的webpack,但是我们并没有安装全局的webpack,所以我们可以安装全局webpack,或者是使用脚本方式启动

执行package.json文件中添加的start命令

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
      
      
    "start": "webpack index.js -o dist/bundle.js"
      
      
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.40.2",
    "webpack-cli": "^3.3.9"
  },
  "dependencies": {
    "jquery": "^3.4.1"
  }
}

跑一下

// 生成 dist文件夹和bundle.js文件
npm run start

然后再把index.html原来引入index.js的地方改成是通过webpack生成的bundle.js

<!--index.html文件-->
<!--<script src="./index.js"></script>-->
<script src="./dist/bundle.js"></script>

在根目录里,建立webpack.config.js:

const path = require('path');

module.exports = {
  entry: path.join(__dirname, './index.js'),	// dirname代表索引到文件所在目录
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  }
}

package.json:

文件修改命令

"scripts": {
    "start": "webpack --config webpack.config.js"
  }
webpack-dev-server

这时候如果修改index.html的背景颜色red改成是gray,会发现浏览器刷新也没有效果,需要再跑一次npm run start命令才有用,这时候就需要webpack-dev-server(热重载)

安装:

npm install webpack-dev-server@3.11.2 -D

package.json:

"scripts": {
    "start": "webpack-dev-server --config webpack.config.js --open --port 3002 --hot"
  }
// --open 自动打开浏览器
// --port 服务监听的端口 3002
// --hot  自动更新

html-webpack-plugin

安装:npm install html-webpack-plugin@4.5.1 -D

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: path.join(__dirname, './index.js'),
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, './index.html'),
      filename: 'index.html'
    })
  ]
}

删掉index.html文件里面的bundle.js引用,因为html-webpack-plugin会自动把打包出来的bundle自动加到我们的index.html代码里

因为webpack默认是不识别.css文件的,需要我们通过 loader.css 文件进行解释成正确的模块。

安装css-loader和style-loader
npm install css-loader@5.2.4 style-loader@2.0.0 -D 
//index.css -> bundle.js -> style-loader -> <style> index.html
// css-loader的作用是将index.css文件解析为webpack能识别的模块,然后打包进bundle.js里面,但是这样样式并未成功显示在浏览器中。
// style-loader的作用就是将打包到bundle.js中的样式绑定到浏览器上,以style标签的形式显示出来

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: path.join(__dirname, './index.js'),
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, './index.html'),
      filename: 'index.html'
    })
  ],
  module: {
    rules: [{
      test: /\.css$/,
      use: ['style-loader', 'css-loader']	// 注意:这里的数组是反向读取的(即从右往左)
    }]
  }
}

补充:引入的文件是less

安装:npm install less-loader@7.2.1 less@4.1.0 -D

规则:

{

​ test: /.less$/,

​ use: [‘style-loader’, ‘css-loader’, ‘less-loader’]

}

ES6 转 ES5

安装依赖包

npm install babel-core babel-loader@7.1.5 babel-plugin-transform-runtime@6.23.0 babel-preset-env@1.7.0 babel-preset-stage-0@6.24.1 -D
html热更新

在安装过html-webpack-plugin之后,安装:

npm install --save-dev raw-loader@4.0.2

在webpack.config.js中配置raw-loader:

module.exports = {
  ......
  module: {
    rules: [
      {
         test: /\.(htm|html)$/,
         use: [
           'raw-loader'
         ]
      },
      ......
    ]
  }
}

index.js 中引入html:

import './index.html'

Vue CLI脚手架

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 实现的交互式的项目脚手架。

  • 通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。

  • 一个运行时依赖 (

    @vue/cli-service
    

    ),该依赖:

    • 可升级;
    • 基于 webpack 构建,并带有合理的默认配置;
    • 可以通过项目内的配置文件进行配置;
    • 可以通过插件进行扩展。
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。

  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。

**安装Vue CLI脚手架的包

npm install -g @vue/cli
# OR
yarn global add @vue/cli
检查版本

安装成功之后,可以用这个命令来检查其版本是否正确 (4.x):

vue --version
vue -V

如果安装比较慢,可以把下载源切换成淘宝的源:

npm 对应的淘宝下载源设置:

//切换taobao镜像源
npm config set registry https://registry.npm.taobao.org/
// 查看下载源
npm config get registry

yarn 对应的淘宝下载源设置:

//切换taobao镜像源
yarn config set registry https://registry.npm.taobao.org/

// 查看下载源
yarn config get registry

创建项目

初始化项目

vue create

运行以下命令来创建一个新项目:

vue create 文件名
例如
vue create hello-world
会自动新生成一个文件

可以选择手动模式生成

对于每一项的功能,此处做个简单描述:

  • TypeScript 支持使用 TypeScript 书写源码。
  • Progressive Web App (PWA) Support PWA支持
  • Router 路由
    Vuex 状态管理
    CSS Pre-processors 支持 CSS 预处理器。
    Linter / Formatter 支持代码风格检查和格式化。
    Unit Testing 支持单元测试。
    E2E Testing 支持 E2E 测试。

vue.config.js

vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。

这个文件应该导出一个包含了选项的对象:

// vue.config.js
module.exports = {
  // 选项...(例如:)
  lintOnSave: false	// 关闭eslint
}

静态资源参考:

HTML和静态资源

预处理器 (Sass/Less/Stylus):

如果当时没有选好,内置的 webpack 仍然会被预配置为可以完成所有的处理。你也可以手动安装相应的 webpack loader

# Sass
npm install -D sass-loader sass

# Less
npm install -D less-loader less

# Stylus
npm install -D stylus-loader stylus
1、需要先额外安装一个全局的扩展
npm install -g @vue/cli-service-global

环境

开发环境

生产环境

预生产环境

测试环境

打包部署

npm run build

多了个dist目录文件夹(html css,js)

部署

上传本地的dist文件夹到服务器

使用FileZilla

1、新建站点,选择SETP协议

报错404,可能文件路径出现问题了(加个.就好了)

安装浏览器插件Vue-Devtools

Vue-Devtools是vue在做项目开发时常用的浏览器插件。

安装

  • 点击浏览器右上角的 三个点 ,在 更多工具 中,选择 扩展程序
  • 打开 扩展程序开发者模式
  • Vue.js Devtools_5.3.3.crx 这个插件直接拽入当前界面,即可完成安装

常用修饰符

vue中常用的修饰符分三种,分别是事件修饰符、按键修饰符和表单修饰符。

1、事件修饰符

  • .stop 阻止事件冒泡(*)
  • .prevent 阻止默认事件(*)
  • .prevent.stop 阻止默认事件的同时阻止冒泡
  • .once 阻止事件重复触发(once与stop不能一起使用,否则再次触发事件,依然会冒泡)(*)

语法: @click.stop.prevent = “事件名”

<template>
    <div>
        <div class="big" @click="cb">
            <div class="small" @click="cs">
                <a href="#" @click.stop.prevent="ca">a标签</a>
            </div>
        </div>
        <button @click.once="cc">点我</button>
    </div>
</template>

<script>
export default {
    
    methods:{
        cb(){
            console.log("点击大的");
        },
        cs(){
            console.log("点击小的");
        },
        ca(){
            console.log("点击a标签");
        },
        cc(){
            console.log("点击按钮");
        }
    }

}
</script>

<style>
.big{
    width: 300px;
    height: 300px;
    background-color: pink;
}
.small{
    width: 200px;
    height: 200px;
    background-color: skyblue;
}
</style>

2、按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<input v-on:keyup.enter="submit">
<input v-on:keyup.13="submit">

3、表单修饰符

表单上常用的 trimnumber

想去除用户输入的前后空格:

<input v-model.trim="val" type="text">

希望在input的change之后再更新输入框的值

<input v-model.lazy="val" type="text">

.trim与.lazy可以合并使用:

二者没有先后顺序

<template>
<!-- 表单修饰符 -->
    <div>
        <!-- 加了.trim之后,获取到的是去除前后空格的结果 -->
        <input type="text" v-model.trim="iptVal">
        <button @click="handleClcik">按钮</button>
        <br>
        <!-- 加了.number之后, 获取到的用户输入的信息就是number类型了-->
        <input type="number" v-model.number="numVal">
        <button @click="handleClcik2">按钮</button><br><br>
        <!-- 加了.lazy, 用户每一次触发就不会马上更新这个值,等到用户按回车的时候,才更新这个值 -->
        <input type="text" v-model.lazy="ipt2Val">
        <p>{{ipt2Val}}</p>
    </div>
</template>

Watch

watch的作用可以监控一个值的变换,并调用因为变化需要执行的方法。可以通过watch动态改变关联的状态。

简单点说,就是实时监听某个数据的变化。

普通监听

    <input type="text" v-model="msg">
    <h3>{{msg}}</h3>
    
    
      watch: {
    msg(val, oldVal){
      console.log(val, oldVal)
    }
  }
  
  跟data 同级
  

立即监听

如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

<input type="text" v-model="num">

跟data 同级
  watch: {
监听变化的数据名
    num: {
      handler(val, oldVal) {
        console.log(val, oldVal);
      },
      // 组件注入页面时就立即监听
      immediate: true
    }
  }

immediate需要搭配handler一起使用,其在最初绑定时,调用的函数也就是这个handler函数。

深度监听

当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

<h3>{{obj.age}}</h3>

<script>
export default {
  name: 'Home',
  data(){
    return {
      obj: {
        name: "Lucy",
        age: 13
      }
    }
  },
  methods: {
    btnClick(){
      this.obj.age = 33;
    }
  },
  watch: {
    obj: {
      handler(val, oldVal){
        console.log(val.age, oldVal.age)		//  33   33
      },
      deep: true
    }
  }
}
</script>

注意:

1、如果监听的数据是一个对象,那么 immediate: true失效;

2、一般使用于对引用类型的监听,深度监听,如监听一个Object,只要Object里面的任何一个字段发生变化都会被监听,但是比较消耗性能,根据需求使用,能不用则不用。

3、因为上面代码obj是引用数据类型,val, oldVal指向一致,导致看到的结果一样。

deep优化

我们可以通过点语法获取对象中的属性,然后转为字符串,即是对深度监听的优化

<template>
  <div class="home">
    <h3>{{obj.age}}</h3>
    <button @click="btnClick">按钮</button>
  </div>
</template>

<script>
export default {
  name: "Home",
  data() {
    return {
      obj: {
        name: "Lucy",
        age: 13
      }
    };
  },
  methods: {
    btnClick() {
      this.obj.age = 33;
    }
  },
  watch: {
    // 通过点语法获取对象中的属性,然后转为字符串,即是对深度监听的优化
    "obj.age": {
      handler(val, oldVal) {
        console.log(val, oldVal);
      },
      deep: true,
      immediate: true,		// 此时监听的数据不是一个对象,可以使用immediate
    }
  }
};
</script>

Watch与Computed的区别

  • watch中的函数是不需要调用的,computed内部的函数调用的时候不需要加()
  • watch(属性监听),监听的是属性的变化,而computed(计算属性),是通过计算而得来的数据
  • watch需要在数据变化时执行异步或开销较大的操作时使用,而对于任何复杂逻辑或一个数据属性,在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。
  • computed 属性的结果会被缓存,且computed中的函数必须用return返回最终的结果
  • watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;

Watch与Computed的使用场景

  • computed

    • 当一个结果受多个属性影响的时候就需要用到computed
    • 最典型的例子: 购物车商品结算的时候
  • watch

    • 当一个数据的变化需要有额外操作的时候就需要用watch
    • 搜索数据
  • 总结:

    • 一个值的结果受其他值的影响,用computed
    • 一个值的变化将时刻影响其他值,用watch

Mixins混入

mixins就是定义一部分公共的方法或者计算属性,然后混入到各个组件中使用,方便管理与统一修改。同一个生命周期,混入对象会比组件的先执行。

1、导出mixins

在src下创建 mixins/index.js,写入:

export const MixinsFn = {
    created() { 
        console.log("这是mixins触发的created")
    }
}

2、引用mixins

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
<script>
import { MixinsFn } from '@/mixins/index.js'
export default {
  created(){
    console.log("这是about触发的created")
  },
  mixins: [MixinsFn]
}
</script>

我们会发现,mixins中的createdabout中的created 优先执行。

ref与$refs

vue中获取页面里的某个元素(标签或组件),可以给它绑定ref属性,有点类似于给它添加id名。

1、简单使用

<template>
 <div class="">
     //ref  = '属性名'
     //获取 $refs.属性名======获取到标签
     <h3 ref="title">{{msg}}</h3>
     <button @click="btnclick">按钮</button>
 </div>
</template>
 
<script>
export default {
  data() {
    return {
      msg: "你好"
    };
  },
  methods: {
    btnclick() {
      console.log(this.$refs.title);		// 得到h3标签
    }
  }
};
</script>
 
<style lang = "less" scoped>
</style>

2、子组件中的数据获取及方法调用

子组件:

<template>
 <div class="">
     <h4>{{num}}</h4>
 </div>
</template>
 
<script>
export default {
  data() {
    return {
      num: 100
    };
  },
  methods: {
      subFn(){
          console.log('子组件中的方法')
      }
  }
};
</script>
 
<style lang = "less" scoped>
</style>

父组件:

<template>
 <div class="">
     <Sub ref="sub" />
     <button @click="btnclick">按钮</button>
 </div>
</template>
 
<script>
import Sub from '../components/Sub'
export default {
  methods: {
    btnclick() {
      //获取子组件的属性值和子组件的方法
      console.log(this.$refs.sub.num);	// 100   
      this.$refs.sub.subFn();						// '子组件中的方法'
    }
  },
  components: {
      Sub
  }
};
</script>
 
<style lang = "less" scoped>
</style>

获取Dom节点

<button @click="fnNum" ref="btn">获取child组件的num</button>


methods:{
    fn() {
      console.log(this.$refs.btn); //可以获取Dom节点----<button @click="fnNum" ref="btn">获取child组件的num</button>
	this.$refs.btn.innerText 可以获取Dom节点的内容-----获取child组件的num
    },
}

vue生命周期

什么是生命周期: 从Vue创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。

**生命周期钩子函数:**就是生命周期事件的别名;

初始

**beforeCreate:**实例刚刚在内存中被创建出来,此时还没有初始化 datamethods 属性。

**created:**实例已经在内存中创建好,此时 datamethods 已经创建好,此时还没有开始编译模板

和 methods、data 是同级
初始
  beforeCreate() {
    console.log("1.1---------------beforeCreate");
    console.log(1.1, this.num, this.fn, document.getElementById("op"));
  },
  created() {
    console.log("1.2-------------created");
    console.log(1.2, this.num, this.fn, document.getElementById("op"));
  },
      
挂载
  beforeMount() {
    console.log("2.1---------------beforeMount");
    console.log(2.1, this.num, this.fn, document.getElementById("op"));
  },
  mounted() {
    console.log("2.2-------------mounted");
    console.log(2.2, this.num, this.fn, document.getElementById("op"));
  },
      
更新
  beforeUpdate(){  //视图跟新之前
    console.log("3.1-------------beforeUpdate");
    console.log(3.1, this.num, this.$refs.myp.innerHTML);
  },
  updated(){  //视图跟新之后
    console.log("3.2-------------updated");
    console.log(3.2, this.num, this.$refs.myp.innerHTML);
  },
      
销毁
  beforeDestory(){  
    
  },
  destoryed(){
     
  }

keep-alive包含的组件是不需要再重新创建(Create)

挂载

**beforeMount:**此时已经完成了模板编译,但是还没有挂载到页面中;

**mounted:**这个时候已经把编译好的模板挂载到页面指定的容器里;

beforeMount: data 的数据可以访问和修改,而且此时的模板已经编译好了,还没有更新到页面中

mounted: 此时编译的模板更新到页面中了

更新

**beforeUpdate:**状态更新之前执行此函数,此时的 data 中的数据是最新,但是界面上显示的还是旧的,因为此时还没有开始重新渲染DOM节点;

**updated:**实例更新完毕之后调用此函数,此时data 中的状态值和界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了;

beforeUpdate: 此时修改输入框的内容,data中的数据已经更新了,但是页面上显示的还是旧数据;

**updated:**此时 data 的内容已经更新,页面显示的也是最新的数据。

销毁

**beforeDestroy:**实例销毁之前调用,在这一步,实例让然完全可用。

**destroyed:**实例销毁后调用,调用后,Vue实例指向的所以东西会被解绑,所有的事件监听器会被移除,所有的子实力也会被销毁。

销毁声明周期(使用keep-alive组件是不会触发销毁生命周期)

活跃和不活跃

要搭配keep-alive组件来使用

 // 从不活跃进入活跃状态
  // activated() {
  //   console.log('activated');
  // },
  
  // // 从活跃进入不活跃
  // deactivated() {
  //   console.log('deactivated')
  // },

Vue-router(路由)

路由的概念

简单来说路由 就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不 同的资源,请求不同的页面是路由的其中一种功能。

1.后端路由

概念:根据不同的用户的不同URL请求,发送到服务器以后返回不同的内容。
本质:是URL请求地址与服务器资源之间的对应关系。

  1. 后端渲染的好处,相对于发送ajax请求拿数据,可以提高首屏渲染的性能,也有利于SEO的优化;
  2. 后端路由的缺点:
    1. 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码
    2. 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情

2.前端路由

概念:根据不同的用户事件,显示不同的页面内容
本质:用户事件与事件处理函数之间的对应关系

前后端分离阶段:

  • 随着Ajax的出现,有了前后端分离的开发模式;
  • 后端只提供API来返回数据(json,xml),前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中
  • 这样做最大的优点就是前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上
  • 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可
  • 目前很多的网站依然采用这种模式开发

单页面应用阶段:

  • 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由
  • 也就是前端来维护一套路由规则

前端路由的核心是什么呢?

  • 改变URL,但是页面不进行整体的刷新

前端路由规则

URL的hash和HTML5的history模式

1、hash模式

在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取; 特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。 hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com (opens new window),因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

2、history模式

history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

//写在导出模块里面 export default

export default new VueRouter({
  routes,
  mode: 'hash'
 //还可以是 'history'
  //两者区别: hash 后面跟着# 之后才是path值
  //http://192.168.6.170:8080/#/me   http://192.168.6.170:8080/#/cart
    
  //history   /后面才是path值
  //http://192.168.6.170:8080/cate   http://192.168.6.170:8080/home
})

Vue-router基本使用

目前前端流行的三大框架,都有自己的路由实现:

  • Angular的ngRouter
  • React的ReactRouter
  • Vue的vue-router

vue-router是Vue的官方路由插件,它和Vue是深度集成的,适合用于构建单页面应用 https://router.vuejs.org/zh/

vue-router是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来;在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.

安装:

npm install vue-router

一般项目中建议在cli创建项目时就直接选择需要路由,并搭配history模式。

选择router 模式

在index.js文件中,明确安装模块功能(并实例化)

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

对index.js文件进行路由设置

//导入模块
import VueRouter from 'vue-router'
import Vue from 'vue'
// 导入路由对象
Vue.use(VueRouter)
// 定义路由规则
const routes = [
  {
    path: '/',
    component: () => import('../views/index.vue') ,
    children:[
      //定义方法一:
      {
        path: '/', // 浏览器访问
      //name的作用用于之后传参获取定义的名(此名是唯一)
        name: 'Home',
        component: Home
      children:[
              {
                path: 'cate',
                component: () => import('../views/cate.vue')   // 路由懒加载
              }
  		  ]
      },
    //定义方法二:
      {
        path: 'cate',
        component: () => import('../views/cate.vue')   // 路由懒加载
      },
    
      {
        path: '/',
        redirect: '/cart'		// 这就是路由的重定向,重新定义跳转路径
      },
      {
        path: 'cart',     // 改成这个之后,原来的/就没有对用的组件了
        component: () => import('../views/cart.vue') 
      },
          
      {
        path: 'me',
        component: () => import('../views/Mehome.vue') 
      },
    
      {
    path: '*',		// 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
    component: () => import('@/views/Page404.vue')
      }
    
    ]
  },

]
// 创建路由实例 并导出
export default new VueRouter({
  // 路由模式
  mode: 'hash', // history hash
  // 路由规则(路由映射关系)
  routes, 
})

懒加载的方式

// 方式一: 结合Vue的异步组件和Webpack的代码分析
const User = resolve => { require.ensure(['@/views/User.vue'], () => { resolve(require('@/views/User.vue')) }) };

// 方式二: AMD写法
const User = resolve => require(['@/views/User.vue'], resolve);

// 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import(/* webpackChunkName: "user" */ '../views/User.vue')

路由跳转方式

使用 router-link 标签来实现跳转

<div id="nav">
  <router-link to="/">Home</router-link> |   //等同于a标签的效果
  <router-link to="/user">User</router-link>
</div>

///跳转之后,内容将替换<router-view/>
//然后通过 `router-view` 来显示页面。`router-link` 最终会被渲染为a标签。
<router-view/>

router-link 默认会被解析为a标签,如果想让它转换成其他的标签,就需要添加tag属性:

<router-link tag="li" to="/user">User</router-link>

此时,router-link 就被解析为li标签。

点击跳转到指定的页面

设置点击事件 方法

    jumpToAbout() {

	push 在末尾添加	
      this.$router.push({
push里面写的是对象

        // path: '/home'
        name: 'Home'
	name是在定义路由规则里面设置的,等同于 path的作用

      })


简写:
// 简写
this.$router.push('/user')

除了push,还有

go、 整数是前进,负数是后退

forward、 前进

back、 后退

replace、替换----replace(新的访问 例如 “/name”)

这几个来触发不同情况的跳转。

router.push(location, onComplete?, onAbort?)
router.push(location).then(onComplete).catch(onAbort)
router.replace(location, onComplete?, onAbort?)
router.replace(location).then(onComplete).catch(onAbort)
router.go(n)
router.back()
router.forward()

携带参数

    jumpFn(){
      this.$router.push({
        // path:""
        name:'About',
        携带的参数的方法名
        query:{
          id:123456,
          name:"张三"
        }
      })
    },
    
    在网页上显示:http://192.168.6.170:8080/cate?id=123456&name=张三
    
在子组件中可以拿到 - 通过生命周期初始函数得到
然后可以放入data 之后可以直接调用

    export default {
  data(){
    return {
      id:null,
      name:null
    }
  },
  生命周期初始函数
      created(){

        console.log(this.$route.query);
        this.id = this.$route.query.id
        this.name = this.$route.query.name
      }
    }

使用params传参

使用params传参,得到的结果与使用query传参得到的结果有以下区别:

this.$router.push({name: "User", params: {userId: 123}})    // http://localhost:8081/user/123
this.$router.push({name: "User", query: {userId: 123}})       // http://localhost:8081/?userId=123
在路由的js文件中:
   '/editbrand': {
        path: '/editbrand/:id', //品牌管理编辑
        name: 'editbrand',
        component: () =>
            import(/* webpackChunkName: "editbrand" */ '../views/editbrand/Editbrand.vue'),
    },

     
     
      
组件中:
      this.$router.push({
        name:'editbrand',
       params:{id:row.id}
      })
* 重调强调:

编程式导航中,使用name进行路径跳转,携带参数可以通过params和query,其中query会将参数携带在导航路径上,而使用path进行路径跳转,无法携带params,只能携带query。

补充:

params参数传参写法相当于在路径直接添加:

//App.vue中:
this.$router.push('/user/12');

// router/index.js中:
path: '/user/:userId',
    
// User.vue中:
created(){
    console.log(this.$route.params.userId);    // 获取到用户id12
}

路由的重定向

const routes = [
  {
    path: '/',
    redirect: '/home'		// 这就是路由的重定向,重新定义跳转路径
  },
  {
    path: '/home',    // 改成这个之后,原来的/就没有对用的组件了
    component: () => import('@/views/Home.vue')
  },
  
  ...  ...
  {
    path: '*',		// 匹配所有剩余的路由,只要不是上面提及的页面,全部跳转到404页面
    component: () => import('@/views/Page404.vue')
  }
]

vue-router 有几种导航钩子?

1、全局守卫: router.beforeEach

2、全局解析守卫: router.beforeResolve

3、全局后置钩子: router.afterEach

4、路由独享的守卫: beforeEnter

5、组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
全局守卫

router.beforeEach

全局前置守卫

导航或者路由被触发时,全局前置守卫被调用,解析执行完成之后,导航再跳转

守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中

注册:全局前置守卫
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})


接收三个参数
to: (去)
Route: 即将要进入的目标 路由对象

from: (来)
Route: 当前导航正要离开的路由


next
: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
一般会使用  next(): 进行管道中的下一个钩子。


	next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

    next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重  	置到 from 路由对应的地址。

    next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 	  next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的   	 to prop 或 router.push 中的选项。

    next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给
    router.onError() 注册过的回调。
全局解析守卫

router.beforeResolve

router.beforeResolve注册方法和全局前置守卫一样

这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子

router.afterEach

这些钩子不会接受 next 函数也不会改变导航本身

注册:
router.afterEach((to, from) => {
  // ...
})
路由独享的守卫

beforeEnter

可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
守卫与全局前置守卫的方法参数是一样的。
组件内的守卫

beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave

可以在路由组件内直接定义以下路由导航守卫:

beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave

const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
      
    //可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
    //beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
      
  },
    例如:
    beforeRouteEnter (to, from, next) {
      next(vm => {
        // 通过 `vm` 访问组件实例
      })
    }
    
    
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

//这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

$refs 和 $el的用法

$refs
$refs 
一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。
dom还没有渲染完成,是不能通过ref调用dom的。

加在普通元素上,获取的是dom元素

<div ref="system">测试</div>
// 获取
mounted() {
  console.log(this.$refs.system);
}


加在子组件上,获取的是组件实例,可以使用组件的所有方法
// this.$ref.xxx.方法名()
// 父组件
<contact-info ref="contactInfo"/>
import ContactInfo from './ContactInfo'
components: { ContactInfo },
mounted() {
   this.$refs.contactInfo.initVal(data) // 调用子组件方法
}
// 子组件
methods: {
  initVal(data){
    Object.keys(this.contactInfo).forEach(val=>{
      this.contactInfo[val] = data[val]
    })
  }
}


具体使用:
组件内(元素内)设置
<div ref="自定义名称">
</div>

this.$refs 拿到
如果是组件实例,可以使用组件的所有方法
this.$refs.方法名

$el
$el
Vue 实例使用的根 DOM 元素。
$el读取的是组件实例挂载的dom元素

// 子组件
<template>
  <div>
    测试
  </div>
</template>
<script>
export default {
  name: 'TestComs'
};
</script>


// 父组件
   <test ref="testCom" />
   <div ref="test">11</div>
  mounted() {
    console.log(this.$refs.testCom, '组件ref'); // 获取组件实例
      
    console.log(this.$refs.testCom.$el, '组件el'); // 获取组件实例的dom元素
      //获得  <div> 测试 </div>
    console.log(this.$refs.test, '元素ref'); // 获取dom元素
      //获得<div ref="test">11</div>
    console.log(this.$refs.test.$el, '元素el'); // $el用于vue组件,普通dom元素不能用
     //获得undefined
  },


$set

Vue中data中变量的数据值发生改变,界面没有跟着更新,是什么原因(Vue数据双向绑定失效)

1.如果data里面定义了对象,对象里面的键值没有,getter/setter函数没法监听到属性值的数据变化,会导致此现象的发生。

解决方法:

Vue.set(obj, key, value);
// or
this.$set(obj, key, value);

使用方法:
接收三个参数
this.$set(原数组或者原对象,索引值或者键值对名,需要赋的值)

Vuex----store

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

说得直白点,vuex就是vue.js中管理数据状态的一个库,通过创建一个集中的数据存储,供程序中所有组件访问。

一个数据只要放在了vuex中,当前项目所有的组件都可以直接访问这个数据。

安装;

npm install vuex --save

在store文件夹中的index.js中写入

//引入模块
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//导出模块
export default new Vuex.Store({
  state: {
    num: 0,
    uerinfo:{}
  },
  mutations: {
    increment (state,user) {
      state.uerinfo = user
    }
  }
})

在miain.js文件中引入store

import store from './store'


new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

vuex有以下常用的几个核心属性概念:

State

  • State

(个人理解)存放数据的容器

vuex中的state类似于data,用于存放数据,只不过这个数据是所有组件公用的。

  state: {
  数据,对象key : value值的方式
    num: 0,
    uerinfo:{}
  },
组件中

<template>
 <div>
    <h3>{{$store.state.num}}</h3>
 </div>
</template>


也可以使用computed
computed: {
  num(){
  	return this.$store.state.num
  }
}

// 标签中
<h3>{{num}}</h3>

Getters

  • Getters

vuex中的getters类似于computed计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

封装异步操作

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  getters: {
    // 这里的参数state可以让我们快速获取到仓库中的数据
    doubleNum(state) { 
      return state.num * 2;
    }
  }
})

  
  
  组件中
<template>
 <div>
    <h3>{{num}}</h3>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.getters.doubleNum
    }
  }
};
</script>
  

Mutations

  • Mutations

个人理解:存放方法,然后此方法被别的组件调用

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  mutations: {
    // payload专业名称为“载荷”,其实就是个参数
    addNum(state, payload) {
      state.num += payload;
    }
  }
})
  
  
  
  调用的组件
  <template>
 <div>
    <h3>{{num}}</h3>
    <button @click="btnClick">累加2</button>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.state.num
    }
  },
  methods: {
    btnClick(){
      // 使用commit来触发事件,第二个参数是要传递给payload的数据
      this.$store.commit('addNum', 2)
    }
  }
};
</script>
  
如:
   this.$store.commit('increment',uerRes.data)
   this.$store.commit('函数方法名',传入的参数)
   
   并且它会接受 state 作为第一个参数

Actions

  • Actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    num: 2
  },
  mutations: {
    addNum(state, payload) {
      state.num += payload;
    }
  },
  actions: {
    // context是一个对象,包含了commit和state
    AsyncAddNum(context,payload) { 
      setTimeout(() => {
        context.commit('addNum', payload)
      }, 1000)
    }
  }
})
  
  
  组件中
<template>
 <div>
    <h3>{{num}}</h3>
    <button @click="btnClick">累加2</button>
 </div>
</template>
 
<script>
export default {
  computed: {
    num(){
      return this.$store.state.num
    }
  },
  methods: {
    btnClick(){
      // dispatch是分发到意思,其实也是触发Actions中的方法
      this.$store.dispatch('AsyncAddNum', 2)
    }
  }
};
</script>
  
  

当然,上面actions中的写法有点累赘,我们还可以改写:

AsyncAddNum({ commit },payload) { 
  setTimeout(() => {
    commit('addNum', payload)
  }, 1000)
}

// 如果你还想获取state中的值,可以这样:
AsyncAddNum({ commit,state },payload) { 
  console.log(state.num);		// 2
  setTimeout(() => {
    commit('addNum', payload)
  }, 1000)
}

Modules

  • Modules

把累加单独抽出来作为一个模块,在store下新建一个 add/index.js 文件:

export default {
    namespaced: true,	//	命名空间,为true时,可以在store中把当前模块文件夹名称(add),当作模块名使用
    state: {
        num: 2
    },
    getters: {
        doubleNum(state) {
            return state.num * 1;
        }
    },
    mutations: {
        addNum(state, payload) {
            state.num += payload;
        }
    },
    actions: {
        AsyncAddNum({ commit }, payload) {
            setTimeout(() => {
                commit('addNum', payload)
            }, 300)
        }
    }
}

把有关累加的所有内容,都移动至本文件。再到原来仓库index.js中的modules添加:

引入存放模块的文件
import add from './add'

export default new Vuex.Store({
  ...,
	modules: {
    add
  }
})
命名空间

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

const moduleA ={
    namespaced:true,  //开启namespace:true,该模块就成为命名空间模块了
    state:{
        count:10,
        countA:888
    },
    getters:{...},
    mutations:{...},
    actions:{...}
}
               
               
               
               
可以在单个模块中通过添加namespaced:true的方式使其成为带命名空间的模块。
               
组件中如何获取带有命名空间moduleA中的state数据?
1、基本方式:
    this.$store.state.moduleA.countA
2、mapState辅助函数方式:
    ...mapState({
        count:state=>state.moduleB.countB
    }) 
      
             
             
组件中调用命名空间模块中的getters
共有三种方式,如下:
//1.
commonGetter(){
    this.$store.getters['moduleA/moduleAGetter']
},
//2.
...mapGetters('moduleA',['moduleAGetter']),此处的moduleA,不是以前缀的形式出现!!!
//3.别名状态下
...mapGetters({
    paramGetter:'moduleA/moduleAGetter'
}),
    
    
组件中调用命名空间模块中的Mutations
共有三种方式,如下:
//1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
//1.
commonMutation(){
    this.$store.commit('moduleA/moduleAMutation');
},
//2.
...mapMutations('moduleA',['moduleAMutation']),
//3.别名状态下
...mapMutations({
    changeNameMutation:'moduleA/moduleAMutation'
}),
    
    
    
组件中调用命名空间模块中的Actions(与mutations一致)
共有三种方式,如下:
1,3加个前缀moduleA/,都可以实现。2使用辅助函数未变名称的特殊点!!!
//1.
commonAction(){
    this.$store.dispatch('moduleA/moduleAAction')
},
//2.
...mapActions('moduleA',['moduleAAction']),
//3.别名状态下
...mapActions({
    changeNameAction:'moduleA/moduleAAction'
})


在带命名空间的模块中,如何将action注册为全局actions
     两个条件:
           ①添加 root: true
           ②并将这个 action 的定义放在函数 handler 中
           
//storeAction在命名空间moduleA中,但是它是一个全局actions
const moduleA = {
    namespaced:true,
    storeAction:{
        root:true,  //条件1
        handler(namespacedContext, payload){//条件2:handler
            //namespacedContext 上下文信息
            //payload 载荷,即参数
            console.log(namespacedContext)
            console.log(payload)
            alert("我是模块A中的全局storeAction")
        }
    }
}



拆分写法

实际上我们可以把state、getter、mutation、action和module都抽离出来,这样可以让store文件看着更加简洁。我们来将 store/index.js 进行拆分:

state.js

export default {
    num1: 0,
    title: '标题'
}

mutations.js

export default {
    cutNum(state, payload) {
        state.num1 -= payload;
    }
}

actions.js

export default {
    AsyncCutNum({ commit }, payload) {
        setTimeout(() => {
            commit('cutNum', payload)
        }, 300)
    }
}

modules.js

import add from './add'
export default {
    add
}

最后,在 store/index.js 中:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import modules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  actions,
  modules
})

辅助函数

mapState

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:

mapState 函数返回的是一个对象

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
  
    // 箭头函数可使代码更简练(方法一)
    count: state => state.count,

    // (方法二)传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // (方法三)为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

mapState 函数返回的是一个对象

我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符 (opens new window),我们可以极大地简化写法:

computed: {
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}
mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}
mapMutations
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}
mapActions
import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

内置组件

keep-alive

缓存加载

作用:主要用于保留组件状态或避免重新渲染。

includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

    用在包裹着占位符,包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
    <keep-alive exclude="Home">
    		<router-view></router-view>
    </keep-alive>
    
    include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
	exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
	max - 数字。最多可以缓存多少组件实例。
	
	给组件起名,以此来确定是需要操作那个组件
	export default {
	  name:"Home",
	  }
	  


逗号分隔字符串
	<keep-alive include="a,b">
    
正则表达式 (使用 `v-bind`)
    <keep-alive :include="/a|b/">
        
        
数组 (使用 `v-bind`)
   <keep-alive :include="['a', 'b']">

最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

axios 访问 API

axios

安装

npm install axios

yarn add axios

引入模块

import axios from "axios";
import qs from "qs"


读取 JSON 数据
methods;{
	Fn(){
		 axios.post('http://192.168.113.249:8081/cms/phoneRegin',{
                params: {
                  ID: 12345
                }
		}
	}.then(res=>{})
	.catch(function (error) {
            console.log(error);
          });

}


方法二
// 直接在 URL 上添加参数 ID=12345
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });




请求头
                axios.get('http://192.168.113.249:8081/cms/phoneRegin',{
                  headers:{
                    'x-auth-token':'  '
                  }



post请求
    axios
      .post('https://www.runoob.com/try/ajax/demo_axios_post.php')
      .then(response => (this.info = response))
      .catch(function (error) { // 请求失败处理
        console.log(error);
      });
  }

post请求 传参说明
axios.post('/user', {
    firstName: 'Fred',        // 参数 firstName
    lastName: 'Flintstone'    // 参数 lastName
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

qs

qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。

用法之一:解析 URI 编码

qs.stringify({phone:this.username,password:this.pwd})

项目

初始化样式

<style lang="less" scoped>
    //
    在这里可以直接写样式或者引入less文件*
    scoped 样式只作用于当前组件
    //
    
.header{
  height: 50px;
  background-color: #333;
}
</style>

安装初始化样式库reset-css:

npm i reset-css 或者   yarn add reset-css

安装成功后在main.js中引入即可:

import "reset-css"

网站数据请求模块

发送请求:

安装axios模块:
npm i axios

尝试在app.vue中做数据请求:

import axios from "axios"
export default {
  	...
   created(){
    //get请求
    axios.get("http://192.168.113.249:8081/cms/products/recommend")
    .then(res=>{
      	console.log(res.data);
    })
    .catch(err=>{
      	console.log(err);
    })
},
}

代理配置:

vue.config.js 进行配置:

module.exports = {
    devServer: {
        port: 8080,
        proxy: {
            '/api': {
                target: "http://192.168.113.249:8081/cms",
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
}

由于配置文件修改了,这里一定要记得重新 yarn serve (跑一下)!!

API与Request封装

src 下新建 request目录 ,在request目录下新建 request.js

request.js 中:

import axios from "axios"

const instance = axios.create({
    baseURL:"http://192.168.113.249:8081/cms",
    timeout:5000
})

instance.interceptors.request.use(config=>{
    console.log("每一次发起请求前,都会先执行这里的代码");
    console.log(config); //config本次请求的配置信息
    return config
},err=>{
    return Promise.reject(err)
})
=
instance.interceptors.response.use(res=>{
    console.log("每一次接收到响应,都会先执行这里的代码,再去执行成功的那个回调函数then");
    return res
},err=>{
    return Promise.reject(err)
})

export default instance

为了更好地管理我们的这些接口,我们把所有请求都抽取出来在一个api.js中

request目录下新建api.js,api.js 中:

import request from './request'

// 请求精品推荐数据
export const JingpinAPI = () => request.get('/products/recommend')

如:

request.js文件中

import axios from 'axios';

const instance = axios.create({
    timeout:15000,
    baseURL:'',
})

instance.interceptors.request.use((config)=>{
    const token = localStorage.getItem('token')
    //设置请求头
    if(token){
        config.headers['X-Nideshop-Token'] = token
    }
    return config
},(err)=>{
    return Promise.reject(err)
})

instance.interceptors.response.use((reset)=>{
    return reset.data;
},(err)=>{
    return Promise.reject(err)
})

export default instance;

api.js文件中

import request from './request';
import qs from 'qs';

//post请求
export const getUserByToken = (data)=>request.post('http://kumanxuan1.f3322.net:8360/admin/auth/getUserByToken',qs.stringify(data))

//get请求
export const getGoods = (data)=>request.get('http://kumanxuan1.f3322.net:8360/admin/goods',{params:data}) 

导航栏点击之后的样式显示:

<li :class="$route.path==='/home'?'active':''">首页</li>

三元表达式:
true 执行前面的,false 执行后面的

点击之后设置路由跳转

<li @click="$router.push('/home')" :class="$route.path==='/home'?'active':''">首页</li>

拼图验证滑块

插件参考:https://gitee.com/monoplasty/vue-monoplasty-slide-verify

安装插件

npm install --save vue-monoplasty-slide-verify
或者
yarn add vue-monoplasty-slide-verify

main.js入口文件引中入

import SlideVerify from 'vue-monoplasty-slide-verify' // 拼图验证码

Vue.use(SlideVerify)

在组件中使用

<template>
	<slide-verify :l="42" :r="20" :w="362" :h="140" @success="onSuccess" @fail="onFail" @refresh="onRefresh" :style="{ width: '100%' }" class="slide-box" ref="slideBlock" :slider-text="msg"></slide-verify>
</template>

<script>
export default {
  data() {
    return {
      msg: "向右滑动"
    };
  },
  methods: {
    // 拼图成功
    onSuccess(times) {
      let ms = (times / 1000).toFixed(1);
      this.msg = "login success, 耗时 " + ms + "s";
    },
    // 拼图失败
    onFail() {
      this.onRefresh(); // 重新刷新拼图
    },
    // 拼图刷新
    onRefresh() {
      this.msg = "再试一次";
    },
  },
};
</script>

<style lang="less" scoped>
/deep/.slide-box {
    width: 100%;
    position: relative;
    box-sizing: border-box;
    canvas {
        position: absolute;
        left: 0;
        top: -120px;
        display: none;
        width: 100%;
        box-sizing: border-box;
    }
    .slide-verify-block{
        width: 85px;
        height: 136px;
    }
    .slide-verify-refresh-icon {
        top: -120px;
        display: none;
    }
    &:hover {
        canvas {
            display: block;
        }
        .slide-verify-refresh-icon {
            display: block;
        }
    }
}
</style>

点击获取验证码按钮的逻辑

可以正常获取验证码的前提是:手机号格式正确

所以,点击获取验证码的逻辑如下:

1、如果校验手机号格式不正确,则return

2、滑块拼图验证不通过,则return

3、验证成功后,发起请求,获取验证码成功,则进行倒计时

【百度】结合运营商之后的手机号码的正则:

/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/
<div class="btn checkcode-btn" @click="getCode">获取验证码</div>
...
<script>
    getCode(){
        // 1、验证手机号是否正确
        if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
            alert("请输入正确的手机号");
            this.$refs.phone.focus();
            return
        } 
        alert("手机号格式正确");
        
        // 2、进行滑块验证
           
        // 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
		
  
      
    },

</script> 

倒计时及其展示

<div class="btn checkcode-btn" @click="getCode">
    <span v-show="!isShowCount">获取验证码</span>
    <span v-show="isShowCount">{{count}} s</span>
</div>
<script>
	methods:{
        countdown(){
            // 计时的方法
            // 倒计时,实际上就是每隔1秒,count减去1
            
            // 每次点击先让count为60
            this.count=60;
            let timer = null;
            timer = setInterval(()=>{
                this.count--
                if(this.count===0){
                    // 清除定时器 
                    clearInterval(timer)
                }
            },1000);
        },
        getCode(){
            // 1、验证手机号是否正确
            /* if(!/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(this.phoneNum)){
                alert("请输入正确的手机号");
                this.$refs.phone.focus();
                return
            } */
            // 2、进行滑块验证
            if (this.msg == "再试一次" || this.msg == "向右滑动") {
                alert("请先进行滑块验证");
                return 
            }
            // 3、验证成功后,发起请求,获取验证码成功,则进行倒计时,并展示秒数
            // 这里先展示秒数
            this.countdown();
            this.isShowCount=true;
           
            
        },
	}
</script>

连续点击倒计时bug

此时连续点击倒计时会有bug,数字越跳越快,主要是重复开启倒计时造成的。

其实我们只需要把事件给到 “获取验证码” 所在的span,就可以解决

<div class="btn checkcode-btn">
    <span v-show="!isShowCount" @click="getCode">获取验证码</span>
	<span v-show="isShowCount">{{count}} s</span>
</div>
<script>
data () {
    return {
        // 是否展示表单的布尔值
        isShowForm:true,
        // 拼图滑块的文字
        msg: "向右滑动",
        // 用户手机号
        phoneNum:"",
        // 最大的计时时间
        countMax:60,
        // 倒计时时间,每秒变化的那个数字
        count:0,
        // 是否展示秒数所在盒子
        isShowCount:false
    }
},
...
...
countdown(){
    // 计时的方法
    // 倒计时,实际上就是每隔1秒,count减去1
    let timer = null;
    this.count = this.countMax;
    timer = setInterval(()=>{
        this.count--
        if(this.count===0){
            // 清除定时器 
            clearInterval(timer);
        }
    },1000);
}
</script>

请求头和请求拦截器

接口上有需要我们修改请求头Content-Type字段,并使用qs.stringnify进行格式转换:

需要在请求拦截器加上:

instance.interceptors.request.use(config=>{
    
    if (config.url === "/sendSMS" || config.url === "/wechatUsers/PCLogin") {
        config.headers["Content-Type"] = "application/x-www-form-urlencoded";
    }
    return config
},err=>{
    return Promise.reject(err)
})

安装qs模块:

npm i qs

api.js中:

import qs from "qs"
// 发送短信验证码请求
export const SendSMSAPI = params => request.post("/sendSMS",qs.stringify(params));

作业

day01

聊天对话框

解题思路:利用v-for遍历数组,利用v-model获取变化的值

//先引入Vue的js文件
<script src="./vue.js"></script>


创建Vue对象
new Vue({
    el:"选择器",
    data:{//数据
    	arr:[],
        val:""
        //val:"A"(意思是默认值是A)
	},
    methods: {
        //方法
    }
    
})


//获取变化的值:用v-model 一般是用在父级元素上,获取子级元素的值的变化
//还可以用在输入框,获取输入框的值
 v-model = "val"
 v-model = "text"


//遍历数组---用  v-for"(item, index) in 需要遍历的数组数组"
//谁需要遍历,就谁用v-for
<div v-for='item in arr'><span>{{item.content}}</span></div>
    
//判断name是A还是B,分别对应给class值,控制类名
//用三元表达式
 :class="item.name==='A'?'atalk':'btalk'"

//三元表达式 表达式?"等于(true)的值":"不等于(false)的值"


//点击事件,点击给数组添加值,然后再遍历
@click = '事件名'

事件中调用data的值,需要用this

methods:{
   talkTxt(){
       this.arr.push(`{name:${this.val},content:${this.text}}`)
       this.text = ""//点击之后自动清空文本框
           }
        }
 

选项卡-tab栏

思路:事件点击方法,传参数,带参数,然后将参数存起来;利用三元表达式判断是否等于参数值,给类名值

 @click='add(1)'
 
 三元表达式:
 :class='val===1?"current":""'

todolist-微博新增发言功能

思路:用数组将内容装起来,然后遍历获取数组的item内容和index索引值,点击删除,删除对应索引值的数组内容。

用v-for ’(item,index)in arr ‘


学生管理系统

思路:用来 v-for = “(item,index)in arr” ;v-show 显示隐藏(true;false);将索引和item值存储,然后传给下个方法;

JavaScript如何判断一个值是不是数字?

第一种方法:isNaN()

数字返回false 字符串返回true

缺点:值有一个空串或是一个空格,isNaN将把c当作数字0来处理,所以检查不严谨。

第二种方法:正则表达式

/1+.?[0-9]* / / / 判 断 字 符 串 是 否 为 数 字 , 判 断 正 整 数 用 / [ 1 − 9 ] + [ 0 − 9 ] ∗ ] ∗ / //判断字符串是否为数字 ,判断正整数用 /^[1-9]+[0-9]*]* ////[19]+[09]]/

reg.test( num )

第三种方法: 利用typeof的返回值


  1. 0-9 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值