13 小程序-VUE-v-model、组件开发-父子通信

1.补充知识

  • 编程范式:命令式编程/声明式编程
  • 编程范式:面向对象编程(第一公民:对象,全部放在对象中)

                      函数式编程(第一公民:函数)

早期,计算提取nums数组出小于100,每个乘2,求和需要分批算

const nums=[10,20,111,222,444,40,50]
let newNums=[]
for(let n of nums){
if(n<100){
newNums.push(n)
}
}
let new2Nums=[]
for(let n of newNums){
new2Nums.push(n*2)
}
let totalNums=0
for(let n of new2Nums){
totalNums+=n
}

现在,我们使用高阶函数filter、map、reduce三种计算:

        高阶函数:filter使用时候,里面需要一个回调函数,且这个回调函数就1个要求,必须返回true值或false值,当true时,函数内部自动将回调的n加入到新的数组中,而false则过滤掉这个n,因此内部用n<100即可完成这个回调。

const num=[10,20,111,222,444,40,50]
let nums1=nums.filter(function (n) {
return n<100
})
console.log(nums1)
  高阶函数:map使用时候,里面需要一个回调函数,且有参数,参数是原数组
里面的每个值,且这个回调函数就1要求,必须返回true值或false值,当true时,
函数内部自动将回调的n加入到新的数组中,而false则过滤掉这个n,
我们用n*2即可加入到数组nums2。

let nums2=nums1.map(function (n) {
return n*2
})



   高阶函数reduce使用的时候,它本身是重载函数,因此我们一般用的是第二个
也就是有初始值,我们本处设置为0,它每次遍历的时候,有两个值,其中
prevalue是上次回调的值,初次使用就是初始值0.
let nums3=nums2.reduce(function (prevalue,n) {
return prevalue+n
},0)
console.log(nums3)

参考原来的for循环遍历,我们高阶函数用法变得简洁许多

const nums=[10,20,111,222,444,40,50]
let newNums=[]
for(let n of nums){
if(n<100){
newNums.push(n)
}
}
let new2Nums=[]
for(let n of newNums){
new2Nums.push(n*2)
}
let totalNums=0
for(let n of new2Nums){
totalNums+=n
}

我们合并使用高阶函数效果如下:

const nums=[10,20,111,222,444,40,50]
let tatal=nums.filter(function (n) {
return n<100
}).map(function () {
return n*2
}).reduce(function (prevalue,n) {
return prevalue+n
},0)

//es6箭头函数,我们很多function可以改成箭头函数

let tatol=nums.filter(n=>n<100).map(n=>n*2).reduce((pre.n)=>pre+n);

2.v-model的使用与原理

        v-model是表单控件的指令,表单控件在实际开发中非常常见,特别是对用户信息的提交,需要大量的表单。例如双向绑定,就是表单元素和数据的双向绑定。

     此处input标签内添加v-model指令,将message数据作为value值添加进input框中,效果如下:以前我们通过mustache语法将message绑定到内容

》{{message}}《,

image

而且删除表框你好,则后面你好也没有了,改边框里面你好,message也变了
,就相当于从响应式变成了双向绑定
<div id="app">
<input type="text" v-model="message">{{message}}

</input>
</div>

<script>
const app=new Vue({
el:'#app',
data:{
message:'你好'
}
})
</script>

此处的实现双向绑定的方法和原理可以参考如下:

<div id="app">
<input type="text" v-bind:value="message"
           v-on:inputmode="inputChange">{{message}}
</input>
</div>

<script>
const app=new Vue({
el:'#app',
data:{
message:'你好'
},
methods:{
inputChange(event){
this.message=event.target.value
}
}
})
</script>

     v-model其实是一个语法糖,它的背后本质上是包含两个操作:

  • 1.v-bind绑定一个value属性
  • 2.v-on指令给当前元素绑定input事件

也就是说下面的代码:等同于下面的代码:

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

等同于

<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">

image

v-bind 用于绑定html属性、v-on 用于绑定html事件

2.1 v-model与单选radio结合使用

     “label”作为英文单词有“标记”的意思。

在html中,<label>标签通常和<input>标签一起使用,<label>标签为input元素定义标注(标记)。<label>标签的作用是为鼠标用户改进了可用性,当用户点击<label>标签中的文本时,浏览器就会自动将焦点转到和该标签相关联的控件上;<label>标签在单选按钮和复选按钮上经常被使用,使用该标签后,你点击单选按钮或复选按钮的文本也是可以选中的。

<label for="关联控件的id" form="所属表单id列表">文本内容</label>

加上name,这样就只能选择一个,因为提交时候name作为可以提交,
这个name是唯一的,这样单选才是互斥的,
但如何结合v-model使用,则可以没有name


image

2.2 v-model与多选checkbox结合使用,存在两种现象:

  • 单个勾选框:要么不选,要么勾选
  • 多个勾选框:多选

单选框效果:

<div id="app">
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议</input>
</label>
<h2>您选中的是{{isAgree}}</h2>
<button :disabled="!isAgree">下一步</button>
</div>
<script>
const app=new Vue({
el:'#app',
data:{
isAgree:false
}
})
</script>

多选框效果
<div id="app">
<label >
<input type="checkbox" value="篮球" v-model="hobbies">篮球</input>
<input type="checkbox" value="足球" v-model="hobbies">足球</input>
<input type="checkbox" value="排球" v-model="hobbies">排球</input>
<input type="checkbox" value="混球" v-model="hobbies">混球</input>
</label>

<h2>您的爱好是{{hobbies}}</h2>
</div>
<script>
const app=new Vue({
el:'#app',
data:{
hobbies:[]

}
})
</script>

复选框分为两种情况:单个勾选框和多个勾选框

单个勾选框:

  • v-model即为布尔值。
  • 此时input的value并不影响v-model的值。

多个复选框:

  • 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
  • 当选中某一个时,就会将input的value添加到数组中。

2.3 v-model与select结合使用

      和checkbox一样,select也分单选和多选两种情况。

单选:只能选中一个值。

  • v-model绑定的是一个值。
  • 当我们选中option中的一个时,会将它对应的value赋值到mySelect中

多选:可以选中多个值。

  • v-model绑定的是一个数组。
  • 当选中多个值,就会将选中的option对应的value添加到数组mySelects中

image

image

2.4 v-model的双向绑定的时候,input有个值绑定概念,简单来说就是把input的value存放在data中一个数组即可,这样就不用写死它

<!--    值绑定,也就是value绑定,通过for循环获取每个值-->
<label v-for="item in originHobbies">
<input type="checkbox" v-bind:value="item" v-model="fruits2">{{item}}
</label>
<h2>选择的是{{fruits2}}</h2>
</div>
<script>
const app=new Vue({
el:'#app',
data:{
fruit:'火龙果',
fruits:[],
fruits2:[],
originHobbies:['苹果','火龙果','杉果','琵琶']
}
})

image

初看Vue官方值绑定的时候,我很疑惑:what the hell is that?

但是仔细阅读之后,发现很简单,就是动态的给value赋值而已:

  • 我们前面的value中的值,可以回头去看一下,都是在定义input的时候直接给定的。
  • 但是真实开发中,这些input的值可能是从网络获取或定义在data中的。
  • 所以我们可以通过v-bind:value动态的给value绑定值。
  • 这不就是v-bind吗?

这不就是v-bind在input中的应用吗?搞的我看了很久,搞不清他想将什么。

这里不再给出对应的代码,因为会用v-bind,就会值绑定的应用了。

2.5 v-model修饰符

lazy修饰符:

  • 默认情况下,v-model默认是在input事件中同步输入框的数据的。
  • 也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
  • lazy修饰符可以让数据在失去焦点或者回车时才会更新:

       早期双向绑定,是实时更新,加上v-model.lazy后,用户敲回车或失去焦点时才更新。

<div id="#app">
<!-- 修饰符lazy 懒惰的意思,也就是懒加载,用到再加载-->
<input type="text" v-model.lazy="message"></input>
<h2>填写的是:{{message}}</h2>
</div>
<script>
const app=new Vue({
el:'#app',
data:{
message:'你好啊'
}
})
</script>

number修饰符:

  • 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
  • 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
  • number修饰符可以让在输入框中输入的内容自动转成数字类型:

默认情况下,v-model输入的值是字符串,我们v-model.number即可为数字

image

trim修饰符:

  • 如果输入的内容首尾有很多空格,通常我们希望将其去除
  • trim修饰符可以过滤内容左右两边的空格

很多时候,看起来没空格,但是真实数据有空格,我们可以这么处理

image

v-model.trim=‘name’,会自动去除

3.组件化开发

人面对复杂问题的处理方式:

  • 任何一个人处理信息的逻辑能力都是有限的
  • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
  • 但是,我们人有一种天生的能力,就是将问题进行拆解。
  • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

组件化也是类似的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

image

组件化是Vue.js中的重要思想

  • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
  • 任何的应用都会被抽象成一颗组件树。

image

      组件化思想的应用:

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

所以,组件是Vue开发中,非常重要的一个篇章,要认真学习。

3.1 注册组件的基本步骤

组件的使用分成三个步骤:

  • 创建组件构造器 vue.extend()。在es6中也有继承概念,跟java类似
  • 注册组件 vue.component()
  • 使用组件 ,在vue实例中使用,实例外使用则没有渲染效果
<script>
<!-- 1.创建组件构造器对象-->
const cpnc=Vue.extend({
template:`//传入模板
<div>
<h2>我是标题</h2>
<p>我是内容</p>
<p>我是内容,哈哈哈</p>
<div>
`

})
// 2.注册组件
Vue.component('mycpn',cpnc)//注册组件的标签名和组件构造器就是上面vue里面
// 3.定义的实例APP对象,局部组件就是在此定义components:{}
    const app=new Vue({
el:'#app',
data:{
message:'你好啊'
}
})
</script>

image

查看运行结果:

  • 和直接使用一个div看起来并没有什么区别。
  • 但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用<my-cpn></my-cpn>来完成呢?

这里的步骤都代表什么含义呢?

1.Vue.extend():

  • 调用Vue.extend()创建的是一个组件构造器。
  • 通常在创建组件构造器时,传入template代表我们自定义组件的模板。
  • 该模板就是在使用到组件的地方,要显示的HTML代码。
  • 事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。

2.Vue.component():

  • 调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
  • 所以需要传递两个参数:1、注册组件的标签名 2、组件构造器

3.组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)

我们来看下面我使用了三次<my-cpn></my-cpn>

而第三次其实并没有生效:,第二次是嵌套div也是可以的。

image

vue当前最新版本2.X,vue CLI脚手架最新版本3.0,脚手架是构建项目用的

3.2 全局组件和局部组件划分

      一般来说,默认的组件都是全局组件,全局组件就是可以在多个vue实例中的使用,也就是多个const app=new vue({}) const app2=new vue({})

     那么如何注册局部组件呢:也就是在某个实例对象下面写:

<div id="app">  //无法使用
<!-- <mycpn></mycpn>-->
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<div id="app2">//可以使用
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<script>
<!-- 1.创建组件构造器对象-->
const cpnc=Vue.extend({
template:`
<div>
<h2>我是标题</h2>
<p>我是内容</p>
<p>我是内容,哈哈哈</p>
</div>
`

})


const app2=new Vue({
el:'#app2',
data:{
message:'你好啊'
},
components:{
cpn:cpnc
}
})

当然开发中,一般使用全局组件,不会使用局部组件,而且开发中一般一个页面一个实例,不会多个实例,但要记得全局组件可以在多个实例中使用。


3.3.父组件和子组件

    所谓父子组件,均是组件,只是A在B组件中进行注册,那么组件A就是B的子组件,也就是说B是A的父组件。

<div id="app">
    <cpn2></cpn2>
</div>
<script>
<!--    第一个组件-->
    const cpnc1= Vue.extend({
        template:`
            <div>
                <h2>我是标题1</h2>
                <p>我是标题,哈哈哈</p>
            </div>
        `
    })

<!--    第二个组件-->
    const cpnc2= Vue.extend({
        template:`
                <div>
                    <cpn1></cpn1>
                    <h2>我是标题2</h2>
                    <p>我是标题,呵呵呵</p>

                </div>
            `
        ,
        components:{
        //    在这里注册组件1
            cpn1:cpnc1
        }
    })

    const app=new Vue({
        el:'#app',
        data:{
            message:"nihao"
        },
        components:{
            cpn2:cpnc2
        }
    })
</script>

image这是效果


      父子组件错误用法:以子标签的形式在Vue实例中使用

  • 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
  • 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了),在父组件编译的时候,会直接将子组件解析使用,这样的话子组件在实例app中其实并不知道子组件的存在,因此使用子组件会出错。在脚手架中,template会被渲染成render函数
  • <child-cpn></child-cpn>是只能在父组件中被识别的。
  • 类似这种用法,<child-cpn></child-cpn>是会被浏览器忽略的。

3.4 组件注册的语法糖

     在vue2.X中,很少见到const cpn1= Vue.extend({ template})

Vue.component("cpn",{
template:`
<div>
<h2>我是语法糖,全局组件</h2>
<p>我是语法糖,全局组件,哈哈哈</p>
</div>
`
})
const app=new Vue({
el:'#app',
data:{

},
components:{
'cpn2':{
template:`
<div>
<h2>我是语法糖,局部组件</h2>
<p>我是语法糖,局部组件,哈哈哈</p>
</div>
`
}
}
})

image

当然,在下面知识中,我们会将template单独抽离出来


3.5 组件模板抽离的写法

A:在<script type="text/x-template" id="cpn2"> 其中id用来和注册组件中template:“#”绑定

<body>
<div id="app">
<cpn></cpn>
</div>
<script type="text/x-template" id="cpn2">
<div>
<h2>我是分离写法组件</h2>
</div>
</script>
<script>
Vue.component('cpn',{
template:'#cpn2'
})
const app=new Vue({
el:'#app',
data:{
}
})
</script>

B template写法:

<template id="cpntwo">
<div>
<h2>我是分离写法组件</h2>
</div>
</template>

image

3.6 组件data必须是函数

       因为我们之前组件template里面的div数据都是写死的,我们如何写活

首先记得:组件无法直接访问vue实例,而且即使能访问,那么vue实例就会非常臃肿。

image

      结论:组件自己的数据存放在哪里呢?

  • 组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
  • 只是这个data属性必须是一个函数
  • 而且这个函数返回一个对象,对象内部保存着数据

image

image

首先,如果不是一个函数,Vue直接就会报错。

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

3.7 父子通信

      在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。但是,在开发中,往往一些数据确实需要从上层传递到下层:

  • 比如在一个页面中,我们从服务器请求到了很多的数据。
  • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。

     这个时候,并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组件(子组件)。如何进行父子组件间的通信呢?Vue官方提到

  • 通过props(properties:属性意思)向子组件传递数据
  • 通过(自定义事件 $emit event)事件向父组件发送消息,emit发射意思

image

       在下面的代码中,我直接将Vue实例当做父组件,并且其中包含子组件来简化代码。真实的开发中,Vue实例和子组件的通信和父组件和子组件的通信过程是一样的。

3.7.1 props如何使用

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

props的值有两种方式:

  • 方式一:字符串数组,数组中的字符串就是传递时的名称。
  • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。

字符串数组 cmovies、cmessage

<div id="app">
<cpn v-bind:cmovies="movies" v-bind:cmessage="message"></cpn>
</div>


<template id="cpn">
<div>
<ul>
<li v-for="item in cmovies" >{{item}}</li>
</ul>
<p>{{cmessage}}</p>
</div>
</template>
<script>
<!--父传子-->
const cpn={
template:'#cpn',
props:['cmovies','cmessage'],
data(){
return{
}
}
};
const app=new Vue({
el:'#app',
data:{
message:'你好啊',
movies:['海王','达到','咋说','税务']
},
components:{
cpn
}

})
</script>

image

     在前面,我们的props选项是使用一个数组。我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

验证都支持哪些数据类型呢?

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

当我们有自定义构造函数时,验证也支持自定义的类型

  • 字符串数组:props:[ 'cmovies','cmessage']
  • 对象:props:{  cmovies:Array ,cmessage:string }对象写法可以验证类型是否是我们初始定义的类型和默认值都可以,很常见 .默认值在没有传递数据的时候,显示。

例如:

props:{

     cmessage:{

          type:String,

          default:'aaaaa' ,

          required:true

     }

}

对于数组而言,vue2.5.4以后,如果要传类型和默认值,得按以下操作:

props:{

     cmovies:{

          type:Array,

          default(){

             return 【】    否则报错,如果是默认对象则return {}即可

            }

     }

}


imageimage

这里 有个required,设置为true,那就是使用我组件时候,必须传参。

3.7.2 父传子通信的props驼峰标识

       当前vue版本中,不支持props属性驼峰原则例如

v-bind:cInfo 会报错,遇到驼峰 c-info即可

image

3.7.3 子传父通信

当子组件发生事件,需要告诉父组件,这样父组件 根据事件的数据请求。props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。

什么时候需要自定义事件呢?

  • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
  • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

自定义事件的流程:

  • 在子组件中,通过$emit()来触发事件。
  • 在父组件中,通过v-on来监听子组件事件。


<body>
<!--父组件模板-->
<div id="app">
<cpn @itemclick="cpnclick"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<button v-for="item in categories" @click="btnclick(item)">{{item.name}} </button>
</div>

</template>
<script>
<!-- 子组件-->
const cpn={
template:'#cpn',
data(){
return{
categories:[
{id:'aaa',name:'热门推荐'},
{id:'bbb',name:'销售热门'},
{id:'ccc',name:'卫生物品'},
{id:'ddd',name:'你的关注'}
]
}
},
methods:{
btnclick(item){
//发射事件,自定义事件和自定义参数
this.$emit('itemclick',item)
}
}
}
<!-- 父组件-->

const app=new Vue({
el:'#app',
data:{
message:'你好啊'
},
components:{
cpn
},
methods:{
cpnclick(item){
console.log('cpnclick',item)
}
}
})

我们来看一个简单的例子:

  • 我们之前做过一个两个按钮+1和-1,点击后修改counter。
  • 我们整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
  • 这样,我们就需要将子组件中的counter,传给父组件的某个属性,比如total。

image

总结

一. 计算属性
1.1. 计算属性的本质
fullname: {set(), get()}
1.2. 计算属性和methods对比
计算属性在多次使用时, 只会调用一次.
它是由缓存的
二. 事件监听
2.1. 事件监听基本使用
2.2. 参数问题
btnClick
btnClick(event)
btnClick(abc, event) -> $event
2.3. 修饰符
stop
prevent
.enter
.once
.native
三. 条件判断
3.1. v-if/v-else-if/v-else
3.2. 登录小案例
3.3. v-show
v-show和v-if区别
四. 循环遍历
4.1. 遍历数组
4.2. 遍历对象
value
value, key
value, key, index
4.3. 数组哪些方法是响应式的
4.4. 作业完成
五. 书籍案例
六. v-model的使用
6.1. v-model的基本使用
v-model => v-bind:value v-on:input
6.2. v-model和radio/checkbox/select
6.3. 修饰符
lazy
number
trim
七. 组件化开发
7.1. 认识组件化
7.2. 组件的基本使用
7.3. 全局组件和局部组件
7.4. 父组件和子组件
7.5. 注册的语法糖
7.6. 模板的分类写法
script
template
7.7. 数据的存放
子组件不能直接访问父组件
子组件中有自己的data, 而且必须是一个函数.
为什么必须是一个函数.
7.8. 父子组件的通信
父传子: props
子传父: $emit
7.9. 项目
npm install
npm run serve




原文地址:https://www.cnblogs.com/rango0550/p/14024004.html