Vue基础

422 阅读5分钟

一、 Vue基础

一、 模板语法

1.1数据绑定

1.1.1文本,v-text、{{}}

在mustache语法中,是可以使用加减乘除运算符的

	<div id="app">
		<div>
			{{uage *2}}
		</div>
		<div v-text="uage"></div>
	</div>
<script>
	const app = new Vue({
		el:'#app',
		data:{
			uage:10
		}
	})
</script>

1.1.2 v-once:

只将数值绑定一次

	<div id="app">
		<div v-once>
			{{uage}}
		</div>
	</div>

1.1.3v-pre:

将标签内的内容不编译进行渲染

	<div id="app">
		<div v-pre>
			{{uage *2}}
		</div>
	</div>

1.1.4v-cloak:

使数据加载之后才渲染页面

	<div id="app">
		<div v-cloak>
			{{uage *2}}
		</div>
	</div>

原理:在vue数据加载之前,标签中有v-cloak这个属性,阻止元素的显示,数据加载之后,驱动页面渲染,元素上就没有v-cloak这个属性了

1.1.5 v-html

插入带html的字符

	<div id="app">
		<div v-html ='url'>
		</div>
	</div>
<script>
	const app = new Vue({
		el:'#app',
		data:{
			uage:10,
			url:'<a href="http://www.baidu.com">ddd</a>'
		}
	})
</script>

1.1.6v-bind:

动态绑定属性值

语法糖::

	<div id="app">
		<a v-bind:href= "url">
			我是百度
		</a>
	</div>
<script>
	const app = new Vue({
		el: '#app',
		data: {
			uage: 10,
			url: 'http://www.baidu.com'
		}
	})
</script>

==扩展使用:绑定class==

文件css

		.active {
			color: red;
		}

		.uncheck {
			color: yellow;
		}
		.green{
			color: green;
		}

文件js

<script>
	const app = new Vue({
		el: '#app',
		data: {
			isActive: true,
			isUncheck: false

		},
		methods: {
			btnClick: function () {
				this.isActive = !this.isActive,
					this.isUncheck = !this.isUncheck

			},
			getClass:function(){
				return  {active:this.isActive,uncheck:this.isUncheck}
			}
		}
	})
</script>

用法一:直接使用{}绑定一个class

		<div :class={active:true}>
			我是直接绑定的样式
		</div>

用法二:使用变量传入boolean类型控制多个class是否生效

		<div @click="btnClick" :class="{active:isActive,uncheck:isUncheck}" >
			我要变色了
		</div>

用法三:与其他class共同使用,样式冲突的情况下以到单独写的class为主

		<div @click="btnClick" :class="{active:isActive,uncheck:isUncheck}"class="green" >
			我要变色了
		</div>

用法四:如果过于复杂,可以放在一个methods或者computed中

		<div @click="btnClick" v-bind:class="getClass()" >
			我要变色了
		</div>
//调用方法的时候可以选择不加括号 

使用数组绑定class:

		<div :class= "['green','fontClass']" class="active" >
			我绑定了好几个class哦~
		</div>

这样驻足绑定的方法可以使用data中的变量,也不与单独写的class冲突 但是在渲染的时候,是优先渲染内联class,所以如果有样式重叠,绑定的样式会覆盖掉内联class,截图如下:

  • v-bind绑定style

    		<div :style="{fontSize:'30px'}">我绑定了style</div>
    		<div :style="{color:color,fontSize:fontSize+'px'}">我也绑定了style</div>
    
    		data: {
    			color: "red",
    			fontSize:30
    
    		},
    

    注意事项:

    1. 格式为:style= "{属性名:属性值}"
    2. 如果属性值是类似font-size带有-的属性值都要转换为fontSize,否则会报错
    3. 属性值如果直接写数值要加'',如果是变量则不用加
        computed: {
            fullName:function(){
                return this.firstName + ' ' + this.lastName;
            }
        },

1.1.7v-model

在表单上创建双向绑定,v-model会忽略所有表单元素的value,checked,selected属性 初始值,而选定Vue实例数据为具体值。

<body>
    <div id="app">
        <input type="text" v-model = "userName">
        <p>{{userName}}</p>
    </div>

</body>
<script>
    const app = new Vue({
        el:"#app",
        data:{
            userName:"hong"
        }
    })
</script>

v-model的相关使用:

<body>
    <div id="app">
        <!-- v-model与radio的配合使用 -->

        <label for="male">
            <input id="male" type="radio" name="sex" v-model="sex" value="男"></label>
        <label for="female">
            <input id="female" type="radio" name="sex" v-model="sex" value="女"></label>
        <p>选中的性别是:{{sex}}</p>
        <!-- v-model与checkBox的配合使用 -->
        <!-- <input type="checkbox" value="篮球" v-model="checked">篮球
        <input type="checkbox" value="足球" v-model="checked">足球
        <input type="checkbox" value="乒乓球" v-model="checked">乒乓球
        <input type="checkbox" value="羽毛球" v-model="checked">羽毛球
        <input type="checkbox" value="橄榄球" v-model="checked">橄榄球
        <input type="checkbox" value="高尔夫" v-model="checked">高尔夫 -->
        <label v-for= "item in hobbies" :for="item" >
            <!-- 注意label的for与input的id都需要动态绑定,input的value也是需要动态绑定的 -->
            <input type="checkbox" :value="item" :id="item" v-model= "checked" >{{item}}
        </label>
        <p>我选中了{{checked}}</p>
        <!-- v-model与select配合使用 -->
        <select  name="" id="" v-model= "checkBooks" multiple>
            <option v-for = "item in books" :value="item">{{item}}</option>
        </select>
        <p>我选中了:{{checkBooks}}</p>
    </div>

</body>
<script src="../js/vue.js"></script>
<script>
    Vue.component('cpn', cpnC)
    const app = new Vue({
        el: '#app',
        data: {
            // 给单选框一个默认值“男”
            sex: '男',//单选
            checked: [],//多选
            hobbies:["足球","乒乓球","羽毛球","橄榄球","高尔夫"],
            books:["钢铁是怎样炼成的","啊","Linux该怎么学","前端好难","努力学习"],
            checkBooks:[]
        },
    })
</script>

1.1.7.1model修饰符

  1. .lazy

    默认情况下v-model是实时同步输入框的值和数据,加上.lazy后变为触发change时间后再同步(即相当于修改后要按下回车,或点击其他位置后才触发数据同步)

    <input type="text" v-model.lazy = "userName">
    
  2. .number

    将用户输入的内容转化成数值类型,转化规则与js的Number()的规则相同

    详见w3shool的JS数据类型转换

    <input type="text" v-model.number = "userName">
    
  3. .trim

    过滤掉输入框的首位空格

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

1.2事件绑定

1.2.1 v-on

作用:绑定事件监听器

语法糖:@

参数:event

1.2.1.1基本使用

使用v-on绑定事件

  • 一个元素上可以绑定多个不同的事件
  • 事件的绑定也可以通过methods中定义函数的方法来实现绑定
  • 当定义的事件没有参数,所以我们在调用方法的时候不需要添加()
<body>
    <div id="app">
        <p>{{total}}</p>
        <!-- 一个元素上可以绑定多个不同的事件 -->
        <div class="div1" @mouseout = "total++" @click = "total--"></div>
        <!-- 事件的绑定也可以通过methods中定义函数的方法来实现绑定 -->
        <!-- 因为现在定义的事件没有参数,所以我们在调用方法的时候不需要添加() -->
        <button v-on:click = "reduce">-</button>
        <button v-on:click = "increase">+</button>
    </div>

</body>
<script>
    const app = new Vue({
        el:"#app",
        data:{
            total:1
        },
        methods:{
            // 定义total增加的函数
            increase(){
                this.total++;
            },
            // 定义total减少的函数
            reduce(){
                this.total--;
            }

        }
    })
</script>

1.2.1.2事件监听传参

  • 当方法需要参数但未传递实参,会将event对象当成默认参数返回
  • 正常传递参数
  • 当需要传递其他参数和event的时候,需要借助$event实现​
<body>
    <div id="app">
        <!-- 省略参数,会将event事件当成默认的参数返回 -->
        <button @click = "clickBtn">按钮</button>
        <!-- 传递所需参数 -->
        <button @click = "clickBtn1('sss')">按钮</button>
        <!-- 同时传递需要的参数和event,使用vue封装的$event进行传参 -->
        <button @click = "clickBtn2(123,$event)">按钮</button>
        
    </div>

</body>
<script>
    const app = new Vue({
        el:"#app",
        data:{

        },
        methods:{
            clickBtn(){
                console.log(event);
            },
            clickBtn1(a){
                console.log(a);
            },
            clickBtn2(a,event){
                console.log(a,event);
            }

        }
    })
</script>

1.2.2 v-on的修饰符

  • .stop调用了event。stopPropagation()
  • .prevent调用event.preventDefault()
  • .{keyCode|keyAlias}是事件只从特定按键触发时才触发回调
  • .native 监听组件根元素的原生事件
  • .once 只触发一次回调
        <!-- 阻止冒泡 -->
        <button @click.stop = "clickBtn">按钮</button>
        <!-- 阻止默认行为 -->
        <button @click.prevent = "clickBtn">按钮</button>
        <!-- 阻止默认行为,没有表达式 -->
        <form @submit.prevent></form>
        <!-- 串联修饰符 -->
        <button @click.stop,prevent = "clickBtn">按钮</button>
        <!-- 按键别名,键盘上的名称 -->
        <input @keydown.+ = "onEnter">
        <!-- 按键代码,按键对应的代码数字 -->
        <input @keydown.13 = "onEnter">

1.2.2 v-if、v-else、v-else-if

1..2.2.1 v-if、v-else

v-if与js的if用法相同,是通过v-if = ""引号中的Boolen值去进行判断,如果为false就隐藏元素是true就显示

<body>
    <div id="app">
        <div v-if="isShow">true</div>
        <div v-else>false</div>
        <button @click="changeClick">切换</button>

    </div>

</body>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            isShow: true
        },
        methods: {
            changeClick() {
                this.isShow = !this.isShow;
            }
        }
    })
</script>

1.2.2.2v-else-if

用处不多,不赘余

    <div id="app">
        <div v-if="score >90 ">优秀</div>
        <div v-else-if="score >80 ">良好</div>
        <div v-else-if="score > 60">及格</div>
        <div v-else>不及格</div>
    </div>

1.3 v-for循环遍历

用法与for…in相同,详见w3school的for…in介绍

1.3.1循环数组

1.3.1.1无索引循环

<body>
    <div id="app">
        <ul>
            <li v-for = "item in names" v-block>{{item}}</li>
        </ul>
    </div>

</body>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            names:["h","w","l","g","z"]
        },
        methods: {
        }
    })
</script>

1.3.1.2 有索引循环

  • 加一个小括号,里面加上item和index
        <ul>
            <li v-for = "(item,index) in names" v-block>{{index}}----{{item}}</li>
        </ul>

1.3.2对象循环遍历

1.3.2.1无key无索引遍历

<body>
    <div id="app">
        <ul>
            <li v-for = "item in names" v-block>{{item}}</li>
        </ul>
    </div>

</body>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            names:{
                name:"hong",
                age:18,
                sex:"男"
            }
        },
        methods: {
        }
    })
</script>

1.3.2.2有key无索引遍历

        <ul>
            <li v-for = "(item,key) in names" v-block>{{key}}------{{item}}</li>
        </ul>

1.3.2.2有key有索引遍历

 <ul>
  <li v-for = "(item,key,index) in names" v-block>{{key}}------{{item}}-----{{index}}</li>
</ul>

1.4(了解)如何正确使用key

  1. v-for使用时,为了能够更好的复用,我们在使用v-for的时候,需要搭配key属性的使用

    <body>
        <div id="app">
            <ul>
                <li v-for = "item in names" key = "item">{{item}}</li>
                <li v-for = "item in user" key = "item">{{item}}</li>
            </ul>
        </div>
    
    </body>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                names:{
                    name:"hong",
                    age:18,
                    sex:"男"
                },
                user:["s","d","f","j"]
            },
            methods: {
            }
        })
    </script>
    

二、计算属性

2.1基本使用

    <div id="app">
        <div>{{fullName}}</div>
    </div>
        data: {
            firstName: 'zhang',
            lastName: 'san'
        },
        computed: {
            fullName: function () {
                return this.firstName + ' ' + this.lastName;
            }
        },

三、ES6语法

  1. 对象的增强语法

    ES5的创建方法

    var a = new Object();
    a.sname = "hong";
    
    var a = {
        sname = "hong"
    }
    
  2. ES6语法

    const sname =  "hong";
    var a = {
        sname,
    }
    //在es6的语法中,上面写法会吧sname当成key,将变量sname的值赋值给它
    

四、组件

4.1 全局组件与局部组件

<body>
    <div id="app">
        <!-- 全局组件在哪里都可以调用 -->
        <global-c></global-c>
        <!-- 局部组件只有在注册的组件内才能调用 -->
        <cpn></cpn>
    </div>
    <div id="app2">
        <global-c></global-c>
        <!-- <cpn></cpn> -->
    </div>

</body>
<script src="../js/vue.js"></script>
<script>
    // 定义组件
    const cpnC = Vue.extend({
        template: `        
        <div>
            <span>hhh</span>
            <input type="text">
        </div>
        `
    })
    // 全局组件注册
    Vue.component('globalCi', cpnC)
    const app = new Vue({
        el: '#app',
        data: {

        },
        // 局部组件注册
        components: {
            cpn: cpnC
        }
    })
    const app2 = new Vue({
        el: "#app2",
    })
</script>

提示:组件命名的时候是允许驼峰命名的,但是在使用的时候,要将大写字母转小写,前面加一个中划线-

如Vue.component('globalComponent',***)在使用时就要

4.2父子组件

<body>
    <div id="app">
        <cpn></cpn>
    </div>
</body>
<script src="../js/vue.js"></script>
<script>
    // 定义组件1
    const cpnC = Vue.extend({
        template: `        
        <div>
            我是组件1
        </div>  
        `
    })
    const cpnC2 = Vue.extend({
        template: `
        <div>
            我是组件2
            <!-- 将注册好的组件在父组件中调用-->
            <sonCpn></sonCpn>
        </div>
        `,
        components:{
            // 将组件1注册到组件2,此时组件1为字子组件,组件2为父组件
            sonCpn:cpnC
        }
    })
    const app = new Vue({
        el: '#app',
        data: {

        },
        // 在root根组件中注册组件2,在使用组件
        components: {
            cpn: cpnC2
        }
    })
    const app2 = new Vue({
        el: "#app2",
    })
</script>

在调用组件的时候,Vue对象会先在自己的组件中去找,是否注册了这个组件,如果没有,去全局组件中查找,如果都没有,将报错

4.2.1父子组件语法糖

旧写法

    // 定义组件
    const cpnC = Vue.extend({
        template: `        
        <div>
            我是组件1
        </div>
        `
    })
    // 全局组件注册
    Vue.component('globalCi', cpnC)

新写法

    Vue.component("ss",{
        template: `        
        <div>
            我是组件1
        </div>  
        `
    })
//------------------------------------------
        components: {
            cpn: {
      template: `        
        <div>
            我是组件1
        </div>  
        `
            }
        }
//-----------------------------------------------
const cpn = {
     template: `        
        <div>
            我是组件1
        </div>  
        `
}
//在vue组件中定义
    const app = new Vue({
        el: '#app',
        components: {
            //ES6语法,key与value相同时,直接写一个就可以了
            cpn
        }
    })

4.2.2将template抽离出来

4.2.2.1使用script标签

使用<script type="text/x-template"></script>标签将html代码包裹,并赋以ID值,实现html代码的剥离

<body>
    <div id="app">
        <cpn></cpn>
        <cpn1></cpn1>
        <!-- <cpn2></cpn2> -->

    </div>
    <script type="text/x-template" id="test">
        <div>
            我是组件,但代码抽离了哦~
        </div>
    </script>


</body>
<script src="../js/vue.js"></script>

<script>
    // 下面是注册全局组件的方式
    Vue.component('cpn', {
        template: "#test"
    })

    const app = new Vue({
        el: '#app',
        // 下面是注册局部组件的方式
        components: {
            cpn1: {
                template: "#test"
            }

        }
    })
</script>

4.2.2.2使用template标签

使用<template></template>标签将html代码包裹,并赋以ID值,实现html代码的剥离

    <template id="test02">
        <div>
            hi~我是用template标签实现的
        </div>
    </template>

实现方法与上面相同只是标签使用方法不同

:low_brightness:注意,在组件中使用变量是可以的但是data要使用data(){}方法,用==return返回一个对象==的方式实现,如下

    // 下面是注册全局组件的方式
    Vue.component('cpn1',{
        template:"#test02",
        //data必须使用方法,然后return返回
        data(){
            return {
                title:"我是标题"
            }
        }
    })

因为我们使用方法再ruturn的情况下,每次调用都相当于在不同的地址生成对象,在复用的时候,会同步更改,所以设计者尤大大规定组件中必须使用data(){reutrn {} }的方式

理论同下,帮助理解

function a (){
    return {
        sname:"hong",
        sage:18
    }
}
let b = a();
let c = a();
let d = a();
//上面的b,c,d并不是相同的物理地址

4.2.3 父子组件传值

4.2.3.1父传子
  • 通过props属性进行传值

    <body>
        <div id="app">
            <cpn :cmovies="movies"></cpn>
        </div>
        <!-- 下面的是子组件的html -->
        <template id="ccpn">
            <div>
                {{cmovies}}
            </div>
        </template>
    </body>
    <script src="../js/vue.js"></script>
    <script>
        // 下面是子组件
        const cpn = {
            template: '#ccpn',
            props: ['cmovies'],
            data() {
                return{
                }
            }
        }
        // 下面是根组件也是父组件
        const App = new Vue({
            el: "#app",
            data() {
                return {
                    movies: ['海王', '海贼']
                }
            },
            components: {
                cpn,
            }
    
        })
    </script>
    

    通过上面的方法,我们借助props属性来将父组件的数据传递给子组件,在调用子组件的时候通过v-bind属性来绑定子组件的变量和父组件中的变量

    • compnents属性不止可以使用[]也可以使用{},在使用对象语法的时候,我们可以验证传过来的数据类型,基本语法如下
4.2.3.1.1 porps属性的使用
  • 动态props

    上面的代码就是动态的props

  • 静态props

    <body>
        <div id="app">
            <!-- props静态的赋值 -->
            <cpn cmovies="sss"></cpn>
        </div>
        <template id="ccpn">
            <div>
                {{cmovies}}
            </div>
        </template>
    </body>
    

    注意点:

    • props的变量前面不需要加,不加:的情况下,后面赋值就不会被认为是变量
  • props验证

     props: { 
                        // 基础类型检测, null意味着任何类型都行
                        propA: Number,
                         // 多种类型
                        propB: [String, Number],
                         // 必传且是String
                        propC: {
                            type: String,
                            required: true
                        },
                         // 数字有默认值
                        propD: {
                            type: Number,
                            default: 101
                        },
                         // 数组、默认值是一个工厂函数返回对象
                        propE: {
                            type: Object,
                            default: function () {
                                console.log("propE default invoked.");
                                return {
                                    message: "I am from propE."
                                };
                            }
                        }, 
                        // 自定义验证函数
                        propF: {
                            isValid: function (value) {
                                return value > 100;
                            }
                        }
                    }
    

    可检测的类型有:

    • String
    • Number
    • Boolean
    • Function
    • Object
    • Array
    • Symbol

    单向数据流

    props 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件五一修改父组件的状态。

    所以不应该在子组件中修改 props 中的值,Vue 会报出警告。

    let childNode = {
      template: `
              <div class="child">
                <div>
                  <span>子组件数据</span>
                  <input v-model="forChildMsg"/>
                </div>
                <p>{{forChildMsg}}</p>
              </div>`,
      props: {
        "for-child-msg": String
      }
    };
    let parentNode = {
      template: `
              <div class="parent">
                <div>
                  <span>父组件数据</span>
                  <input v-model="msg"/>
                </div>
                <p>{{msg}}</p>
                <child :for-child-msg="msg"></child>
              </div>
            `,
      components: {
        child: childNode
      },
      data() {
        return {
          msg: "default string."
        };
      }
    };
    

    这里我们给父组件和子组件都有一个输入框,并且显示出父组件数据和子组件的数据。当我们在父组件的输入框输入新数据时,同步的子组件数据也被修改了;这就是 props 的向子组件传递数据。而当我们修改子组件的输入框时,浏览器的控制台则报出错误警告

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forChildMsg"

  • 修改 props 数据

    • 通常有两种原因:
    • prop 作为初始值传入后,子组件想把它当做局部数据来用
    • prop 作为初始值传入后,由子组件处理成其他数据输出

    应对办法是:

    • 定义一个局部变量,并用 prop 的值初始化它

    但是由于定义的 ownChildMsg 只能接受 forChildMsg 的初始值,当父组件要传递的值变化发生时,ownChildMsg 无法收到更新。

    let childNode = {
      template: `
              <div class="child">
                <div>
                  <span>子组件数据</span>
                  <input v-model="forChildMsg"/>
                </div>
                <p>{{forChildMsg}}</p>
                <p>ownChildMsg : {{ownChildMsg}}</p>
              </div>`,
      props: {
        "for-child-msg": String
      },
      data() {
        return { ownChildMsg: this.forChildMsg };
      }
    };
    

    这里我们加了一个

    用于查看 ownChildMsg 数据是否变化,结果发现只有默认值传递给了 ownChildMsg,父组件改变只会变化到 forChildMsg,不会修改 ownChildMsg。

    • 定义一个计算属性,处理 prop 的值并返回

      由于是计算属性,所以只能显示值,不能设置值。我们这里设置的是一旦从父组件修改了 forChildMsg 数据,我们就把 forChildMsg 加上一个字符串"---ownChildMsg",然后显示在屏幕上。这时是可以每当父组件修改了新数据,都会更新 ownChildMsg 数据的。

      let childNode = {
        template: `
                <div class="child">
                  <div>
                    <span>子组件数据</span>
                    <input v-model="forChildMsg"/>
                  </div>
                  <p>{{forChildMsg}}</p>
                  <p>ownChildMsg : {{ownChildMsg}}</p>
                </div>`,
        props: {
          "for-child-msg": String
        },
        computed: {
          ownChildMsg() {
            return this.forChildMsg + "---ownChildMsg";
          }
        }
      };
      
    • 更加妥帖的方式是使用变量存储 prop 的初始值,并用 watch 来观察 prop 值得变化。发生变化时,更新变量的值。

      let childNode = {
        template: `
                <div class="child">
                  <div>
                    <span>子组件数据</span>
                    <input v-model="forChildMsg"/>
                  </div>
                  <p>{{forChildMsg}}</p>
                  <p>ownChildMsg : {{ownChildMsg}}</p>
                </div>`,
        props: {
          "for-child-msg": String
        },
        data() {
          return {
            ownChildMsg: this.forChildMsg
          };
        },
        watch: {
          forChildMsg() {
            this.ownChildMsg = this.forChildMsg;
          }
        }
      };
      
    • 单向数据流

    props 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件五一修改父组件的状态。

    所以不应该在子组件中修改 props 中的值,Vue 会报出警告。

    let childNode = {
      template: `
              <div class="child">
                <div>
                  <span>子组件数据</span>
                  <input v-model="forChildMsg"/>
                </div>
                <p>{{forChildMsg}}</p>
              </div>`,
      props: {
        "for-child-msg": String
      }
    };
    let parentNode = {
      template: `
              <div class="parent">
                <div>
                  <span>父组件数据</span>
                  <input v-model="msg"/>
                </div>
                <p>{{msg}}</p>
                <child :for-child-msg="msg"></child>
              </div>
            `,
      components: {
        child: childNode
      },
      data() {
        return {
          msg: "default string."
        };
      }
    };
    

    这里我们给父组件和子组件都有一个输入框,并且显示出父组件数据和子组件的数据。当我们在父组件的输入框输入新数据时,同步的子组件数据也被修改了;这就是 props 的向子组件传递数据。而当我们修改子组件的输入框时,浏览器的控制台则报出错误警告

    [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forChildMsg"

  • 修改 props 数据

    通常有两种原因:

    • prop 作为初始值传入后,子组件想把它当做局部数据来用

    • prop 作为初始值传入后,由子组件处理成其他数据输出

    应对办法是

    • 定义一个局部变量,并用 prop 的值初始化它

      但是由于定义的 ownChildMsg 只能接受 forChildMsg 的初始值,当父组件要传递的值变化发生时,ownChildMsg 无法收到更新。

      let childNode = {
          template: `
                  <div class="child">
                    <div>
                      <span>子组件数据</span>
                      <input v-model="forChildMsg"/>
                    </div>
                    <p>{{forChildMsg}}</p>
                    <p>ownChildMsg : {{ownChildMsg}}</p>
                  </div>`,
          props: {
            "for-child-msg": String
          },
          data() {
            return { ownChildMsg: this.forChildMsg };
          }
        };
    

    这里我们加了一个

    用于查看 ownChildMsg 数据是否变化,结果发现只有默认值传递给了 ownChildMsg,父组件改变只会变化到 forChildMsg,不会修改 ownChildMsg。

    • 定义一个计算属性,处理 prop 的值并返回

      由于是计算属性,所以只能显示值,不能设置值。我们这里设置的是一旦从父组件修改了 forChildMsg 数据,我们就把 forChildMsg 加上一个字符串"---ownChildMsg",然后显示在屏幕上。这时是可以每当父组件修改了新数据,都会更新 ownChildMsg 数据的。

    let childNode = {
      template: `
              <div class="child">
                <div>
                  <span>子组件数据</span>
                  <input v-model="forChildMsg"/>
                </div>
                <p>{{forChildMsg}}</p>
                <p>ownChildMsg : {{ownChildMsg}}</p>
              </div>`,
      props: {
        "for-child-msg": String
      },
      computed: {
        ownChildMsg() {
          return this.forChildMsg + "---ownChildMsg";
        }
      }
    };
    
    • 更加妥帖的方式是使用变量存储 prop 的初始值,并用 watch 来观察 prop 值得变化。发生变化时,更新变量的值。
    let childNode = {
      template: `
              <div class="child">
                <div>
                  <span>子组件数据</span>
                  <input v-model="forChildMsg"/>
                </div>
                <p>{{forChildMsg}}</p>
                <p>ownChildMsg : {{ownChildMsg}}</p>
              </div>`,
      props: {
        "for-child-msg": String
      },
      data() {
        return {
          ownChildMsg: this.forChildMsg
        };
      },
      watch: {
        forChildMsg() {
          this.ownChildMsg = this.forChildMsg;
        }
      }
    };
    

    上面部分代码借鉴www.jianshu.com/p/89bd18e44…

4.2.3.2 子传父
  • 在子组件中通过$emit()来触发事件。

  • 在父组件中通过v-on来监听子组件事件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <!-- 父组件模板 -->
        <div id="app">
            <!-- 调用childMessage方法的时候如果省略参数,系统将会自动接收子组件方法传递过来的值 -->
            <cpn @btn-click = childMessage></cpn>
        </div>
        
    <!-- 子组件模板 -->
        <template id="cpn">
            <div>
                <button @click = "logClick(item)" v-for = "item in categories">{{item.wname}}</button>
            </div>
        </template>
    </body>
    <script src="../js/vue.js"></script>
    <script>
        // 子组件
        const cpn ={
            template:"#cpn",
            data(){
                return {
                    categories:[
                        {
                            id:1,
                            wname:'篮球'
                        },{
                            id:2,
                            wname:'乒乓球'
                        },
                        {
                            id:3,
                            wname:'羽毛球'
                        },
                    ]
                }
            },
            methods:{
                logClick(item){
                    // 这个输出是在子组件本身内完成的
                    console.log("我点击了",item.wname);
                    // 下面是将数据传递给父组件
                    this.$emit('btn-click',item.wname,)
                }
            }
        }
        // 父组件
        const app = new Vue({
            el:'#app',
            data(){
                return{
                message:'hello'
    
                }
            },
            methods:{
                childMessage(arg){
                    console.log(arg);
                }
            },
            components:{
                cpn
            }
        })
    </script>
    </html>
    

父子组间访问

4.3.4.1 父访问子
  • $children

    $children是一个数组,打印之后显示VueComponent,VueComponent里面包含了很多数据,其中就有自组件中的属性或者方法。

    • 整体代码:
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../js/vue.js" type="text/javascript"></script>
    </head>
    
    <body>
        <div id="app">
            <button @click="btnClick">按钮</button>
            <Cpn></Cpn>
        </div>
        <template id="cpn">
            <div>我是子组件</div>
        </template>
    </body>
    
    <script>
        //下面是子组件
        const Cpn = {
            template: '#cpn',
            // 子组件的data必须使用方法的形式
            data(){
                return{
                    msg:"我是一条在子组件的消息"
                }
            },
            methods: {
                showMessage() {
                    console.log("sss");
                    return  "我是返回的内容"
                }
            }
        };
      //下面是父组件  
        const app = new Vue({
            el: "#app",
            data() {
                return {
                   
                }
            },
            methods: {
                btnClick() {
                    // 获取$children
                    console.log(this.$children);
                    // 获取子组件中的方法并调用
                    console.log(this.$children[0].showMessage());
                    // 获取子组件data中的变量
                    console.log(this.$children[0].msg)
                },
            },
            components: {
                Cpn
            }
        })
    </script>
    
    </html>
    
    • 关键代码:
     methods: {
                btnClick() {
                    // 获取$children
                    console.log(this.$children);
                    // 获取子组件中的方法并调用
                    console.log(this.$children[0].showMessage());
                    // 获取子组件data中的变量
                    console.log(this.$children[0].msg)
                },
            },
    

    但是不推荐这种,因为如果后期dom树更改的时候,就会对这个方法造成影响。

  • $refs

    refs是一个对象,默认是个空对象,里面包含了被选中的子组件的所有元素

    • 在需要使用的子集上通过属性ref添加属性名

    • 在父级使用$refs.属性名来获取自组件中的数据

    • 整体代码

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="../js/vue.js" type="text/javascript"></script>
      </head>
      
      <body>
          <div id="app">
              <button @click="btnClick">按钮</button>
              <Cpn ref="son" ></Cpn>
          </div>
          <template id="cpn">
              <div>我是子组件</div>
          </template>
      </body>
      
      <script>
          const Cpn = {
              template: '#cpn',
              // 子组件的data必须使用方法的形式
              data(){
                  return{
                      msg:"我是一条在子组件的消息"
                  }
              },
              methods: {
                  showMessage() {
                      console.log("sss");
                      return  "我是返回的内容"
                  }
              }
          };
          const app = new Vue({
              el: "#app",
              data() {
                  return {
                     
                  }
              },
              methods: {
                  btnClick() {
                      // // 获取$children
                      // console.log(this.$children);
                      // // 获取子组件中的方法并调用
                      // console.log(this.$children[0].showMessage());
                      // // 获取子组件data中的变量
                      // console.log(this.$children[0].msg)
      
                      // 打印$refs
                      console.log(this.$refs);
                      // 获取aaa的msg变量
                      console.log(this.$refs.son.msg);
                      // 获取aaa的方法
                      console.log(this.$refs.son.showMessage());
                  },
              },
              components: {
                  Cpn
              }
          })
      </script>
      
      </html>
      
    • 关键代码;

          btnClick() {
              // 打印$refs
              console.log(this.$refs);
              // 获取aaa的msg变量
              console.log(this.$refs.son.msg);
              // 获取aaa的方法
              console.log(this.$refs.son.showMessage());
          },
      
  • 子访问父(理解)

    • $parent

      可以访问到子组件的直系父级(但因为这样的原因,开发时一个组件可能有多个父级,但并不是所有父级都有你所要的属性,那么就会返回undefined,所以开发时一般不用)。

      • 完整代码

        <!DOCTYPE html>
        <html lang="en">
        
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <script src="../js/vue.js" type="text/javascript"></script>
        </head>
        
        <body>
            <div id="app">
                <cpn ref="son"></cpn>
            </div>
            <!-- 下面是cpn子组件 -->
            <template id="cpn">
                <div>
        
                    <div>我是cpn组件</div>
                    <ccpn></ccpn>
                </div>
        
        
        
            </template>
            <!-- 下面是ccpn组件 -->
            <template id="ccpn">
                <div>
                    <div>我是ccpn组件</div>
                    <button @click="checkParent">按钮</button>
                </div>
            </template>
        </body>
        
        <script>
            // 下面是cpn的子组件
            const ccpn = {
                template: "#ccpn",
                methods: {
                    checkParent() {
                        console.log(this.$parent.msg);
                    }
        
                }
        
            }
            // 下面是第一个子组件
            const cpn = {
                template: '#cpn',
                // 注册ccpn组件
                components: {
                    ccpn
                },
                data() {
                    return {
                        msg: "我是cpn组件中的一条消息"
                    }
                },
                methods: {}
            };
        
            const app = new Vue({
                el: "#app",
                components: {
                    cpn,
                }
            })
        </script>
        
        </html>
        
      • 关键代码:

            methods: {
                checkParent() {
                    console.log(this.$parent.msg);
                }
        
            }
        
    • $root

      无论在哪的组件,可以直接获取到根组件的属性:

      • 完整代码

        <!DOCTYPE html>
        <html lang="en">
        
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
            <script src="../js/vue.js" type="text/javascript"></script>
        </head>
        
        <body>
            <div id="app">
                <cpn ref="son"></cpn>
            </div>
            <!-- 下面是cpn子组件 -->
            <template id="cpn">
                <div>
        
                    <div>我是cpn组件</div>
                    <ccpn></ccpn>
                </div>
        
        
        
            </template>
            <!-- 下面是ccpn组件 -->
            <template id="ccpn">
                <div>
                    <div>我是ccpn组件</div>
                    <button @click="checkParent">按钮</button>
                </div>
            </template>
        </body>
        
        <script>
            // 下面是cpn的子组件
            const ccpn = {
                template: "#ccpn",
                methods: {
                    checkParent() {
                        console.log(this.$root.msg);
                    }
        
                }
        
            }
            // 下面是第一个子组件
            const cpn = {
                template: '#cpn',
                // 注册ccpn组件
                components: {
                    ccpn
                },
                data() {
                    return {
                        msg: "我是cpn组件中的一条消息"
                    }
                },
                methods: {}
            };
        
            const app = new Vue({
                el: "#app",
                data(){
                    return{
                        msg:"我是根组件的一条消息"
                    }
                },
                components: {
                    cpn,
                }
            })
        </script>
        
        </html>
        
      • 关键代码

                methods: {
                    checkParent() {
                        console.log(this.$root.msg);
                    }
        
                }
        

五、插槽

5.1普通插槽的使用

  • 首先在子组件中需要改变的地方写上插槽(<slot></slot>)

  • 在父级调用组件的标签内写这里需要显示的内容(调用就必须使用双标签

  • 整体代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../js/vue.js" type="text/javascript"></script>
        <style>
            img {
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <!-- 第一个插槽显示按钮 -->
            <cpn> <button>按钮</button></cpn>
            <!-- 第二个插槽显示图片 -->
            <cpn> <img src="../img/01.png" alt="个人logo"></cpn>
            <!-- 第三个插槽显示文字 -->
            <cpn> <span>我就是一段文字</span></cpn>
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <h2>我是一个子组件</h2>
                <!-- 定义一个插槽 -->
                <slot></slot>
            </div>
        </template>
    </body>
        
    <script>
        const cpn = {
            template: "#cpn",
        };
        const app = new Vue({
            el: "#app",
            components: {
                cpn
            }
        })
    </script>
    </html>
    
  • 关键代码:

        <div id="app">
            <!-- 第一个插槽显示按钮 -->
            <cpn> <button>按钮</button></cpn>
            <!-- 第二个插槽显示图片 -->
            <cpn> <img src="../img/01.png" alt="个人logo"></cpn>
            <!-- 第三个插槽显示文字 -->
            <cpn> <span>我就是一段文字</span></cpn>
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <h2>我是一个子组件</h2>
                <!-- 定义一个插槽 -->
                <slot></slot>
            </div>
        </template>
    

5.2默认插槽值

  • 使用过程与上面一样,不同的是在子组件中直接给<solt></slot>一个默认值

  • 在调用的时候,如果没有指定插槽内容,就会显示默认的插槽内容

    <body>
        <div id="app">
            <!-- 第一个插槽显示按钮 -->
            <cpn> <button>我是另一个按钮</button></cpn>
            <!-- 第二个插槽默认内容 -->
            <cpn></cpn>
            <!-- 第三个插槽显示文字 -->
            <cpn> <span>我就是一段文字</span></cpn>
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <h2>我是一个子组件</h2>
                <!-- 给插槽一个默认值 -->
                <slot><button>按钮</button></slot>
            </div>
        </template>
    </body>
    
    

5.3具名插槽

  • 使用name属性给插槽一个标识

  • 在调用的时候使用属性slot来确定给哪个插槽替换

  • 如果没有用名称来确定的标识的内容,就会去寻找未具名的插槽,将其替换

  • 如果没有未具名的插槽,未指定name的内容将不会显示

  • 整体代码

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../js/vue.js" type="text/javascript"></script>
        <style>
            img {
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <cpn>
                <!-- 将第一个插槽替换为按钮 -->
                <button slot="first">&lt;</button>
                <!-- 将第二个插槽替换为文本+输入框 -->
                <span slot="second">
                    <span>搜索</span>
                    <input type="text" placeholder="搜索">
                </span>
                <!-- 第三个插槽变空 -->
                <span slot="third"></span>
                <!-- 替换没有名称的插槽 -->
                <span></span>
                <span></span>
            </cpn>
    
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <slot name="first">我是第一个插槽</slot>
                <slot name="second">我是第二个插槽</slot>
                <slot name="third">我是第三个插槽</slot>
                <slot></slot>
            </div>
        </template>
    </body>
    
    <script>
        const cpn = {
            template: "#cpn",
        };
        const app = new Vue({
            el: "#app",
            components: {
                cpn
            }
        })
    </script>
    
    </html>
    
  • 关键代码

       <div id="app">
            <cpn>
                <!-- 将第一个插槽替换为按钮 -->
                <button slot="first">&lt;</button>
                <!-- 将第二个插槽替换为文本+输入框 -->
                <span slot="second">
                    <span>搜索</span>
                    <input type="text" placeholder="搜索">
                </span>
                <!-- 第三个插槽变空 -->
                <span slot="third"></span>
                <!-- 替换没有名称的插槽 -->
                <span></span>
                <span></span>
            </cpn>
    
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <slot name="first">我是第一个插槽</slot>
                <slot name="second">我是第二个插槽</slot>
                <slot name="third">我是第三个插槽</slot>
                <slot></slot>
            </div>
        </template>
    

    如果我们在子组件中将最后的那个未具名的插槽删除,那么上面的文字将不会显示:

        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <slot name="first">我是第一个插槽</slot>
                <slot name="second">我是第二个插槽</slot>
                <slot name="third">我是第三个插槽</slot>
            </div>
        </template>
    

5.4作用域插槽

  • 在子组件中的插槽上通过:来绑定一个属性名与属性值,语法为<slot :list = "Arr"> 内容 </slot>属性名不能包含大写字母

  • 父级在调用的时候需要在组件内写一个<template slot-scope="slot">内容</template>(低版本必须用template,高版本可以不用)

  • 整体代码

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="../js/vue.js" type="text/javascript"></script>
        <style>
            img {
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <cpn></cpn>
            <cpn>
                <template slot-scope="slot">
                    <span v-for="item in slot.list">{{item}}-</span>
                </template>
            </cpn>
    
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <h2>我是子组件</h2>
                <slot :list="Arr">
                    <ul>
                        <li v-for="item in Arr">
                            {{item}}
                        </li>
                    </ul>
                </slot>
            </div>
        </template>
    </body>
    
    <script>
        const cpn = {
            template: "#cpn",
            data() {
                return {
                    Arr: ["xiaoming", "xiaohong", "xiaoguang"]
                }
            }
        };
        const app = new Vue({
            el: "#app",
    
    
            data() {
                return {
    
                }
            },
            methods: {},
            components: {
                cpn
            }
        })
    </script>
    
    </html>
    
  • 关键代码:

        <div id="app">
            <cpn></cpn>
            <cpn>
                <template slot-scope="slot">
                    <span v-for="item in slot.list">{{item}}*</span>
                </template>
            </cpn>
    
        </div>
        <!-- 下面是子组件 -->
        <template id="cpn">
            <div>
                <h2>我是子组件</h2>
                <slot :list="Arr">
                    <ul>
                        <li v-for="item in Arr">
                            {{item}}
                        </li>
                    </ul>
                </slot>
            </div>
        </template>
    

六、webpack