3 vue组件

一般来说我们称为:html+css+js写的小行代码是一个组件

1 组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

image

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

组件分为两种:

  • 全局组件:可以在任意地方使用
  • 局部组件:只在当前组件加载的时候才会加载局部组件

起步:局部组件

<div id="app">
    <!-- 3.使用子组件-->
    <App></App>
</div>

<script src="vue.js"></script>
<script>
    // App组件 html+css+js
    // 使用局部组件的打油诗:建子 挂子 用子
    // 1.创建组件
    const App = {
        // 注意:在组件中这个data必须是一个函数
        data(){
            return {
                msg: '我是App组件'
            };
        },
        template: `
            <div>
                <h3>{{msg}}</h3>
                <button @click="handleClick">按钮</button>
            </div>
        `,
        methods: {
            handleClick(){
                this.msg = "学习局部组件";
            }
        },
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            // 2.挂载子组件
            App,
        }
    })
</script>

起步:全局组件

<div id="app">
    <!-- 3.使用子组件-->
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    // App组件 html+css+js

    // 创建全局组件(第一个参数是组件名,第二个是配置)
    // 只要创建全局组件,可以在任意地方使用
    Vue.component('Vheader', {
        template: `
            <div>
                我是导航组件
            </div>
        `
    })
    Vue.component('Vaside', {
        template: `
            <div>
                我是侧边栏组件
            </div>
        `
    })

    const Vbtn = {
        template: `
            <button>按钮</button>
        `
    }


    const Vcontent = {
        data() {
            return {}
        },
        template: `
            <div>
                我是内容组件
                <Vbtn></Vbtn>
            </div>
        `,
        components: {
            Vbtn,
        }
    }

    const App = {
        // 注意:在组件中这个data必须是一个函数
        data() {
            return {};
        },
        components: {
            Vcontent,
        },
        template: `
            <div>
                <Vheader></Vheader>
                <div>
                    <Vaside/>
                    <Vcontent/>
                </div>
            </div>
        `,
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            // 2.挂载子组件
            App,
        }
    })
</script>

2 组件通信

1 父往子传数据

// 父传子数据(通过props):
// 1.在子组件中声明props接收在父组件挂载的属性
// 2.可以在子组件的template中任意使用
// 3.在父组件绑定自定义的属性
<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    // 子组件
    Vue.component('Child',{
        template: `
            <div>
                <h3>我是一个子组件</h3>
                <h4>{{childData}}</h4>
            </div>
        `,
        props:['childData']
    })

    // 父组件
    const App = {
        data() {
            return {
                msg: '我是父组件传进来的值'
            };
        },
        template: `
            <div>
                <Child :childData="msg"/>
            </div>
        `, // 传给子组件数据
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

2 子往父传数据

// 子往父传值
// 1.在父组件中子组件绑定自定义事件
// 2.在子组件中触发原生的事件,在事件函数通过this.$emit触发自定义事件
<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    // 子组件
    Vue.component('Child',{
        template: `
            <div>
                <h3>我是一个子组件</h3>
                <input type="text" @input="handleInput">
            </div>
        `,
        methods:{
            handleInput(e){
                const val = e.target.value;
                this.$emit('inputHandler',val);
            }
        }
    })

    // 父组件
    const App = {
        data() {
            return {
                newVal: '',
            };
        },
        methods: {
          input(newVal){
              this.newVal = newVal;
          }
        },
        template: `
            <div>
                <div class="father">数据:{{newVal}}</div>
                <Child @inputHandler="input"/>
            </div>
        `,
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            // 2.挂载子组件
            App,
        }
    })
</script>

3 平行事件

1.中央事件总线bus

两个子组件同指向一个父组件,那么两个子组件之间怎么通信呢?,

这个就用到了bus

const bus = new Vue();
<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    const bus = new Vue();

    // 中央事件总线 bus
    // 组件中的this指向的是当前的组件
    Vue.component('B',{
        data(){
            return {count: 0}
        },
        template:`
            <div>{{count}}</div>
        `,
        // created当事件被创建出来时,就给于调用
        created() {
            // $on 绑定一个事件(指向当前的对象)
            bus.$on('add',(n)=>{
                this.count+=n;
            })
        }
    })

    Vue.component('A',{
        data(){
            return {} 
        },
        template:`
            <div>
                <button @click="handleClick">加入购物车</button>
            </div>
        `,
        methods: {
            handleClick(){
                // 触发绑定的函数 $emit 触发一个事件
                bus.$emit('add',1);
            }
        }
    })

    const App = {
        data(){
            return {}
        },
        template: `
            <div>
                <A></A>
                <B></B>
            </div>
        `
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

2.provideinject

父组件provide来提供变量,然后在子组件中通过inject来注入变量,无论组件嵌套多深,都可以取到值。
<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    Vue.component('B',{
        data(){
            return {count: 0}
        },
        template:`
            <div>{{msg}}</div>
        `,
        // 接收数据
        inject:['msg'],
        created() {
            console.log(this.msg)
        }
    })

    Vue.component('A',{
        data(){
            return {}
        },
        created() {
            // 获取父组件的数据
            console.log(this.$parent.title)
            // 获取子组件的数据(可加[]因为可能不止一个子组件)
            console.log(this.$children)
            // 可查看所有的$值
            console.log(this)
        },
        template:`
            <div>
                <B></B>
            </div>
        `,
    })

    const App = {
        data(){
            return {title:'老爹'}
        },
        template: `
            <div>
                <A></A>
            </div>
        `,
        // 注入数据
        provide(){
            return {
                msg: '老爹的数据'
            }
        }
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

3 插槽

1 匿名插槽

<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    Vue.component('MBtn',{
        template: `
            <button>
                <slot></slot>
            </button>
        `
    })

    const App = {
        data() {
            return {title: '老爹'}
        },
        template: `
            <div>
                <MBtn><a href="">登录</a></MBtn>
                <MBtn>注册</MBtn>
            </div>
        `,
    }


    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

2 具名插槽

<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    // 只要匹配到slot标签中的值 template中的内容就会被插入到这个槽中
    Vue.component('MBtn',{
        template: `
            <button>
                <slot name="login"></slot>
                <slot name="submit"></slot>
            </button>
        `
    })

    const App = {
        data() {
            return {title: '老爹'}
        },
        template: `
            <div>
                <MBtn>
                    <template slot="login">
                        <a href="">登录</a>
                    </template>
                </MBtn>
                <MBtn>
                    <template slot="submit">
                        提交
                    </template>
                </MBtn>
            </div>
        `,
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

3 作用域插槽

需求假设:已经开发了一个代办事项列表的组件,很多模块都在用
1.之前的数据格式和引用接口不变,正常显示
2.新功能模块增加对勾

<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    const todoList = {
        data(){
            return {}
        },
        props: {
            todos: Array,
            defaultValue: [],
        },
        template: `
            <ul>
                <li v-for="item in todos" :key="item.id">
                    <slot :itemValue="item"></slot>
                    {{item.title}}
                </li>
            </ul>
        `
    }

    const App = {
        data() {
            return {
                todoList: [{
                    title: '大哥你好吗',
                    isComplate: true,
                    id: 1
                },
                {
                    title: '小弟我还行',
                    isComplate: false,
                    id: 2
                },
                {
                    title: '你在干什么',
                    isComplate: false,
                    id: 3
                },
                {
                    title: '抽烟喝酒烫头',
                    isComplate: true,
                    id: 4
                }]
            }
        },
        components: {
            todoList,
        },
        template: `
            <todoList :todos="todoList">
                <template v-slot="data">
                    <input type="checkbox" v-model="data.itemValue.isComplate">
                </template>
            </todoList>
        `
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

4 生命周期钩子

所有生命周期钩子的 this 上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。因为箭头函数绑定了父级上下文,所以 this 不会指向预期的组件实例,并且this.fetchTodos 将会是 undefined。

生命周期下的方法:

beforeCreate  # 在组件创建之前
created  # 在组件创建之后

beforeMount  # 在挂载之前(装载数据在DOM之前)
mounted  # 在挂载之后(装载数据在DOM之后)

beforeUpdate  # 在更新页面之前
updated  # 在更新页面之后

activated  # 借助keep-alive组件  激活(标签的创建销毁)
deactivated  # 借助keep-alive组件  停用(标签的创建销毁)

beforeDestroy  # 销毁之前(标签的创建销毁)
destroyed  # 销毁之后(标签的创建销毁)

errorCaptured

示例:

<style>
    .active{color: red}
</style>
<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    Vue.component('Test',{
        data(){
            return {msg: '大帅逼'}
        },
        methods: {
            handleClick(){
                this.msg="alex";
                this.isRed=true;
            }
        },
        template: `
            <div>
                <button @click="handleClick">改变</button>
                <h3 :class="{active: isRed}">{{msg}}</h3>
            </div>
        `,
        beforeCreate() {
            console.log('组件创建之前',this.$data);
        },
        created() {
            // 非常重要的事情,在此时发送ajax 请求后端数据
            console.log('组件创建之前',this.$data);
        },
        beforeMount() {
            // 即将挂载,还没有挂载
            console.log('组件创建之前',document.getElementById('app'));
        },
        mounted() {
            // 挂载完成 也可发送ajax
            console.log('DOM挂载完成',document.getElementById('app'));
        },
        beforeUpdate() {
            // 获取页面更新之前的DOM
            console.log('更新之前的DOM',document.getElementById('app').innerHTML);
        },
        updated() {
            // 获取页面最新的DOM
            console.log('更新之后的DOM',document.getElementById('app').innerHTML);
        },
        beforeDestroy() {
            // 销毁之前方法
            console.log('销毁之前')
        },
        destroyed() {
            console.log('销毁完成')
        },
        activated() {
            console.log('组件被激活了')
        },
        deactivated() {
            console.log('组件被禁用了')
        }

    })

    const App = {
        data(){
            return {isShow: true}
        },
        components: {},
        methods: {
            clickHandle(){
                this.isShow = !this.isShow;
            }
        },
        template: `
            <div>
                <keep-alive>
                    <Test v-if="isShow"></Test>
                </keep-alive>
                <button @click="clickHandle">改变生死</button>
            </div>
        `
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

5 异步组件加载

把相应的js文件,引入到Vue组件中。

Test.js文件:

export default {
    data() {
        return {msg: '大帅逼'}
    },
    template: `
        <h3>{{msg}}</h3>
    `
}

html文件:

<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script type="module">

    const App = {
        data(){
            return {isShow: false}
        },
        methods: {
            asyncLoad(){
                this.isShow = !this.isShow;
            }
        },
        components: {
            // 加载异步组件(需要一个函数加载) 
            Test: ()=>import('./Test.js')
        },
        template: `
            <div>
                <button @click="asyncLoad">异步加载</button>
                <Test v-if="isShow"></Test>
            </div>
        `
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })
</script>

6 refs的使用

获取DOM节点的操作。

<div id="app">
    <App></App>
</div>

<script src="../vue.js"></script>
<script>
    Vue.component('Test',{
        data(){
            return {msg: '大帅逼'}
        },
        template: `
            <div>
                <h3>{{msg}}</h3>
            </div>
        `
    })

    const App ={
        data(){
            return {}
        },
        mounted(){
            // 1.如果给标签加ref,获取的就是真实的DOM节点
            console.log(this.$refs.btn);
            // 2.如果给子组件添加ref,获取的是当前子组件对象
            console.log(this.$refs.test);
            // 加载页面自动获取焦点
            this.$refs.input.focus();

        },
        components: {},
        template: `
            <div>
                <Test ref="test"></Test>
                <input type="text" ref="input">
                <button ref="btn">改变</button>
            </div>
        `
    }

    new Vue({
        el: '#app',
        data: {},
        components: {
            App,
        }
    })


</script>

补充:

$refs  获取组件内的元素
$parent  获取当前组件的父组件
$children  获取当前组件的子组件
$root  获取New Vue的实例化对象
$el  获取组件对象的DOM元素

7 nextTick的使用

获取更新之后的DOM添加事件的特殊情况

$nextTick 是在下次DOM更新循环结束之后执行的延迟回调,在修改数据之后使用该方法,则可以在回调中获取更新之后的DOM
  • 简单的使用:

    <div id="app">
        <h3>{{message}}</h3>
    </div>
    
    <script src="../vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {message: '大帅逼'}
        })
    
        vm.message = 'new Message';
        // 当vm中的数据被重新赋值时,vm实例不会重新渲染
        // console.log(vm.$el.textContent);
        // 通过nextTick可获取最终渲染后的数据(为了数据变化之后等待vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick在当前的回调函数中能获取最新的DOM)
        Vue.nextTick(()=>{
            console.log(vm.$el.textContent);
        })
    
    </script>
    
    <div id="app">
    </div>
    
    <script src="../vue.js"></script>
    <script>
        const App = {
            data(){
                return{
                    isShow: false,
                }
            },
            template:`
    <div>
    <input type='text' v-if='isShow' ref='fos'/>
        </div>
    `,
            mounted(){
                this.isShow = true;
                console.log(this.$refs.fos);
                // focus()
                this.$nextTick(function(){
                    this.$refs.fos.focus();
                })
    
            }
        }
    
        new Vue({
            el: '#app',
            data(){return{}},
            template:`<App/>`,
            components:{App,}
        })
    </script>
    
  • nextTick的应用:

    需求:
    在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,
    然后我在接口一返回数据就展示了这个浮层组件,展示的同时,
    上报一些数据给后台(这些数据是父组件从接口拿的)
    这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,
    这些数据还未更新到组件上去,上报失败
    
    <div id="app">
        <App></App>
    </div>
    
    <script src="../vue.js"></script>
    <script>
        const Pop = {
            data(){
                return {isShow: false}
            },
            props: {
                name: {
                    type: String,
                    default: ''
                }
            },
            template: `
                <div v-if="isShow">
                    {{name}}
                </div>
            `,
            methods: {
                show(){
                    this.isShow = true; // 弹窗组件展示
                    console.log(this.name);
    
                }
            }
        }
    
        const App = {
            data(){
                return {name: ''}
            },
            created(){
                // 假设从后端获取的数据
                setTimeout(()=>{
                    // 数据更新
                    this.name = '大帅逼';
                    this.$nextTick(()=>{
                        this.$refs.pop.show();
                    })
                }, 1000)
            },
            components: {Pop,},
            template: `<pop ref="pop" :name="name"></pop>`
        }
    
        const vm = new Vue({
            el: '#app',
            components: {App,}
        })
    </script>
    

8 对象变更检测注意事项

由于Vue不能检测对象属性的添加和删除,解决方法:

<div id="app">
    <h3>{{user.name}},{{user.age}},{{user.phone}}</h3>
    <button @click="handlerAdd">添加属性</button>
</div>

<script src="../vue.js"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            user: {}
        },
        methods:{
            handlerAdd(){
                // Vue.set(object,key,value)添加响应式属性
                // this.user.age = 20;  // 错误添加
                // this.$set(this.user,'age',20);  // 添加一次
                this.user = Object.assign({},this.user,{
                    age: 20,
                    phone: 12306
                })  // 添加多个响应式属性
            }
        },
        created(){
            // 假设从后端获取的数据
            setTimeout(()=>{
                this.user = {name: "张三"}
            },1250)
        }
    })
</script>

9 mixin混入技术

mixin主要是来分发Vue组件中的可复用功能

<div id="app">
    {{msg}}
</div>

<script src="../vue.js"></script>
<script>
    // mixin主要是来分发Vue组件中的可复用功能
    const MyMixin = {
        data(){
            return {msg: '123'}
        },
        created(){
            this.sayHello();
        },
        methods: {
            sayHello(){
                console.log('hello mixin');
            }
        }
    }

    new Vue({
        el: '#app',
        data: {title: '大帅逼'},
        created() {console.log(111);},
        // 将MyMixin中的方法和属性混入到Vue实例中
        mixins: [MyMixin],
    })


</script>
  • 应用:

    假设:有模态框组件,提示框组件。他们看起来不一样,用法不一样,但是逻辑是一样的(切换bool值)

<div id="app">

</div>

<script src="../vue.js"></script>
<script>
    /*
    全局mixin的创建:使用时要小心,因为每个组件实例创建,它都谁被调用
    Vue.mixin({...})
    */

    // 局部的mixin
    const toggleShow = {
        data(){
            return {isShow: false}
        },
        methods: {
            toggleShow(){
                this.isShow = !this.isShow;
            }
        }
    }

    // 模态框组件
    const Model = {
        template: `
            <div v-if="isShow"><h3>模态框组件</h3></div>
        `,
        // 局部的mixin
        mixin: [toggleShow],
    }

    // 提示框组件
    const ToolTip = {
        template: `
            <div v-if="isShow"><h2>提示框组件</h2></div>
        `,
        // 局部的mixin
        mixin: [toggleShow],
    }


    new Vue({
        el: '#app',
        template: `
            <div>
                <button @click="handlerModel">模态框</button>
                <button @click="handlerToolTip">提示框</button>
                <Model ref="model"></Model>
                <ToolTip ref="toolTip"></ToolTip>
            </div>
        `,
        components: {Model,ToolTip},
        methods: {
            handlerModel(){
                this.$refs.model.toggleShow();
            },
            handlerToolTip(){
                this.$refs.toolTip.toggleShow();
            }
        }
    })
</script>

1 组件分类

  • 通用组件

    基础组件,大部分U|都是这种组件,比如表单布局弹窗等

  • 业务组件
    与需求挂钩,会被复用,比如抽奖,摇一摇等

  • 页面组件
    每个页面都是一个组件,不会复用

2 使用第三方组件

2-1 element-ui

官方网站:

https://element.eleme.cn/#/zh-CN/component/installation

比如vue最流行的element,就是典型的通用组件,执行npm install element-ui 安装

  • 完整导入
// 在mian.js中
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI)
  • 按需导入

    借助babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

    首先,安装babel-plugin-component:

    npm install babel-plugin-component -D
    

    然后,将.babelrc修改为:

    {
    "presets": [["es2015",{"modules": false}]],
    "plugins": [
    	[
    	"component",
    		{
    		"libraryName": "element-ui",
    		"styleLibraryName": "theme-chalk"
    		}
    	]
    ]
    }
    

    接下来,如果你只希望引入部分组件,比如Button和Select,那么需要在main.js中写入以下内容:

    import Vue from 'vue';
    import {Button,Select} from 'element-ui';
    import App from './App.vre';
    
    Vue.component(Button.name, Button);
    Vue.component(Button.name, Select);
    /* 或者写 
    Vue.use(Button)
    Vue.use(Select)
    */
    
    new Vue({
        el: '#app',
        render: h=>h(App)
    });
    
  • vue-cli中可以使用vue add element安装

    安装之前注意提前提交当前工作内容,脚手架会覆盖若干文件

    image

    发现项目发生了变化,打开App.vuectrl+z撤回

    image

  • 方式2

    npm install element-ui
    手动创建文件和配置
    
    在终端输入,可快速生成相应文件
    vue add element
    

    image

    1 Fully import :完全引入 Import on demand :按需引入

    2 是否会写css的变量:No

    3 开发语言:zh-CN


2-2 axios


3 自定义组件

  • FormElement.vue

    <template>
      <div>
        <h3>element表单</h3>
        <!-- 自己的组件 -->
        {{ruleForm}}
        <m-form-item label="用户名" prop="name">
          <m-input v-model="ruleForm.name"></m-input>
        </m-form-item>
        <m-form-item label="密码" prop="pwd">
          <m-input type="password" v-model="ruleForm.pwd"></m-input>
        </m-form-item>
      </div>
    </template>
    
    <script>
    import MInput from './Input.vue'
    import MFormItem from './FormItem.vue'
    export default {
      name: "FormElement",
      data(){
        return{
          ruleForm: {
            name: '',
            pwd: '',
          },
        }
      },
      components: {
        MInput,
        MFormItem
      }
    }
    </script>
    
    <style scoped>
    
    </style>
    
  • input组件

    <template>
       <div>
           <input :type="type" :value="inputVal" @input="handleInput">
       </div>
    </template>
    
    <script>
    export default {
        name: 'InputData',
        props: {
            type: {
                type: String,
                default: 'text'
            },
            value: {
                type: String,
                default: '',
            }
        },
        data() {
            return {
                inputVal: this.value
            }
        },
        methods: {
            handleInput(e){
                // 赋值
                // 实现双向数据绑定
                this.inputVal = e.target.value;
                this.$emit('input', this.inputVal);
                // 通知父组件值的更新
            }
        },
    }
    </script>
    
    <style>
    
    </style>
    
  • FormItem组件

    <template>
      <div>
          <label v-if="label">{{label}}</label>
          <!-- 槽的作用:会把子组件插入到slot标签中 -->
          <slot></slot>
          <!-- 显示错误的校验信息 -->
          <p v-if="valdateStatus==='error'" class="error">{{errorMessage}}</p>
      </div>
    </template>
    
    <script>
    // 0.label和prop实行绑定
    // 1.获取当前输入框的规则
    // 2.如果输入框rule不匹配显示错误信息
    // 3.Input组件中涌入输入内容时,通知FormItem做校验
    // 4.使用async-validator做校验
    export default {
        data() {
            return {
                valdateStatus: '',  // 校验的状态
                errorMessage: '', // 显示错误信息
            }
        },
        props: {
            label: {
                type: String,
                default: ''
            },
            prop: {
                type: String,
                default: ''
            }
        }
    }
    </script>
    
    <style>
    .error{
        color: red;
    }
    </style>
    
  • From组件

    <template>
      <div>
          <slot></slot>
      </div>
    </template>
    
    <script>
    export default {
        name: "FormData",
        provide(){
            return {
                form: this
            }
        },
        props: {
            model: {
                type: Object,
                required: true
            },
            rules: {
                type: Object
            }
        }
    }
    </script>
    
    <style>
    
    </style>
    
posted @ 2022-09-26 14:42  角角边  Views(56)  Comments(0)    收藏  举报