Vue从入门到实战Day04

一、组件的三大组成部分(结构/样式/逻辑)

1. scoped样式冲突

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

1. 全局样式:默认组件中的样式会作用到全局

2. 局部样式:可以给组件加上scoped属性,可以让样式只作用于当前组件

scoped的原理:

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

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

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

示例:

components/BaseOne.vue

components/BaseTwo.vue

<template>
  <div class="base-one">
    BaseTwo
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
div {
  border: 3px solid red;
  margin: 30px;
}
</style>

App.vue

<template>
  <div id="app">
    <BaseOne></BaseOne>
    <BaseTwo></BaseTwo>
  </div>
</template>

<script>
import BaseOne from './components/BaseOne'
import BaseTwo from './components/BaseTwo'
export default {
  name: 'App',
  components: {
    BaseOne,
    BaseTwo
  }
}
</script>


效果:

2. data是一个函数

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

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

data() {
    return {
        count: 100
    }
},

示例:

components/BaseCount.vue

<template>
  <div class="base-count">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 100,
    }
  },
}
</script>

<style>
.base-count {
  margin: 20px;
}
</style>

App.vue

<template>
  <div class="app">
    <baseCount></baseCount>
    <baseCount></baseCount>
    <baseCount></baseCount>
  </div>
</template>

<script>
import baseCount from './components/BaseCount'
export default {
  components: {
    baseCount,
  },
}
</script>

<style>
</style>

效果:

二、组件通信

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

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

不同的组件关系 和 组件通信方案分类

组件关系分类:

1. 父子关系

2. 非父子关系

1. 组件通信语法

2. 父传子

父组件通过 props 将数据传递给子组件

  • ①父组件:给组件添加标签,添加属性的方式,传值
  • ②子组件:通过props进行接收
  • ③子组件:渲染使用

 示例:

效果:

什么是 prop

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

Prop作用:向子组件传递数据

特点:

  • 可以传递任意数量的prop
  • 可以传递任意类型的prop

示例:

App.vue

<template>
  <div class="app">
    <UserInfo
      :username="username"
      :age="age"
      :isSingle="isSingle"
      :car="car"
      :hobby="hobby"
    ></UserInfo>
  </div>
</template>

<script>
import UserInfo from './components/UserInfo.vue'
export default {
  data() {
    return {
      username: '小帅',
      age: 28,
      isSingle: true,
      car: {
        brand: '宝马',
      },
      hobby: ['篮球', '足球', '羽毛球'],
    }
  },
  components: {
    UserInfo,
  },
}
</script>

<style>
</style>

components/UserInfo.vue

<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{ username }}</div>
    <div>年龄:{{ age }}</div>
    <div>是否单身:{{ isSingle ? '是' : '否'}}</div>
    <div>座驾:{{ car.brand }}</div>
    <div>兴趣爱好: {{ hobby.join('、') }}</div>
  </div>
</template>

<script>
export default {
  props: ['username', 'age', 'isSingle', 'car', 'hobby']
}
</script>

<style>
.userinfo {
  width: 300px;
  border: 3px solid #000;
  padding: 20px;
}
.userinfo > div {
  margin: 20px 10px;
}
</style>

效果:

props校验

思考:组件的prop可以乱传吗?

作用:为组件的prop指定验证要求,不符合要求,控制台就会有错误提示 -> 帮助开发者,快速发现错误

语法

①类型校验

props: {
    校验的属性名:类型  // Number String Boolean···
},

②非空校验

③默认值

④自定义校验

props: {
    校验的属性名: {
        type: 类型,  // Number String Boolean···
        required: true,  // 是否必填
        default: 默认值,  // 默认值
        validator (value) {
            // 自定义校验逻辑
            return 是否通过校验
        }
    }
},

示例:

App.vue

<template>
  <div class="app">
    <BaseProgress :w="width"></BaseProgress>
  </div>
</template>

<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
  data() {
    return {
      width: 30,
    }
  },
  components: {
    BaseProgress,
  },
}
</script>

<style>
</style>

components/BaseProgress.vue

<template>
  <div class="base-progress">
    <div class="inner" :style="{ width: w + '%' }">
      <span>{{ w }}%</span>
    </div>
  </div>
</template>

<script>
export default {
  // props: ["w"],

  // 1.基础写法(类型校验)
  // props: {
  //   // 类型校验
  //   w: Number
  // }

  // 2.完整写法(类型、是否必填、默认值、自定义校验)
  props: {
    w: {
      type: Number,
      required: true,
      default: 0,
      validator (value) {
        // console.log(value)
        if(value >= 0 && value <= 100) {
          return true
        } else {
          console.error('传入的prop w必须是0-100的数字')
          return false
        }
        
      }
    }
  }

}
</script>

<style scoped>
.base-progress {
  height: 26px;
  width: 400px;
  border-radius: 15px;
  background-color: #272425;
  border: 3px solid #272425;
  box-sizing: border-box;
  margin-bottom: 30px;
}
.inner {
  position: relative;
  background: #379bff;
  border-radius: 15px;
  height: 25px;
  box-sizing: border-box;
  left: -3px;
  top: -2px;
}
.inner span {
  position: absolute;
  right: 0;
  top: 26px;
}
</style>

效果:

prop & data 单向数据流

共同点: 都可以给组件提供数据

区别:

  • data的数据是自己的 -> 随便改
  • prop的数据是外部的 -> 不能直接改,要遵循单向数据流

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

示例:

App.vue

<template>
  <div class="app">
    <BaseCount 
    :count="count"
    @changeCount="handleChange"
    ></BaseCount>
  </div>
</template>

<script>
import BaseCount from './components/BaseCount.vue'
export default {
  components:{
    BaseCount
  },
  data(){
    return {
      count:100
    }
  },
  methods:{
    handleChange(newCount) {
      this.count = newCount
    }
  }
}
</script>

<style>

</style>

components/BaseCount.vue

<template>
  <div class="base-count">
    <button @click="handleSub">-</button>
    <span>{{ count }}</span>
    <button @click="handleAdd">+</button>
  </div>
</template>

<script>
export default {
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
  //   return {
  //     count: 100,
  //   }
  // },

  // 2.外部传过来的数据 不能随便修改
  // 单向数据流:父组件的prop更新,会单向的向下流动,影响到子组件
  props: {
    count: Number 
  },
  methods: {
    handleAdd() {
      this.$emit('changeCount', this.count + 1)
    },
    handleSub() {
      this.$emit('changeCount', this.count - 1)
    }
  }
 
}
</script>

<style>
.base-count {
  margin: 20px;
}
</style>

3. 子传父

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

  • ①子组件:$emit发送消息;
  • ②父组件:给子组件添加消息监听;
  • ③父组件:实现处理函数。

示例:

components/Son.vue

<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    我是Son组件 {{ title }}
    <button @click="changeFn">修改title</button>
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  props: ['title'],
  methods: {
    changeFn() {
      // 1. 通过$emit,向父组件发送消息通知
      this.$emit('changeTitle', '传智教育')
    }
  }
}
</script>

<style>

</style>

App.vue

<template>
  <div class="app" style="border: 3px solid #000; margin: 10px">
    我是APP组件
    <!-- 2. 父组件,对消息进行监听 -->
    <Son :title="myTitle" @changeTitle="handleChange"></Son>
  </div>
</template>

<script>
import Son from "./components/Son.vue"
export default {
  name: "App",
  components: {
    Son,
  },
  data() {
    return {
      myTitle: "学前端,就来黑马程序员",
    }
  },
  methods: {
    // 提供对应的处理函数,提供逻辑
    handleChange(newTitle) {
      // console.log(newTitle)
      this.myTitle = newTitle
    }
  }
}
</script>

<style>
</style>

效果:

4. 非父子(拓展)— 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', '这是一个消息')

示例:

创建一个都能访问到的事件总线(空Vue示例) -> src/utils/EventBus.js

import Vue from 'vue'

// 1. 创建一个都能访问到的事件总线(空的Vue实例)
const Bus  =  new Vue()

export default Bus

src/components/BaseA.vue:接收方

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

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    // 在A组件(接收方),进行监听Bus的事件(订阅消息)
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

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

src/components/BaseB.vue:发送方

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

<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      // 3. B组件(发送方),触发事件的方式传递参数
      // 任何监听了这个消息的组件都可以接收到对应的消息
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

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

src/components/BaseC.vue:接收方

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

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

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

App.vue

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

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

<style>

</style>

5. 非父子通信(拓展) — provide & inject

provide & inject作用:跨层级共享数据。

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

示例:

App.vue

<template>
  <div class="app">
    我是APP组件
    <button @click="change">修改数据</button>
    <SonA></SonA>
    <SonB></SonB>
  </div>
</template>

<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
  provide() {
    return {
      // 简单类型 是非响应式的
      color: this.color,
      // 复杂类型 是响应式的
      userInfo: this.userInfo,
    }
  },
  data() {
    return {
      color: 'pink',
      userInfo: {
        name: 'zs',
        age: 18,
      },
    }
  },
  methods: {
    change() {
      this.color = 'red'
      this.userInfo.name = 'ls'
    },
  },
  components: {
    SonA,
    SonB,
  },
}
</script>

<style>
.app {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
}
</style>

components/SonA.vue

<template>
  <div class="SonA">我是SonA组件
    <GrandSon></GrandSon>
  </div>
</template>

<script>
import GrandSon from '../components/GrandSon.vue'
export default {
  components:{
    GrandSon
  }
}
</script>

<style>
.SonA {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>

 components/SonB.vue

<template>
  <div class="SonB">
    我是SonB组件
  </div>
</template>

<script>
export default {

}
</script>

<style>
.SonB {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>

components/GrandSon.vue

<template>
  <div class="grandSon">
    我是GrandSon
    {{ color }} -{{ userInfo.name }} -{{ userInfo.age }}
  </div>
</template>

<script>
export default {
  inject: ['color', 'userInfo'],
}
</script>

<style>
.grandSon {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 100px;
}
</style>

效果:

三、综合案例:小黑记事本(组件版)

需求说明:

①拆分基础组件 

新建组件 -> 拆分存放结构 -> 导入注册使用

②渲染待办任务

提供数据(公共父组件) -> 父传子传递list -> v-for渲染

③添加任务  

收集数据 v-model -> 监听事件 -> 子传父传递任务 -> 父组件unshift

④删除任务

监听删除携带id -> 子传父传递id -> 父组件filter删除

⑤底部合计 和 清空功能

底部合计:父传子传递list -> 合计展示

清空功能:监听点击 -> 子传父通知父组件 -> 父组件清空

⑥持久化存储 

watch监视数据变化,持久化到本地

TodoHeader.vue

<template>
  <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input
        @keyup.enter="handleAdd" 
        placeholder="请输入任务" class="new-todo" v-model="todoName"/>
    <button class="add" @click="handleAdd">添加任务</button>
  </header>
</template>

<script>
export default {
    data() {
        return {
            todoName: ''
        }
    },
    methods: {
        handleAdd() {
            // console.log(this.todoName)
            // 非空判断
            if(this.todoName.trim() === '') {
                alert('任务名称不能为空!')
                return
            }
            this.$emit('add', this.todoName)
            this.todoName = ''
        }
    }
};
</script>

<style>
</style>

TodoMain.vue

<template>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo" v-for="(item, index) in list" :key="item.id">
        <div class="view">
          <span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
          <button class="destroy" @click="handleDel(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
</template>

<script>
export default {
    props: {
        list: Array,
        
    },
    methods: {
        handleDel(id) {
            this.$emit('delete', id)
        }
    }
};
</script>

<style>
</style>

TodoFooter.vue

<template>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
    <!-- 清空 -->
    <button class="clear-completed" @click="clear">清空任务</button>
  </footer>
</template>

<script>
export default {
    props: {
        list: Array
    },
    methods: {
        clear() {
            this.$emit('clear')
        }
    }
};
</script>

<style>
</style>

App.vue

<template>
  <!-- 主体区域 -->
  <section id="app">
    <TodoHeader @add="handleAdd"></TodoHeader>
    <TodoMain :list="list" @delete="handleDel"></TodoMain>
    <TodoFooter :list="list" @clear="handleClear"></TodoFooter>
  </section>
</template>

<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";

// 渲染功能:
// 1. 提供数据:提供在公共的父组件 App.vue
// 2, 通过父传子,将数据传递给TodoMain
// 3. 利用v-for渲染

// 添加功能
// 1. 收集表单数据 v-model
// 2. 进行监听事件(回车 + 点击 都要进行添加)
// 3. 子传父,将任务名称传递给父组件App.vue
// 4. 进行添加unshift(自己的数据自己负责)

// 删除功能
// 1. 监听事件(监听删除的点击) 携带id
// 2. 子传父,将任务id传递给父组件App.vue
// 3. 进行删除 filter(自己的数据自己负责)

// 底部合计
// 1. 父传子传list
// 2. 数据统计

// 清空功能
// 1. 子传父,通知到父组件
// 2. 由父组件进行清空

// 持久化存储
// 1. watch深度监视list的变化,往本地存储,进入页面优先读取本地

export default {
  data() {
    return {
      list: JSON.parse(localStorage.getItem("list")) || [
        { id: 1, name: "打篮球" },
        { id: 2, name: "跳绳" },
        { id: 3, name: "打羽毛球" },
        { id: 4, name: "游泳" },
      ],
    };
  },
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  watch: {
    // 对list进行监视
    list: {
      deep: true,
      handler(newValue) {
        localStorage.setItem("list", JSON.stringify(newValue));
      }
    },
  },
  methods: {
    // 删除
    handleDel(id) {
      this.list = this.list.filter((res) => res.id !== id);
    },
    // 添加
    handleAdd(todoName) {
      // console.log(todoName)
      this.list.unshift({
        id: +new Date(),
        name: todoName,
      });
    },
    // 清空
    handleClear() {
      this.list = [];
    },
  },
};
</script>

<style>
</style>

效果:

四、进阶语法

1. v-model原理

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

作用:提供数据的双向绑定

①数据变化,视图跟着变 :value

②视图变,数据跟着变 @input

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

<template>
    <div id="app">
        <input v-model="msg" type="text">
        
        <input :value="msg" @input="msg = $event.target.value" type="text">
    </div>
</template>

示例:

App.vue

<template>
  <div class="app">
    <input type="text" v-model="msg1"/>
    <br />
    <!-- 模板中获取事件的形参 -> $event 获取 -->
    <input type="text" :value="msg2" @input="msg = $event.target.value">
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg1: '',
      msg2: ''
    }
  },
}
</script>

<style>
</style>

效果:

2. v-model应用于组件

表单类组件封装 & v-model简化代码

1. 表单类组件 封装

父传子:数据应该是父组件 props 传递过来的,v-model 拆解 绑定数据

子传父:监听输入,子传父传值给父组件修改

App.vue

<template>
  <div class="app">
    <BaseSelect 
    :cityId="selectId"
    @changeId="selectId = $event"
    >
    </BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '102',
    }
  },
  components: {
    BaseSelect,
  },
  methods: {
    handleChange(e) {
      console.log(e);
      this.selectId = e
    }
    
  }
}
</script>

<style>
</style>

components/BaseSelect.vue

<template>
  <div>
    <select :value="cityId" @change="handleChange" >
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>
export default {
  props: {
    cityId: String
  },
  methods: {
    handleChange(e) {
      // console.log(e.target.value)
      this.$emit('changeId', e.target.value)
    }
  }
}
</script>

<style>
</style>

效果:

2. 父组件v-model简化代码,实现子组件和父组件数据双向绑定

①子组件中:props通过value接收,事件触发input

②父组件中:v-model给组件直接绑定数据(:value + @input

示例:

BaseSelect.vue

<template>
  <div>
    <select :value="value" @change="handleChange" >
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>
export default {
  props: {
    value: String
  },
  methods: {
    handleChange(e) {
      // console.log(e.target.value)
      this.$emit('input', e.target.value)
    }
  }
}
</script>

<style>
</style>

App.vue

<template>
  <div class="app">
    <BaseSelect 
    v-model="selectId"
    >
    </BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '103',
    }
  },
  components: {
    BaseSelect,
  },
  methods: {
    handleChange(e) {
      console.log(e);
      this.selectId = e
    }
    
  }
}
</script>

<style>
</style>

效果:

3. .sync修饰符

作用:可以实现子组件和父组件数据的双向绑定,简化代码

特点:prop属性名,可以自定义,非固定为value

场景:封装弹框类的基础组件,visible属性:true显示,false因此

本质:就是 :属性名@update:属性名 合写

示例:

App.vue

<template>
  <div class="app">
    <button class="logout"
      @click="isShow = true"
    >退出按钮</button>
    <!-- :visible.sync 等价于 :visible + @update:visible -->
    <BaseDialog 
      :visible.sync="isShow"
    ></BaseDialog>
  </div>
</template>

<script>
import BaseDialog from "./components/BaseDialog.vue"
export default {
  data() {
    return {
      isShow: false
    }
  },
  methods: {
    
  },
  components: {
    BaseDialog,
  },
}
</script>

<style>
</style>

BaseDialog.vue

<template>
  <div class="base-dialog-wrap" v-show="visible">
    <div class="base-dialog">
      <div class="title">
        <h3>温馨提示:</h3>
        <button class="close" @click="close">x</button>
      </div>
      <div class="content">
        <p>你确认要退出本系统么?</p>
      </div>
      <div class="footer">
        <button @click="close">确认</button>
        <button @click="close">取消</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    visible: Boolean
  },
  methods: {
    close() {
      this.$emit('update:visible', false)
    }
  }
}
</script>

<style scoped>
.base-dialog-wrap {
  width: 300px;
  height: 200px;
  box-shadow: 2px 2px 2px 2px #ccc;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 0 10px;
}
.base-dialog .title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 2px solid #000;
}
.base-dialog .content {
  margin-top: 38px;
}
.base-dialog .title .close {
  width: 20px;
  height: 20px;
  cursor: pointer;
  line-height: 10px;
}
.footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 26px;
}
.footer button {
  width: 80px;
  height: 40px;
}
.footer button:nth-child(1) {
  margin-right: 10px;
  cursor: pointer;
}
</style>

效果:

4. ref 和 $refs / $nextTick

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

特点:查找范围 -> 当前组件内(更精确稳定)

①获取dom:

1. 目标标签 — 添加ref属性

<div ref="chartRef">我是渲染图表的容器</div>

2. 恰当时机,通过this.$refs.xxx,获取目标标签

mounted() {
    console.log(this.$refs.chartRef)
),

示例:

App.vue

<template>
  <div class="app">
    <div class="base-chart-box">
      这是一个捣乱的盒子
    </div>
    <BaseChart></BaseChart>
  </div>
</template>

<script>
import BaseChart from './components/BaseChart.vue'
export default {
  components:{
    BaseChart
  }
}
</script>

<style>
.base-chart-box {
  width: 200px;
  height: 100px;
}
</style>

BaseChart.vue

<template>
  <div ref="mychart" class="base-chart-box">子组件</div>
</template>

<script>
import * as echarts from 'echarts'

export default {
  mounted() {
    // 基于准备好的dom,初始化echarts实例
    // const myChart = echarts.init(document.querySelector('.base-chart-box'))
    const myChart = echarts.init(this.$refs.mychart)
    // console.log(this.$refs.mychart)
    
    // 指定图表的配置项和数据
    const option = {
      title: {
        text: 'ECharts 入门示例',
      },
      tooltip: {},
      xAxis: {
        data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
      },
      yAxis: {},
      series: [
        {
          name: '销量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20],
        },
      ],
    }
    
    // 使用刚指定的配置项和数据显示图表
    myChart.setOption(option)
  },
}
</script>

<style scoped>
.base-chart-box {
  width: 400px;
  height: 300px;
  border: 3px solid #000;
  border-radius: 6px;
}
</style>

效果:

②获取组件:

1.目标组件 — 添加ref属性

<BaseForm ref="baseForm"></BaseForm>

2. 恰当时机,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法

this.$refs.baseForm.组件方法()

示例:

BaseForm.vue

<template>
  <div class="app">
    <div>
      账号: <input v-model="username" type="text">
    </div>
     <div>
      密码: <input v-model="password" type="text">
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: 'admin',
      password: '123456',
    }
  },
  methods: {
    // 收集表单数据,返回一个对象
    getValues() {
      return {
        username: this.username,
        password: this.password
      }
    },
    // 重置表单
    resetValues() {
      this.username = ''
      this.password = ''
    }
  }
}
</script>

<style scoped>
.app {
  border: 2px solid #ccc;
  padding: 10px;
}
.app div{
  margin: 10px 0;
}
.app div button{
  margin-right: 8px;
}
</style>

App.vue

<template>
  <div class="app">
    <BaseForm ref="baseForm"></BaseForm>
    <button @click="handleGet">获取数据</button>
    <button @click="handleReset">重置数据</button>
  </div>
</template>

<script>
import BaseForm from './components/BaseForm.vue'
export default {
  data() {
    return {

    }
  },
  components: {
    BaseForm,
  },
  methods: {
    handleGet() {
      console.log(this.$refs.baseForm.getValues())
    },
    handleReset() {
      this.$refs.baseForm.resetValues()
    }
  }
}
</script>

<style>
</style>

效果:

5. Vue异步更新、$nextTick

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

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

this.$nextTick(() => {
    // 业务逻辑
})

需求:编辑标题,编辑框自动聚焦

1. 点击编辑,显示编辑框

2. 让编辑框,立刻获取焦点

this.isShowEdit = true  // 显示输入框
this.$refs.inp.focus()  // 获取焦点

问题:”显示之后“,立刻获取焦点是不能成功的!

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

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

示例代码:

App.vue

<template>
  <div class="app">
    <div v-if="isShowEdit">
      <input type="text" v-model="editValue" ref="inp" />
      <button>确认</button>
    </div>
    <div v-else>
      <span>{{ title }}</span>
      <button @click="handleEdit">编辑</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '大标题',
      isShowEdit: false,
      editValue: '',
    }
  },
  methods: {
    handleEdit() {
      // 1. 显示输入框(异步DOM更新)
      this.isShowEdit = true
      // 2. 让输入框 获取焦点($nextTick等DOM更新完, 立刻去执行指定的函数体)
      this.$nextTick(() => {
        this.$refs.inp.focus()
      })
    }
  },
}
</script>

<style>
</style>

效果:点击编辑后,显示输入框,并获取焦点

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值