Vue工程化开发 & 脚手架 Vue CLI & 组件进阶 & 组件通信

136 阅读3分钟

工程化开发模式

基于构建工具(例如:webpackviterollup等)的环境中开发 Vue

image.png Vue CLIVue官方提供的一个全局命令工具。可以快速创建一个集成了webpack配置的用于开发Vue的标准化基础架子

  • 使用步骤
    • 安装vue脚手架:npm i @vue/cli -gyarn global add @vue/cli
    • 创建项目:vue create project-name(项目名)
    • 启动项目:npm run serveyarn serve (package.json文件中查找运行命令)

image.png 组件化开发:一个页面可以拆分成一个个的组件,每个组件都有着自己独立的结构、样式、行为

好处:便于维护,利于复用 → 提升开发效率

<template> // 结构
  <div class="app"></div>
</template>

<script> // 行为
export default {
    
    data() {
        return {}
    }
}
</script>

<style lang="less" scoped></style> // 样式
// 开启less 样式预处理。需要装对应的包 npm i less less-loader

// scoped 解决样式冲突,
// 添加 scoped 后,style内的样式只作用于当前.vue文件
// 不添加,则默认样式作用于全局

data

一个组件的data选项必须是一个函数 → 保证每个组件实例,维护 独立 的一份数据对象。

原理:每次创建新的组件实例,都会新执行一次 data 函数,得到一个新的对象

scoped原理:

1、当前组件内标签都被添加 data-v-hash 值的属性

2、css选择器都被添加 [data-v-hash]值 的属性选择器

最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到 image.png

image.png

组件注册

  • 局部注册:只能在注册的组件内使用
    • 创建.vue文件
    • 在使用的组件内导入并注册
    //使用
    <组件对象 />
    
    // 导入
    import 组件对象 from '文件路径'
    
    export default {
        // 注册
        components: { 组件对象 }
    }
    
  • 全局注册:所有组件内都能使用
    • 创建.vue文件
    • main.js 中进行全局注册
    // 导入
    import 组件对象1 from '文件路径'
    import 组件对象2 from '文件路径'
    
    // 调用 Vue.component 进行全局注册
    Vue.component('组件名1', 组件对象1)
    Vue.component('组件名2', 组件对象2)
    

组件通信

  • 概念:指 组件与组件 之间的数据传递
    • 组件的数据是 独立 的,无法直接访问其他组件的数据
    • 想用其他组件的数据 → 组件通信

组件关系分类

  • 父子关系
  • 非父子关系

组件通信解决方案

方案一: props$emit

适用于 父子组件间 的通信 image.png

父组件 通过 props 将数据传递给 子组件。注意: props只读,不可以被修改(单向数据流)

子组件 利用 $emit 通知 父组件修改更新

单向数据流:父级prop的数据更新,会向下流动,影响子组件。

props定义:

组件上 注册的一些 自定义属性

props特点:

1、可以传递任意数量prop

2、可以传递任意类型prop

props接收语法:

// 写法1:
props: [ 'msg' ]

// 写法2: 添加数据类型
props: { msg: String }

// 写法3:添加数据校验
props: {
   校验的属性名:{
      type: 类型,
      required: true, // 是否必填
      default: 默认值,
      validator (value) {
        // 自定义校验逻辑
        return 是否通过校验
      }
   }
}

props传值 image.png

$emit发送消息通知给父组件,父组件通过监听事件,提供对应的处理函数,来更新数据

image.png

方案二、event bus 事件总线

适用于 非父子组件间,简易消息传递(复杂场景 → vuex

1、创建一个都能访问到的事件总线(空Vue实例)→ `utils/EventBus.js`
import Vue from 'vue';
const Bus = new Vue();
export default Bus;

2、组件A(接收方),监听`Bus实例`的事件
created () {
    Bus.$on('sendMsg', (msg) => {
        this.msg = msg;
    })
}

3、组件B(发送方),触发`Bus实例`的事件
Bus.$emit('sendMsg', '这是一个消息')

image.png

方案三、provide & inject

适用于 跨层级组件间 的通信。

子组件不限嵌套层级,只要调用 inject 即可注入 provide 中提供的数据。

1、 父组件 `provide` 提供数据
export default {
    provide () {
        return {
            // 普通类型(非响应式)
            color: this.color,
            // 复杂类型(响应式)
            userInfo: this.userInfo
        }
    }
}
2、子/孙组件 `inject`注入取值使用
export default {
    inject: ['color', 'userInfo'],
    created() {
        console.log(this.color, this.userInfo)
    }
}

方案四、vuex

通用解决方案,适合复杂业务场景,包括 兄弟组件、跨级组件间的通信

v-model 原理

  • 原理:本质是一个语法糖。例如:在输入框上,就是:value属性@input事件的 合写

因此,父子组件中传值时,可以通过 v-model 来简化代码,给组件直接绑定数据。

相当于,给子组件传递了 :value@input

本质上,实现了 子组件 与 父组件数据 的双向绑定

  • 作用:提供数据的双向绑定
    • 数据变,视图跟着变:value
    • 视图变,数据跟着变@input
    <template>
        <div id='app'>
            <input type='text' v-model='msg' />
            
            <input type='text' :value='msg' @input='msg = $event.target.value' />
        </div>
    </template>
    

    注意: $event 用于模板中,用于获取事件的形参。

.sync 修饰符

  • 作用:可以实现 子组件 与 父组件数据 的双向绑定, 简化代码
  • 特点: 与v-model 相比,prop属性名可以自定义,非固定的 value
  • 本质:就是 :属性名@update:属性名 的合写
// 父组件
<SonComponent :visible.sycn='isShow' />

// 等价于
<SonComponent :visible='isShow' @update:visible=“isShow = $event” />


// 子组件
props: {
    visible: Boolean
},

this.$emit('update:visible', false)

ref & $refs

  • 作用:利用ref$refs可以获取dom元素组件实例
    • 用于普通 DOM 元素上,引用的是 DOM元素
    <div ref='elementRef'>获取dom元素</div>
    
    // 使用
    this.$refs.elementRef
    
    • 用在组件上,引用指向 组件实例,可以通过实例直接调用组件的方法或访问数据
    <SonComponent ref='componentRef' />
    
    // 使用
    this.$refs.componentRef.组件的方法()
    

插槽

  • 默认插槽

    作用:让组件内部的一些结构支持自定义

    场景:当组件内某一部分结构不确定,想要自定义时,可以用 插槽slot 占位封装

    语法

    1、组件内需要定制的结构部分,改用`<slot></slot>`占位
    
    <template>
        <div class='model-component'>
            <div>标题</div>
            <slot></slot>
        </div>
    </template>
    
    2、使用组件时,`<ModelComponent></ModelComponent>`标签内部 传入的`具体结构`替换 `<slot>`
    

    插槽-后备内容:也叫插槽的默认值

    封装组件时,可以为slot插槽提供默认内容

    语法: 在<slot>标签内,放置内容,作为默认显示内容

    效果: 1、使用组件时,不传内容,则显示slot中的默认内容

    2、使用组件时,传内容,则slot标签整体被替换

  • 具名插槽

    作用:一个组件内有多处结构,需要外部传入标签,进行定制。

    语法

    1、多个slot使用name属性区分名字

    2、template配合v-slot:名字 来分发对应标签

    3、v-slot:插槽名 可以简化为 #插槽名

    1、区分名字
    <template>
        <div>
           <div class='model-header'>
               <slot name='header'></slot>
           </div>
           <div class='model-content'>
               <slot name='content'></slot>
           </div>
           <div class='model-footer'>
               <slot name='footer'></slot>
           </div>
        </div>
    </template>
    
    2、使用
    <ModelComponent>
        <template v-slot:header>
            替换header 插槽
        </template>
        <template v-slot:content>
            替换content 插槽
        </template>
        <template #footer>
            替换 footer 插槽
            <button>按钮</button>
        </template>
    </ModelComponent>
    
  • 作用域插槽

    作用:定义slot插槽时,可以给插槽绑定数据,将来使用组件时可以用

    语法

    1、给 slot标签,以添加属性的方式传值。

    <slot :id="item.id" msg='测试文本'></slot>
    

    2、所有添加的属性,都会被收集到一个对象中。

    obj = { id: 3, msg: '测试文本' }
    

    3、在template 中,通过 #插槽名 = "obj" 接收,默认插槽名为 default

    <ModelComponent :list="list">
        <template #default="obj">
            <button @click='del(obj.id)'>删除</button>
        </template>
    </ModelComponent>
    

Vue异步更新 & $nextTick

需求:编辑标题,编辑框自动聚焦
1、点击编辑,显示编辑框
2、让编辑框,`立刻自动获取焦点``autofocus``safari`浏览器中存在兼容问题)

// 错误实现方式: 编辑框显示后,并没有自动获取焦点
this.isShowEdit = true;
this.$refs.inp.focus()
原因 → `Vue是异步更新DOM`

// 正确解决方式
this.$nextTick(() => {
    this.$refs.inp.focus()
})

image.png

$nextTick

等DOM更新后,才会触发执行此方法中的函数体。即:想要在DOM 更新完成之后做某件事,可以使用 $nextTick

  • 语法this.$nextTick(函数体)

  • 应用场景

    • created() 钩子函数中进行DOM操作,一定要放在 $nextTick 中。

    created() 钩子函数执行的时候,DOM并未进行任何渲染

    • 数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进$nextTick的函数体中。