Web前端笔记-14、Vue-工程化开发、组件化、组件注册、组件通信子传父父传子、props校验、EventBus、依赖注入、ref refs、sync修饰符、异步更新 & \$nextTick

工程化开发

真正开发使用。

核心包传统开发模式:基于 html / css /js 文件,直接引入核心包,开发 Vue。

工程化开发模式:基于构建工具(例如:webpack)的环境中开发 Vue。

工程化开发和脚手架Vue CLI

Vue CLI是 Vue 官方提供的一个全局命令工具。

可以帮助我们快速创建一个开发 Vue 项目的标准化基础架子。【集成了 webpack 配置】

好处:

  1. 开箱即用,零配置
  2. 内置 babel 等工具
  3. 标准化

使用步骤:

  1. 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
  2. 查看vue/cli版本: vue --version
  3. 创建项目架子:vue create project-name -m npm(项目名不能使用中文)
  4. 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)
  5. 安装插件:vue-Official ,Vue3 snippets

NSbeJWFJroR-F1JOMObfazz9d2OfChRHuSbdAu0Gwvk=

main.js入口,将组件放入html的#app中。

一些细节

组件.vue起名和3个组成部分。

起名规范:组件名必须是多词格式。小驼峰、大驼峰或中横线格式。

补充:文件名不符合多词格式,可以在export default中加属性name:'多词'即可。

template要求:Vue2有且只能有一个根元素。Vue3可以。

script要求:vue代码都要写到default对象里面,包括methods, computed, watch。

注意:data的写法发生变化。

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

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

<script>
export default {
  data() {
    return {}
  },
  methods: {},
  computed: {},
  watch: {},
  created() {},
  mounted() {},
}
</script>

如何支持less:style的lang属性值改为"less" 加上scoped属性。安装包:npm i less less-loader -D

组件化

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

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

组件分类:普通组件(层层调用,所有组件都要最终放入根组件)、根组件(App.vue)。

形成组件树。

所有普通组件放入components中。

组件注册

普通组件的注册使用

组件注册的两种方式:

  • 局部注册:只能在注册的组件内使用
  1. 创建.vue 文件(三个组成部分)
  2. 在使用的组件内script中导入并注册
<template>
  <div class="app">
    <MyHeader></MyHeader>
    <TestMain></TestMain>
    <MyFooter></MyFooter>
  </div>
</template>
<script>
/**
 * 局部注册组件
 * 1. 导入
 * 2. 注册组件 -- 注册成html标签(标签就代表组件了)
 * 3. 页面中使用
 */
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import TestMain from './components/MyMain.vue'
export default {
  // 注册组件
  components: {
    // 标签名: 导入的变量名
    MyHeader: MyHeader,
    TestMain: TestMain,
    MyFooter: MyFooter,
  },
  data() {
    return {}
  },
}
</script>
  • 全局注册:所有组件内都能使用,适用于很多很多组件都要用
  1. 创建.vue 文件(三个组成部分)
  2. main.js 中进行全局注册
// 全局注册组件
import TestButton from './components/TestButton.vue'
Vue.component('TestButton', TestButton)

scoped解决样式冲突问题

默认情况:写在组件中的样式会 全局生效 — 因此很容易造成多个组件之间的样式冲突问题。

  1. 全局样式:默认组件中的样式会作用到全局
  2. 局部样式:可以给组件加上scoped 属性,可以让样式只作用于当前组件

scoped原理?

  1. 当前组件内标签都被添加 data-v-hash值 的属性
  2. css选择器都被添加[data-v-hash值]的属性选择器

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

组件通信

组件通信,就是指组件与组件之间的数据传递。

  • 组件的数据是独立的,无法直接访问其他组件的数据。
  • 想用其他组件的数据,使用组件通信。

组件的关系

组件关系分为:父子关系和非父子关系。

父子组件之间的数据传递(通信)

  • 父传子
<template>
  <div class="App">
    <!-- 
    父传子语法:
    父:通过自定义属性的方式,向子组件传递数据
    子:通过props接收传递来的数据
    -->
    <SingleTest v-for="item in goodsList" :key="item.id" :obj="item" />
  </div>
</template>
<template>
  <div class="good">
    <h3>{{ obj.name }}</h3>
    <p>商品单价:{{ obj.price }}</p>
    <p>商品描述:{{ obj.info }}</p>
  </div>
</template>
<script>
export default {
  props: ['obj'],
}
</script>
  • 子传父

data与prop 共同点:都可以给组件提供数据。

区别:

  • data 的数据是自己的 - 随便改
  • prop 的数据是外部的 - 子只使用,不能直接改父数据,要遵循单向数据流。改就要子通知父,父组件自己去改数据。

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

谁的数据谁负责。

语法:子如何通知父

  1. 子使用内置方法通知:$emit('自定义事件名', 传的数据)
  2. 父在引用子组件的标签上自定义事件:@自定义事件名="方法"
  3. 父在methods中定义方法并处理数据:方法(val){//val就是接收到的数据}
<button @click="$emit('down', obj.id)">砍一刀</button>
<SingleTest
  v-for="item in goodsList"
  :key="item.id"
  :obj="item"
  @down="subAlittle"
/>
methods: {
  subAlittle(val) {
    let row = this.goodsList.find((item) => item.id === val)
    row.price -= 0.1
  },
},

props校验

父传子,父组件中子标签自定义属性,子组件中props数组接收使用。

但如果需要对props进行校验,就需要写成对象格式。

写成对象是因为要写配置项进行校验。

required: true -------- 表示父组件必须传递这个值,没传就会报错。

type: String | Number | Object | Array | BooLean | Function

type: [String, Number]-------类型校验

default: ''-----默认值,默认值如果是对象或数组,要写成函数格式,return默认值

<script>
export default {
  name: 'SonPage',
  props: {
    age: {
      required: true,
    },
    list: {
      default: () => {
        return []
      },
    },
    visible: {
      type: Boolean,
    },
    say: Function, // 只校验类型
  },
}
</script>

v-model原理

v-model 可以拆分成 :value + @input

<input type="text" :value="uname" @input="changeValue" />

需求:实现输入框组件的v-model

<MyInput></MyInput>

如果直接使用v-model是不会双向同步的。

v-model 必须拆分成 :value + @input

:value="uname"相当于父传子的第一步:定义属性。然后在子中props接收value,应用:value="value",完成父传子。

@input相当于子传父的最后一步。在子中添加事件@input="$emit('input', $event.target.value)",子给父的input发通知,事件名为input。父的@input="changeName"的changeName接收到$event.target.value,赋值给this.uname完成子传父。

然后再把组件中的:value + @input换回成v-model="uname"

组件的v-model需要子组件的配合。子组件需要接收value属性,并触发input事件。

<template>
  <div>
    <h3>需求1:不用v-model实现双向绑定</h3>
    <input type="text" :value="uname" @input="changeValue" />
    <hr />

    <h3>需求2:实现输入框组件的v-model</h3>
    <!-- <MyInput :value="uname" @input="changeName"></MyInput> -->
    <MyInput v-model="uname"></MyInput>
    <hr />

    <h3>需求3:实现下拉框组件的v-model</h3>
    <MySelect v-model="address"></MySelect>
  </div>
</template>

<script>
import MyInput from './components/MyInput.vue'
import MySelect from './components/MySelect.vue'
export default {
  data() {
    return {
      uname: 'zhangsan111',
      address: '上海',
    }
  },
  components: {
    MyInput,
    MySelect,
  },
  methods: {
    changeValue(e) {
      this.uname = e.target.value
      // console.log(e.target.value)
    },
    // changeName(val) {
    //   this.uname = val
    // },
    // changeAddress(val) {
    //   this.address = val
    // },
  },
}
</script>

<style lang="less" scoped></style>

<template>
  <select :value="value" @change="$emit('input', $event.target.value)">
    <option value="北京">北京市</option>
    <option value="上海">上海市</option>
    <option value="广州">广州市</option>
    <option value="深圳">深圳市</option>
  </select>
</template>

<script>
export default {
  name: 'MySelect',
  props: ['value'],
}
</script>

<style lang="less" scoped></style>

sync修饰符

还是父传子子传父。

:属性.sync="变量"

等同于:属性="变量" + @update:属性="xxx"

<template>
  <div>
    <button @click="visible = true">删除</button>
    <MyDialog :visible.sync="visible"></MyDialog>
  </div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data() {
    return {
      visible: false,
    }
  },
  components: {
    MyDialog,
  },
}
</script>

<style>
body {
  background-color: #b3b3b3;
}
</style>

<template>
  <div class="dialog" v-show="visible">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close" @click="$emit('update:visible', false)">✖️</span>
    </div>
    <div class="dialog-content">我是文本内容</div>
    <div class="dialog-footer">
      <button>取消</button>
      <button>确认</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {}
  },
  props: ['visible'],
}
</script>

<style scoped>
* {
  margin: 0;
  padding: 0;
}
.dialog {
  width: 470px;
  height: 230px;
  padding: 0 25px;
  background-color: #ffffff;
  margin: 40px;
  border-radius: 5px;
}
.dialog-header {
  height: 70px;
  line-height: 70px;
  font-size: 20px;
  border-bottom: 1px solid #ccc;
  position: relative;
}
.dialog-header .close {
  position: absolute;
  right: 0px;
  top: 0px;
  cursor: pointer;
}
.dialog-content {
  height: 80px;
  font-size: 18px;
  padding: 15px 0;
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
}
.dialog-footer button {
  width: 65px;
  height: 35px;
  background-color: #ffffff;
  border: 1px solid #e1e3e9;
  cursor: pointer;
  outline: none;
  margin-left: 10px;
  border-radius: 3px;
}
.dialog-footer button:last-child {
  background-color: #007acc;
  color: #fff;
}
</style>

组件通信-EventBus

了解。非父子组件通信。

1.作用

非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)

2.步骤

  1. 创建一个都能访问的事件总线 (空Vue实例)
    import Vue from 'vue'
    const Bus = new Vue()
    export default Bus
    
  2. A组件(接受方),监听Bus的 $on事件
    created () {
      Bus.$on('sendMsg', (msg) => {
        this.msg = msg
      })
    }
    
  3. B组件(发送方),触发Bus的$emit事件
    Bus.$emit('sendMsg', '这是一个消息')
    

nWyhiQ_RxysvpE_t7O9IHLtDBrl9K7wACh5gb52QKTc=

3.代码示例

<EventBus.js>

import Vue from 'vue'
const Bus  =  new Vue()
export default Bus

BaseA.vue(接受方)

<template>
  <div class="base-a">
    我是A组件(接收方)
    <p>{{msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
}
</script>

<style scoped>
.base-a {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

BaseB.vue(发送方)

<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button>发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
}
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

<App.vue>

<template>
  <div class="app">
    <BaseA></BaseA>
    <BaseB></BaseB> 
  </div>
</template>

<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue' 
export default {
  components:{
    BaseA,
    BaseB
  }
}
</script>

<style>

</style>

组件通信-依赖注入

了解。非父子间组件通信。

1.作用

跨层级共享数据

2.场景

Wk5Sh9kU2h7s-jGfgyNcjWelS93PrWeeZh-u_zHtm6I=

3.语法

  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)
  }
}

4.注意

  • provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
  • 子/孙组件通过inject获取的数据,不能在自身组件内修改

ref与refs

找页面元素、组件。

利用ref$refs可以用于 获取 dom 元素 或 组件实例

特点:查找范围 → 当前组件内(更精确稳定 qs找整个页面中的)

找到组件可以调用组件中的方法 this.$refs.test.方法()

相同的ref只找最后一个,但v-for循环出来的都会找到。

  • 首先,给要获取的盒子添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
  • 然后,获取时通过 $refs获取 this.$refs.chartRef 获取
mounted () {
 console.log(this.$refs.chartRef)
}
<MyForm ref="myform"></MyForm
<button @click="$refs.myform.resetForm()">重置</button>

异步更新 & $nextTick

编辑标题, 编辑框自动聚焦

  1. 点击编辑,显示编辑框
  2. 让编辑框,立刻获取焦点
// 显示输入框
this.isShowEdit = true  
// 获取焦点
this.$refs.inp.focus()  

"显示之后",立刻获取焦点是不能成功的!

原因:Vue 是异步更新DOM (提升性能)

$nextTick:等 DOM更新后,才会触发执行此方法里的函数体

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

this.$nextTick(() => {
  this.$refs.inp.focus()
})

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

posted @ 2025-05-31 22:24  subeipo  阅读(20)  评论(0)    收藏  举报