vue 基础学习 第四天

32 组件

  • 什么是组件: 用我自己的话理解就是 “可以复用的、易于管理的div”。 => 一个页面需要轮播图,轮播图写成一个组件,如果需要复用在另一个页面载入该组件即可。用 Vue 开发的网页就是由不同的组件组成的。(之前都是写无数个div划分区域,现在写无数个组件然后组装成网页)
  • 基础代码
<div id="app">
    <!-- 定义好全局组件之后, -->
    <test1></test1>
    <test2></test2>
    <test3></test3>
    <test4></test4>
</div>


<script>
    // 我们在这里定义 “全局组件”
    Vue.component('test1', { // Vue.component('标签', { //... 定义 })
        template: '<h1> 这是全局组件的模板test1 </h1>', //模板
    });

    // 为了方便我们还可以定义一个变量存储局部组件然后在根组件中载入 (这个定义也必须声明在前面)
    var test4= {
        template: '<h2> 这是局部组件的模板test4 </h2>',
    }

    // 我们之前一直定义的其实是 “根组件”
    var app = new Vue({
        el: '#app',
        data: {
            
        },
        // 我们还可以定义 “局部组件”
        components: {
            test3: {
                template: '<h2> 这是局部组件的模板test3 </h2>',
            },
            // test4: test4,
            // 可以使用 ES6 语法,由于 key: value 是一样的,直接:
            test4
        }
    });

    // “全局组件” 必须在根组件之前定义
    Vue.component('test2', {
        template: '<h1> 这是全局组件的模板test2 </h1>',
    });
</script>
  • 定义全局组件 Vue.component('这里对应载入时在html代码中写的标签名', { //这里面写具体定义 })
  • 全局组件 必须 在根组件前面声明。
  • 定义局部组件,写在根组件的 components 里面。 标签名: { //...定义 }
  • 也可以在根组件前面声明并用变量存储起来,然后直接在 components 中载入该变量即可。
  • ES6的语法,如果 json 的 键值对, key的名字和value的名字一样,可以直接写 key ,不写 value 。

33 组件中定义 data 数据

  • 代码
 <div id="app">
    <test></test>
</div>

<!-- 在 Vue 中可以使用这样的方式定义模板 -->
<script type="text/x-template" id="myComponent">
    <ul>
        <li v-for="v in news">{{ v.id }} - {{ v.title }}</li>
    </ul>
</script>

<script>
    var myComponent = {
        template: "#myComponent",
        // data: {} //子组件中不能把data定义成属性。会报错:
        // The "data" option should be a function that returns a per-instance value in component definitions.
        data() { //必须这样定义 data() { return {json对象} }
            return {
                news: [
                    {id:1, title:'测试1'},
                    {id:2, title:'测试2'},
                ],
            };
        }
    };
    var app = new Vue({
        el: '#app',
        data: {

        },
        components: {
            // 有个坑: ES6 方式注册子组件不能使用 驼峰 风格命令,Vue会自动转换成全小写。
            test: myComponent,
        }
    });
</script>
  • 我们是在外面定义的子组件,然后在根组件的 components 中注册。
  • 在注册的时候发现了一个问题: 用ES6语法注册时,我将组件名定义为 "myComponent" ,Vue 解析为 "mycomponent"。没办法注册。
  • 在子组件中定义data必须使用 data() { //json对象 } 这样的语法,而不能直接写 data: {}
  • 可以使用 <script type="text/x-template" id="test"></script> 来定义模板。然后再子组件定义中使用模板 template: "#test"

34 父组件给子组件传递参数

  • 代码
<div id="app">
    <!-- 在标签里面传递 :子组件.props中声明的名字 = "根组件.data中的变量名" -->
    <!-- 如果不写 :属性="value" 的话,会解析为字符串 -->
    <students :students="students" flag1="false" :flag2="true"></students>    
</div>

<!-- 模板 -->
<script type="text/x-template" id="students">
    <ul>
        <li v-for="student in students"> {{ student.id }} - {{ student.name }} </li>

        <span v-if="flag1"> 这其实是字符串 {{ flag1 }} </span> <!-- 这里之所以flag = "false" 会显示是因为 字符串"false" = 布尔true -->
        <span v-if="flag2"> :这才是布尔值 {{ flag2 }} </span> <!-- 因此这里如果传递 flag2 = false 则不会显示 -->
    </ul>
</script>

<script>
    // 子组件
    var students= {
        template: "#students",
        // 在接收时需要在 props 属性中声明
        props: ['students', 'flag1', 'flag2'],
    };
    
    // 根组件
    var app = new Vue({
        el: '#app',
        // 定义父组件的数据
        data: {
            students: [
                {id: 1, name: 'liuihaoyu'},
                {id: 2, name: 'lidaye'},
                {id: 3, name: 'linainai'},
            ],
        },
        // 注册子组件
        components: {
            students,
        }
    });
</script>
  • 整个过程:(根组件)父组件.data中定义数据,然后在html代码中 子组件的标签上 使用 :xxx="定义的变量" 传递数据。
  • 子组件需要声明 props = ['接受的数据xxx', '接受的数据yyy', '接受的数据zzz']
  • 如果在html中, 子组件的标签上这么传递数据 xxx="value" ,这样 子组件.props 接受的其实是字符串,而不是父组件data中声明的值。

35 props 数据验证

  • 代码
<div id="app">
    <!-- 在标签里面传递 :子组件.props中声明的名字 = "根组件.data中的变量名" -->
    <!-- 如果不写 :属性="value" 的话,会解析为字符串 -->
    <students></students>    
</div>

<!-- 模板 -->
<script type="text/x-template" id="students">
    <ul>
        <li v-for="student in students"> {{ student.id }} - {{ student.name }} </li>
    </ul>
</script>

<script>
    // 子组件
    var students= {
        template: "#students",
        // 在 props 中进行数据验证
        props: { // 1、要求props定义成一个对象
            students: { // 2、用 【属性名: { //...相关配置 }】 进行数据接收
                type: [Array, Object], // 指定数据类型
                // required: true, // 设置数据是否必填
                default() { // 设置默认值
                    return [
                        {id: 1, name: 'liuhaoyu'},
                    ];
                },  
                validator(value) { // 设置数据验证规则
                    return value[0].id > 0;
                }
            },
        }
    };
    
    // 根组件
    var app = new Vue({
        el: '#app',
        // 定义父组件的数据
        data: {
        },
        // 注册子组件
        components: {
            students,
        }
    });
</script>
  • 如果想对 子组件.props 属性声明并接收的数据进行数据校验的话,需要将 props 写成对象。
  • 之后接受数据时用 变量名: { //...相关定义 } 进行接收。
  • 在相关定义里,我们可以:
    • type: 指定数据类型,可以使用数组的形式指定多种数据类型,
    • required: true | false 指定是否必须传入该参数
    • default() { //return一个默认值 } 设置默认值
    • validator(value) { //执行相关验证为true通过 } 进行数据验证,为真则通过。 这里发现不通过也会显示数据,但是会在浏览器控制台提醒错误。记得参数里要写一个 value 对应传递进来的数据。

36 通过子组件呼叫父组件实现简单的购物车

  • 代码
<div id="app">
    <cart :goods="goods" @refresh="totalPrice"></cart>
    <span>
        总计:¥ {{ total }} 元
    </span>
</div>

<!-- 模板 -->
<script type="text/x-template" id="cart">
    <table border="1">
        <thead>
            <tr>
                <th>商品名称</th>
                <th>价格</th>
                <th>数量</th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="good in goods">
                <td>{{ good.name }}</td>
                <td>{{ good.price }}</td>
                <td>
                    <input type="text" v-model="good.number" @blur="refresh">
                </td>
            </tr>
        </tbody>
    </table>
</script>

<script>
    //子组件
    var cart = {
        template: "#cart",
        props: {
            goods: {
                type: Array,
            }
        },
        methods: {
            refresh() {
                this.$emit('refresh')
            }
        },
    }
    
    // 根组件
    var app = new Vue({
        el: '#app',
        data: {
            // 定义商品信息
            goods: [
                {name: "macbookPro 2018", price: "20000", number:1},
                {name: "iphone 8", price: "6888", number:1},
                {name: "iphone8 Plus", price: "8888", number:1},
            ],
            // 总价初始化
            total: 0,
        },
        // 注册子组件
        components: {
            cart,
        },
        methods: {
            // 计算总价
            totalPrice() {
                this.total = 0;
                this.goods.forEach((good) => {
                    this.total += good.price * good.number;
                });
            }
        },
        // 挂载钩子程序
        mounted() { // 类似于 初始化程序
            this.totalPrice(); // 直接调用计算总价的方法
        },
    });
</script>
  • 在父组件挂载钩子程序 mounted() { //调用里面的方法,记得 **this.**function() } 。 “相当于初始化方法” => 当组件载入完的时候就执行。
  • 子组件在更改商品数量时,更新父组件中的总价:
    • 第1步: html代码中,在子组件的标签上绑定事件 refresh @refresh="totalPrice" 即子组件调用 refresh() 方法时,就调用的是父组件里的 计算总价 方法。
    • 第2步: 给绑定了商品数量的 input 添加一个失焦事件 @blur 当它改变时,调用子组件的 refresh() 方法
    • 第3步: 子组件的 refresh() 方法被调用时,使用 $this.$emit('refresh') 调用 第1步 上绑定的自定义事件 refresh。
    • 即: 子组件标签上的 @refresh => 事件, @blur="refresh" => 子组件的 methods 中定义的方法。 是 refresh() 方法,通过 this.$emit('事件名') 呼叫了事件。

37 更优写法实现36购物车功能

  • 代码
<div id="app">
    <!-- 这里使用 :绑定属性.sync同步数据="父组件的goods" => 达到了当子组件的goods发生变化时,父组件的goods也会变化 -->
    <cart :goods.sync="goods"></cart>
    <span>
        总计:¥ {{ totalPrice }} 元
    </span>
</div>

<!-- 模板 -->
<script type="text/x-template" id="cart">
    <table border="1">
        <thead>
            <tr>
                <th>商品名称</th>
                <th>价格</th>
                <th>数量</th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="good in goods">
                <td>{{ good.name }}</td>
                <td>{{ good.price }}</td>
                <td>
                    <input type="text" v-model="good.number">
                </td>
            </tr>
        </tbody>
    </table>
</script>

<script>
    //子组件
    var cart = {
        template: "#cart",
        props: {
            goods: {
                type: Array,
            }
        },
    }
    
    // 根组件
    var app = new Vue({
        el: '#app',
        data: {
            // 定义商品信息
            goods: [
                {name: "macbookPro 2018", price: "20000", number:1},
                {name: "iphone 8", price: "6888", number:1},
                {name: "iphone8 Plus", price: "8888", number:1},
            ],
            // 总价初始化
            total: 0,
        },
        // 注册子组件
        components: {
            cart,
        },
        // 计算总价
        computed: {
            totalPrice() {
                var sum = 0;
                this.goods.forEach((good) => {
                    sum += good.price * good.number;
                });
                return sum;
            }
        }
    });
</script>
  • 不需要在子组件标签上绑定自定义事件了,只需要 :goods.sync="goods" 来实现:当子组件的变量 goods 发生变化时, 父组件的goods也同步(sync)发生变化。
  • 同时不需要再在子、父组件中定义 methods 了。 子组件的 input 中也不需要 @blur 绑定失焦事件了。
  • 在父组件中定义 computed 计算属性 computed: { 属性() { //...进行计算 return 结果 } }
  • 在 html 代码,总价中载入 计算属性 中定义的 totalPrice 即可。

38 子组件 slot 内容分发

  • 代码
<div id="app">
    <test>
        <h1 slot="title">这是标题</h1>
        <p slot="content">这是内容</p>
        <test1 slot="myinput" type="email" title="邮箱" placeholder="username@example.com"></test1>
        <test1 slot="myinput" type="text" title="用户名" placeholder="yourNickName"></test1>
        <test1 slot="myinput" type="password" title="密码" placeholder="yourBirthday"></test1>
    </test>
</div>

<!-- 子组件模板 test -->
<script type="text/x-template" id="test">
    <div>
        <slot name="title"></slot>
        <slot name="content"></slot>
        <slot name="myinput"></slot>
    </div>
</script>

<!-- 子组件模板 test1 -->
<script type="text/x-template" id="test1">
    <div>
        <span>{{ title }}</span>
        <input :type="type" :placeholder="placeholder">
    </div>
</script>

<script>
    // 子组件 test
    var test = {
        template: "#test",
    };
    // 子组件 test1
    var test1 = {
        template: "#test1",
        props: ['type', 'title', 'placeholder']
    }
    // 根组件
    var app = new Vue({
        el: '#app',
        data: {
        },
        components: {
            test,
            test1,
        }
    });
</script>
  • 在子组件中定义 slot <slot name="取个名字">
  • 在根组件中填充 slot <任意标签 slot="要填充的slot名字">填充的内容</slot>
  • 利用其他组件在根组件中填充某个组件 <其他组件 slot="要填充的slot名字" 话可以传递属性...></其他组件>
  • 可以反复填充一个slot。

39 scope 的使用

  • 代码
<div id="app">
    <users :users="users" scope="v">
        <!-- 这里 **必须** 使用 template 标签 -->
        <!-- 并且使用 scope="任意变量名" 接收 slot抛出的数据 -->
        <template scope="data">
            <li>
                {{ data.user.id }} - {{ data.user.name }}
            </li>
        </template>
    </users>
</div>

<script type="text/x-template" id="users">
    <ul>
        <!-- <li v-for="user in users"> {{ user.id }} - {{ user.name }}</li> -->
        <!-- 这里用slot定义,但是在后面用 :user="user" 抛出数据 -->
        <slot v-for="user in users" :user="user"></slot>
    </ul>
</script>

<script>
    var users = {
        template: "#users",
        props: {
            users: {
                type: Array,
            },
        }
    };
    var app = new Vue({
        el: '#app',
        data: {
            users: [
                {id: 1, name: 'liuhaoyu'},
                {id: 2, name: 'lidaye'},
                {id: 3, name: 'linainai'},
            ]
        },
        components: {
            users,
        }
    });
</script>
  • 具体过程: 父组件中定义 data.users 数据,然后在子组件标签上传递给子组件。
  • 然后在子组件循环时,使用 <slot> 来遍历数据
  • 并且在遍历时,抛出数据 <slot v-for="data in datas" :data="data"></slot>
  • 在父组件中,载入子组件的标签内
<子组件>
    <template scope="任意变量名这里暂时用data">
        <合适的标签>
            {{ data.data.属性 }} =>第一个data对应scope里的,第二个data对应子组件中使用 :data 抛出的。
        </合适的标签>
    </template>
</子组件>
  • 总体来说,这个功能不常用。整个逻辑更像是在 html 代码中遍历子组件中的 slot。

40 动态组件

  • 代码
<div id="app">
    <input type="radio" v-model="formType" value="myInput"> 使用 input
    <input type="radio" v-model="formType" value="myTextarea"> 使用 textarea

    <div :is="formType"></div>
</div>

<script>
    var myInput = {
        template: "<div> <input> </div>",
    };
    var myTextarea = {
        template: "<div> <textarea></textarea> </div>",
    }
    var app = new Vue({
        el: '#app',
        data: {
            formType: "myInput",
        },
        components: {
            myInput,
            myTextarea
        }
    });
</script>
  • 我们实现了一个功能:通过勾选不同的单选框,显示不同的表单项(input | textarea).
  • 具体实现是通过一个 div 作为载体, 绑定 id 属性 <div :id="formType"> .
  • 这里的 formType 对应的是我们定义并在根组件的 components 属性中声明的根组件.
  • 补充一点疑问: 子组件在 components 声明叫做 myInput, 那么在 html 中载入组件应该使用 <my-input></my-input> 载入,而不是 <myInput></myInput> => 因为这样浏览器会解析为全小写的变量名.(之前遇到过这个问题)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容