键盘敲烂,月薪过万作业不做,等于没学
当前系列: Vue.js 修改讲义

事件

首先,组件上一样可以绑定事件。但注意:

事件是绑定在组件自己的template上,是调用组件的地方
Vue.component('todo-item', {
    template: '<a @click="greet" >点我</a>',
    methods: {
        greet: function () {
之前我们学过的事件知识,完全适用于子组件。


$emit

props的传值是单向的,所以子组件无法影响父组件,除非使用事件:

  • 在子组件上绑定事件,但事件处理程序中使用$emit()通知父组件:
    greet: function () {
        this.$emit("welcome");   //welcome是一个自定义的事件名
    }
  • 父组件使用v-on监听子组件事件:
    <todo-item @welcome="sayHello"></todo-item>
    //父组件实例的methods中
    sayHello: function () {
        alert('hello');
    }

传值

$emit()可以带一个额外的参数

this.$emit("welcome", {
    sname: '阿泰'
});

父组件使用$event获取该参数值

<todo-item @welcome="sayHello($event)"></todo-item>

也可以省略掉这个参数,一样可以通过方法处理函数中的event拿到:(复习事件event)

sayHello: function (event) {
    alert('hello,' + event.sname);
}

注意子组件的事件名,建议总是使用 kebab-case 命名法,因为如果使用 camelCase 或 PascalCase的话,在父组件中都会被转化成kebab-case,造成不一致(演示)


form表单绑定

演示:

  • 组件一样可以使用v-model的:
    template: '<input type="text" name="username" v-model="username" />',
    data() {
        return {
            username: "atai"
        }
    },
  • 通过F12控制台修改v-model的值:
    vm.$children[0].username = "大飞哥"

但是,如果我们要将组件的值$emit到父组件,或者更进一步,vue data就是由父组件提供,就需要明白表单绑定的本质是事件(复习)

这样的v-model声明:

<input type="text" name="username" v-model="username" />
就等价于:
<input @input="username=$event.target.value" :value="username"></input>
我们利用这一点:
  • 在父组件template中这样声明:
    <yz-enroll v-model="username"></yz-enroll>
    其实就类似于:
    <yz-enroll @input="username=$event" :value="username"></yz-enroll>
    暂时看不懂没关系,后面在回过来来看,^_^
  • username(vue data)设置在父组件中,所以需要用props将父子组件关联起来:
    props: ["value"],
  • template要这样写:
    template: `<input type="text" name="username"
    @input="$emit('input', $event.target.value)" :value="value" />`

即:

  1. :value="username"和props: ["username"]确保vue data中预设的值能够作为input value呈现出来
  2. @input="$emit('input', $event.target.value)"把用户的输入传递给父组件
  3. 父组件通过监听子组件的input事件,获取用户输入,并将其同步到vue data中

自定义model

理解:之前绑定(默认)使用的是input事件和value属性。

但其实这两个值都是可以自定义的,在子组件中额外引入一个model:

model: {
    prop: 'selected',   //属性值:之前默认value 
    event: 'custom'     //事件名:之前默认input
},

然后,相应的更改props和template

props: ["selected"],    //之前是value
template: `<input type="text" name="username" 
    @input="$emit('custom', $event.target.value)"     //之前是input
    :value="selected" />`    //之前是value

vue的文档不知道在说些啥,ʅ(‾◡◝)ʃ

PS:尽信书不如无书,飞哥的视频/讲义也一样,实践是检验真理的唯一标准。

就算是选择性表单(radio/checkbox),一样的呀,既可以使用默认的input和value,也可以使用自定义的model:

data: {
    lodging: true
}
<yz-enroll v-model="lodging"></yz-enroll>
唯一需要注意的是:$emit()的时候,向上传递的不是$event.target.value,而是$event.target.checked
        v-on:change="$emit('custom', $event.target.checked)"


插槽

到此为止,我们调用组件的时候,类HTML标签中没有文本(或其他html元素)。

slot

如果我们想要:

  • 在调用组件时声明一段文本,
  • 在该文本插入到组件的template中,最后渲染出来

就需要使用slot:

template: `<p><slot></slot></p>`

<yz-enroll>hello,源栈</yz-enroll>

模板中当然也可以有其他内容,比如:

template: `<p><slot></slot>,^_^</p>`

默认文本

可以在<slot></slot>中事先设定一段文本:
template: `<p><slot>hello,源栈</slot></p>`

这样,

  • 如果组件调用时没有赋值,使用slot中声明的值
  • 否则,使用组件调用时传入的值
    <yz-enroll>大飞哥你好!</yz-enroll>

命名slot

即当组件的template中要使用多个slot:

  • 每个template都要有个名字
    template: `<p><slot name="before"></slot><slot name="after"></slot></p>`
  • 调用时使用templatev-slot
    <yz-enroll>
        <template v-slot:before>hello</template>
        <template v-slot:after>源栈</template>
    </yz-enroll>

命名slot可以和未命名(默认)slot混用,一个不带 name 的 <slot> 出口会带有隐含的名字“default”,比如:

            template: `<p>
                <slot name="before"></slot>
                <slot></slot>
                <slot name="after"></slot>
            </p>`
调用时,可以:
<yz-enroll>
    <template v-slot:before>hello</template>
    ,
    <template v-slot:after>源栈</template>
</yz-enroll>
也可以:
<yz-enroll>
    <template v-slot:before>hello</template>
    <template v-slot:default>,</template>
    <template v-slot:after>源栈</template>
</yz-enroll>

作用域

调用组件的HTML中可以使用vue data中的数据,比如:
<yz-enroll url="/code">    <!-url是传给子组件的->
        {{username}} <!-OK,username是vue data中的->
        {{url}}      <!-undefined->
</yz-enroll>

HTML元素

为了讲解的清晰简洁,前面我们都使用的是文本,其实HTML标签一样是可以的:

  • 在父组件调用的时候
    <yz-enroll>
        <span> {{username}}</span>
    </yz-enroll>
  • 在子组件的模板里
    template: `<a :href='url'>
            <small> <slot></slot></small>
        </a>`

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。


动态组件

可在调用组件时动态的切换不同的组件。

比如,首先准备两个组件:learn和yz-enroll

var learn = {
    template: `<p>开始学习</p>`
};
Vue.component("yz-enroll", {
    template: `<input type="checkbox" />`
})

learn作为局部组件需要注册一下:

components: {
    learn
},
然后,在vue data中声明一个变量show:
data: {
    show: "learn",  //值为组件名
}
最后,就可以通过vue data中的show动态调用子组件了:
<div :is="show"></div>
演示:更改show的值

keep-alive

动态component是每次都新生成的。

演示:勾选上这个checkbox,再切换template,再切换回checkbox,你会发现:勾选的状态没了……

如果想要保持住以前的勾选状态,就要使用keep-alive缓存这个组件:

<keep-alive>
    <component :is="show"></component>
</keep-alive>


异步组件

如果组件需要通过ajax获取数据,怎么办?

一种办法,和我们之前axios中讲的一样,在钩子函数(比如mounted)中修改vue data:

template: `<label>
    <input type="checkbox" />{{sname}}    <!-template中使用vue data-> </label>`,
data() {
    return {
        sname: 'atai'
    }
},
mounted: function () {
    axios.get("/Student/23").then(response => {
        this.sname = "feige";    //data()方法不影响像对象一样使用this.sname
    })
}

但是,vue为我们提供了一种更简单的方法,异步组件

Vue.component("yz-enroll", function (resolve) {    //回调函数
    axios.get("/Student/23").then(response => {
        resolve({    //必须resolve template: `<label>
                <input type="checkbox" /> ${response.data.sname}
            </label>` }
        )
    })
})

Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。


v-for综合示例

组件也可以被循环渲染出来,先上vue data:

data: {
    todos: [
        {
            id: 1,
            title: '写讲义',
        },
        {
            id: 2,
            title: '作业点评',
        },
        {
            id: 3,
            title: '招生宣传'
        }
    ],
}
再弄一个简单的组件:
Vue.component('todo-item', {
    template: `<li>{{ title }}</li>`
})
父组件使用props向子组件传值:
props: ['title']
<ul>
    <todo-item v-for="(todo, index) in todos" :key="todo.id" :title="todo.title"></todo-item>
</ul>
注意:组件列表必须要有key

早期版本会使用这种写法避免ul和todo-item的冲突:

<li is="todo-item" v-for="(todo, index) in todos" ……

添加一个新的子组件,关键是key值怎么办?如果是真实开发环境,添加的todo数据要首先传往后台,在后台会生成相应的id再返回给前台!纯前端演示时我们可以id++示意

<input v-model="newTodoText" id="new-todo">
<button v-on:submit.prevent="addNewTodo">添加</button>
methods: {
    addNewTodo: function () {
        this.todos.push({
            id: ++this.nextTodoId,
            title: this.newTodoText
        })
        this.newTodoText = ''
    }
}
删除子组件,template中添加一个button,这个button的点击事件能向父组件传递:
template: `<li>{{ title }}<button v-on:click="$emit('remove')">X</button></li>`,
注意:子组件没有将自己的key/id传递给父组件,而是由父组件获取该值然后处理:
v-on:remove="todos.splice(index, 1)"


作业

  1. 将以下页面部分封装成组件

    1. 页头和页脚,其中页头又分为:
      1. 左边logo和slagon
      2. 右边导航条
    2. 最新消息
    3. 侧边栏widgets
    以上组件内容由自己获得(静态/Ajax)
  2. 将以下页面部分封装成组件
    1. 用户名链接,含:等级、用户名、url
    2. 关键字,含:文本和使用次数
    3. 内容(求助/文章/意见建议任选其一)列表项,含:发布时间、标题、正文、作者(组件)、关键字列表(组件)
    4. 分页栏
    以上组件内容由父组件提供,文本内容请使用slot
  3. 将以下form表单元素封装成组件,并能向父组件通过事件传递数据
    1. 验证码输入框,组件自己能够检测用户输入是否正确,同时将验证结果告知父组件,父组件据此确定form表单能否提交
    2. 用户名检索文本框,组件自己可以根据用户输入检索并显示以xx开头的用户,同时将被选中用户告知父组件
    3. 关键字输入框,组件自己能够展示、删除,同时将所有关键字告知父组件
  4. 将以下部分封装成可切换的组件:
    1. 评论框:能够根据当前用户是否登录显示不同的内容
    2. 排行榜:tabs切换
学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 Vue.js 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码