11.组件练习:Tabs

未匹配的标注
  • 本系列文章为laracasts.com 的系列视频教程——Learn Vue 2: Step By Step 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频,支持正版
  • 视频源码地址:github.com/laracasts/Vue-Forms
  • 项目初始版本为Vue 2.1.3,教程还在更新中。

本节说明

  • 对应第 11 小节:Practical Component Exercise 3:Tabs

本节内容

接下来来做第三个练习:Tabs组件,我们的例子从 Bulma 网站选取。我们期望的组件将会向下面那样使用:

index.html

<!DOCTYPE html>

<html>
    <head>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.2.3/css/bulma.css">
        <style type="text/css">
            body{
                padding-top: 40px;
            }
        </style>
    </head>

    <body>
        <div id="root" class="container">
            <tabs>
                <tab name="About us">
                    <h1>Here is the content about our tabs</h1>
                </tab>

                <tab name="About our culture">
                    <h1>Here is the content about our culture</h1>
                </tab>

                <tab name="About our vision">
                    <h1>Here is the content about our vision</h1>
                </tab>
            </tabs>
        </div>

        <script src="https://unpkg.com/vue@2.1.3/dist/vue.js"></script>

        <script src="main.js"></script>
    </body>
</html>

所以我们将会有两个组件:tabstabtabs中包含多个tab。首先我们来注册tabstab组件:

main.js

Vue.component('tabs',{
    template:`
    <div>
        <div class="tabs">
            <ul>
                <li v-for="tab in tabs">
                    <a href="#">{{ tab.name }}</a>
                </li>
            </ul>
        </div>

        <div class="tabs-details">
            <slot></slot>
        </div>
    <div>
    `,

    data(){
        return { tabs:[] };
    },

    created() {
        this.tabs = this.$children;
    },
});

Vue.component('tab',{
    template:`
        <div><slot></div></div>
    `,

    props:{
        name:{ required:true },
    }
});

new Vue({
    el:'#root'
});

目前的页面效果:
file
看上去我们需要设置选中状态:

main.js

Vue.component('tabs',{
    template:`
    <div>
        <div class="tabs">
            <ul>
                <li v-for="tab in tabs" :class="{'is-active':tab.selected}">
                    <a href="#">{{ tab.name }}</a>
                </li>
            </ul>
        </div>
        .
        .
    <div>
    `,
    .
    .
});

Vue.component('tab',{
    template:`
        <div><slot></div></div>
    `,

    props:{
        name:{ required:true },
        selected:{ default:false }
    }
});

new Vue({
    el:'#root'
});

我们默认tab组件的selected属性为false,那么:class="{'is-active':tab.selected}"则表示我们默认is-active类不存在。接下来我们给第一个tab组件绑定selected属性为true,显示为选中状态:

index.html

.
.
<div id="root" class="container">
        <tabs>
            <tab name="About us" :selected="true">
                <h1>Here is the content about our tabs</h1>
            </tab>

            <tab name="About our culture">
                <h1>Here is the content about our culture</h1>
            </tab>

            <tab name="About our vision">
                <h1>Here is the content about our vision</h1>
            </tab>
        </tabs>
    </div>
    .
    .

现在来查看效果:
file
下面我们来做点击标签切换选中状态。你可能会像下面这样做:

main.js

Vue.component('tabs',{
    template:`
    <div>
        <div class="tabs">
            <ul>
                <li v-for="tab in tabs" :class="{'is-active':tab.selected}">
                    <a href="#" @click="selectTab(tab)">{{ tab.name }}</a>
                </li>
            </ul>
        </div>

        <div class="tabs-details">
            <slot></slot>
        </div>
    <div>
    `,

    data(){
        return { tabs:[] };
    },

    created() {
        this.tabs = this.$children;
    },

    methods:{
        selectTab(selectedTab){
            this.tabs.forEach(tab => {
                tab.selected = (tab.name == selectedTab.name);
            });
        }
    }
});

Vue.component('tab',{
    template:`
        <div><slot></div></div>
    `,

    props:{
        name:{ required:true },
        selected:{ default:false }
    }
});

new Vue({
    el:'#root'
});

我们点击标签,出发selectTab方法,改变其他组件的selected的属性值。但是这样在Vue 2中是行不通的:
file
组件内不能修改 props 的值,同时修改的值也不会同步到组件外层,即调用组件方不知道组件内部当前的状态是什么。

所有的 prop 都使得其父子 prop 之间形成了一个 单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

然而我们现在的确想做到父子组件间的双向通信,我们可以这么做:

main.js

Vue.component('tabs',{
    template:`
    <div>
        <div class="tabs">
            <ul>
                <li v-for="tab in tabs" :class="{'is-active':tab.isActive}">
                    <a href="#" @click="selectTab(tab)">{{ tab.name }}</a>
                </li>
            </ul>
        </div>

        <div class="tabs-details">
            <slot></slot>
        </div>
    <div>
    `,

    data(){
        return { tabs:[] };
    },

    created() {
        this.tabs = this.$children;
    },

    methods:{
        selectTab(selectedTab){
            this.tabs.forEach(tab => {
                tab.isActive = (tab.name == selectedTab.name);
            });
        }
    }
});

Vue.component('tab',{
    template:`
        <div><slot></slot></div>
    `,

    props:{
        name:{ required:true },
        selected:{ default:false }
    },

    data() {
        return {
            isActive:false
        }
    },

    mounted() {
        this.isActive = this.selected;
    },
});

new Vue({
    el:'#root'
});

我们将样式关联到isActive数据属性中,然后进行动态切换:
file
接下来我们进行最后的步骤:切换标签时动态显示不同的内容,并且更新链接。对于第一个问题,我们只需设置内容显示与否由组件的isActive是否为true即可;对于第二个问题,标签的链接属于不会变化的内容,计算属性 可以帮我们做到:

main.js

Vue.component('tabs',{
    template:`
    <div>
        <div class="tabs">
            <ul>
                <li v-for="tab in tabs" :class="{'is-active':tab.isActive}">
                    <a :href="tab.href" @click="selectTab(tab)">{{ tab.name }}</a>
                </li>
            </ul>
        </div>

        <div class="tabs-details">
            <slot></slot>
        </div>
    <div>
    `,

    data(){
        return { tabs:[] };
    },

    created() {
        this.tabs = this.$children;
    },

    methods:{
        selectTab(selectedTab){
            this.tabs.forEach(tab => {
                tab.isActive = (tab.name == selectedTab.name);
            });
        }
    }
});

Vue.component('tab',{
    template:`
        <div v-show="isActive"><slot></slot></div>
    `,

    props:{
        name:{ required:true },
        selected:{ default:false }
    },

    data() {
        return {
            isActive:false
        }
    },

    computed:{
        href(){
            return '#' + this.name.toLowerCase().replace(/ /g,'-');
        }
    },

    mounted() {
        this.isActive = this.selected;
    },
});

new Vue({
    el:'#root'
});

最终效果:
file

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~