Vue基础知识点

138 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

第一章 Vue使用

image.png

脚手架文件结构介绍

├── node_modules 
├── public
   ├── favicon.ico: 页签图标
   └── index.html: 主页面
├── src
   ├── assets: 存放静态资源
      └── logo.png
   │── component: 存放组件
      └── HelloWorld.vue
   │── App.vue: 汇总所有组件
   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

Vue基础知识

Vue指令

v-text:用于操作纯文本 它会替代显示对应的数据对象的值 但是注意:此处为单项数据绑定,数据对象上的值改变了,插值会发生改变;但是改变插值并不会影响数据对象的值。v-text的简写就是{{}}

v-html:用于输出html,它与v-text的区别在于v-text是输出纯文本的内容,浏览器不会再对其进行html解析,但v-html会将其进行html解析

<div id="app">
    <p v-html="html"><p>
</div>
let app = new Vue({
    el:"#app",
    data:{
        html:'<span style='color:red'>v-html</span>'
    }
})

v-if:如果为true,当前标签才会显示到页面上

<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2> //这里的“1 === 1”为true

v-else:与v-if v-else-if 一起使用 当v-if v-else都为false时 它控制的标签才会显示

<div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div>
        <div v-else>哈哈</div>

v-show:通过控制css样式 display来显示和隐藏标签 适用于标签反复显示隐藏的情况

v-for:用于循环渲染,遍历数组和对象把它们的数据展示出来

v-on:绑定事件监听 一般可以简写为@

v-bind:单向数据绑定,数据只能从data流向页面 ,页面的数据改变了并不会影响data

v-bind:src 可以简写为 :src

<div id="root">
        <!-- 普通写法 -->
        <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
            双向数据绑定:<input type="text" v-model:value="name"><br/> --><!-- 上面可以简写为下面 -->
        单向数据绑定:<input type="text" :value="name"><br />
        双向数据绑定:<input type="text" v-model="name"><br /><!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
        <!-- <h2 v-model:x="name">你好啊</h2> -->
    </div>

v-model:v-model可以实现数据的双向绑定 应用于input,textarea,select元素上创建数据的双向绑定

ref:为某个元素指定唯一的标识,vue对象通过$refs访问这个元素

// 子组件
<template>
  <div>
    我是子组件
  </div>
</template><script>
export default {
  data() {
    return {
      name: "myhua"
    };
  }
};
</script>
​
​
// 父组件
​
<template>
  <div id="app">
    <Son ref="myref"></Son>
  </div>
</template><script>
import Son from "./components/son";
export default {
  mounted() {
    console.log(this.$refs.myref.name); //输出子组件data中的name的值:myhua
  },
  components: {
    Son
  }
};
</script>

Vue基本使用

computed和watch

image.png

两者有何区别?

computed(计算属性)是依赖已有的变量来计算的一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量,并且computed具有缓存机制,依赖值不变的情况下其会直接读取缓存进行复用,computed不能进行异步操作

watch(监听属性)是监听某一个变量的变化,并执行相对应的回调函数,通常是一个变量的变化决定多个变量的变化,watch可以进行异步操作

简单记就是 一般情况下 computed是多对一 watch是一对多

Vue中的数据代理

数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

<body>
	<!-- 
				1.Vue中的数据代理:
							通过vm对象来代理data对象中属性(name address)的操作(读/写) vm最终改的都是data中的name address
				2.Vue中数据代理的好处:
							更加方便的操作data中的数据
				3.基本原理:
							通过Object.defineProperty()把data对象中所有属性添加到vm上。
							为每一个添加到vm上的属性,都指定一个getter/setter。
							在getter/setter内部去操作(读/写)data中对应的属性。
		 -->

	<!-- 准备好一个容器-->
	<div id="root">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			name: '尚硅谷',
			address: '宏福科技园'
		}
	})
	//在控制台利用vm.name = 'hhh'  修改name的值 这里是setter发挥了作用
	//利用 vm._data.name === vm.name  验证通过vm去修改name是否可以修改data.name
</script>

动态绑定class和style

使用动态属性

命名时驼峰命名法

<template>
  <div>
    <p :class="{ black: isBlack, yellow: isYellow }">使用 class(对象)</p>
    <p :class="[pink1, yellow]">使用 class (数组)</p>
    <p :style="styleData">使用 style</p>
  </div>
</template><script>
export default {
  data() {
    return {
      //对象
      isBlack: true,
      isYellow: true,
​
      //数组
      pink1: "pink",
      black: "black",
      yellow: "yellow",
​
      //内联样式style
      styleData: {
        fontSize: "40px", // 转换为驼峰式
        color: "red",
        backgroundColor: "#ccc", // 转换为驼峰式
      },
    };
  },
};
</script><style scoped>
.black {
  background-color: #999;
}
.yellow {
  color: yellow;
}
.pink {
  background-color: hotpink;
}
</style>

条件渲染

v-if v-else的用法,可使用变量,也可以使用 === 表达式

<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2> //这里的“1 === 1”为true
<div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div>
        <div v-else>哈哈</div>

v-if 和 v-show 的区别?

v-if是通过控制dom元素的删除和生成来实现显隐的,每一次显隐都会使组件重新跑一遍生命周期,因为显隐决定了组件的生成和销毁

v-show是通过控制dom元素的css样式来实现显隐的,不会销毁

实现一次性切换或者切换不频繁的话用 v-if v-else-if

切换功能频繁的话我们就用v-show

v-if 和 v-show 的使用场景

实现一次性切换或者切换不频繁的话用 v-if v-else-if

切换功能频繁的话我们就用v-show

循环(列表)渲染

如何遍历数组和对象?

<template>
  <div>
    <p>遍历数组</p>
    <ul>
      <li v-for="(item, index) in listArr" :key="item.id">
        {{ index }} - {{ item.id }} - {{ item.title }}
      </li>
    </ul>
​
    <p>遍历对象</p>
    <ul>
      <li v-for="(val, key, index) in listObj" :key="key">
        {{ index }} - {{ key }} - {{ val.title }}
        <!-- 遍历对象时key就是下面的 a b c -->
      </li>
    </ul>
  </div>
</template><script>
export default {
  data() {
    return {
      flag: false,
      listArr: [
        { id: "a", title: "标题1" }, // 数据结构中,最好有 id ,方便使用 key
        { id: "b", title: "标题2" },
        { id: "c", title: "标题3" },
      ],
      listObj: {
        a: { title: "标题1" },
        b: { title: "标题2" },
        c: { title: "标题3" },
      },
    };
  },
};
</script>

key的重要性以及key为什么不能写成index或者random?(但是实际写了也不会报错)

key的主要作用是为了高效的更新虚拟DOM。key需要使用唯一的标识 ,而index,和random并不是对象的唯一的标识(因为index对应的只是一个固定的位置 当我们在数组中间插入一条数据时 其余后面的index都会发生改变 那么就会导致重新渲染)

<div v-for="(item,index) in list" :key='index'>{{item.name}}</div>
//没有插入时的数据
const list = [
    {
        id: 1 ,
        name:'1'
    }
     {
        id: 2 ,
        name:'2'
    }
     {
        id: 3 ,
        name:'3'
    }
]
//在中间插入一条数据
const list = [
      {
        id: 1 ,
        name:'1'
    }
     {
        id: 4 ,
        name:'4'
    }
     {
        id: 1 ,
        name:'1'
    }
     {
        id: 1 ,
        name:'1'
    }
]
//那么此时
之前的数据                         之后的数据

key: 0  index: 0 name: 1     key: 0  index: 0 name: 1
key: 1  index: 1 name: 2     key: 1  index: 1 name: 我是插队的那条数据
key: 2  index: 2 name: 3     key: 2  index: 2 name: 2
                             key: 3  index: 3 name: 3
//之后的数据除了第一条的index没有改变之后 其余的index都改变了 故只插入一条数据的情况小要引起三条数据的渲染 增加了耗能

因为Vue是数据驱动视图,通过改变数据进而达到改变视图,加上key之后Vue的虚拟DOM的diff算法更容易定位到相应的元素,避免去遍历DOM造成性能的浪费

v-for和v-if不能一起使用!

事件(event)

没有参数时默认传入的是event参数,有参数时显示在最后传入$event


<template>
  <div>
    <p>{{ num }}</p>
    <button @click="increment1">+1</button>
    <button @click="increment2(2, $event)">+2</button>
  </div>
</template><script>
export default {
  data() {
    return {
      num: 0,
    };
  },
  methods: {
    increment1(event) {
      // eslint-disable-next-line
      console.log("event1", event, "--", event.__proto__.constructor); // event是原生的 event 对象
      // eslint-disable-next-line
      console.log(event.target);
      // eslint-disable-next-line
      console.log(event.currentTarget); //事件是被注册到当前元素的,和 React 不一样
      this.num++;
​
      // 1. event 是原生的
      // 2. 事件被挂载到当前元素
      // 和 DOM 事件一样
    },
    increment2(val, event) {
      // eslint-disable-next-line
      console.log(event.target);
      this.num = this.num + val;
    },
    loadHandler() {
      // do some thing
    },
  },
  mounted() {
    window.addEventListener("load", this.loadHandler);
  },
  beforeDestroy() {
    //【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
    // 自己绑定的事件,需要自己销毁!!!
    window.removeEventListener("load", this.loadHandler);
  },
};
</script>

Vue组件间通信

props 父 => 子

props配置项

  1. 功能:让子组件组件接收父组件传过来的数据 父 => 子

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
          name:{
          type:String, //类型
          required:true, //必要性
          default:'老王' //默认值
          }
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

$emit 子组件 ===> 父组件 自定义事件

自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. 触发自定义事件:this.$emit('atguigu',数据)

  5. 解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

任意组件间通信

全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

    new Vue({
        ......
        beforeCreate() {
            Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
        },
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

vuex

Vuex是专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

image.png

何时使用? 多个组件需要共享数据时使用

搭建过程

1.创建文件:在src下 创建store/index.js

内部代码如下

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex
Vue.use(Vuex)
​
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
​
//创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state
})
​
​

2.在main.js中创建vm时传入store配置项

......
//引入store
import store from './store'
......
​
//创建vm
new Vue({
    el:'#app',
    render: h => h(App),
    store
})

基本使用

1.在store下面的index.js中 初始化数据 ,配置actions,mutations

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
​
//actions——用于响应组件中的动作(服务员)这里面是有业务逻辑的
const actions = {
    //响应组件中加的动作   context是上下文对象  用于触发commit里面的方法  value为外部传进来的参数
jiaOdd(context,value){
        console.log('actions中的jiaOdd被调用了')
     //if里面默认的是不等于0即true才会进入
        if (context.state.sum % 2) {
            // context.state.sum += value //这样写也可以实现加的操作但是开发者工具失效了(即开发者工具里面捕获不到操作步骤了) 开发中我们是需要开发者工具的介入的 因为开发者工具一直是在跟Mutations对话
            context.commit('JIA',value)
        }
    },
    jiaWait(context,value){
        console.log('actions中的jiaWait被调用了')
        setTimeout(()=>{
            context.commit('JIA',value)
        },500)
    }
}
​
const mutations = {
    //执行加
    JIAN(state,value){//state为store里面的index.js配置的state 用来给其他组件调用  这一步是真正改变state的步骤
        // console.log('mutations中的JIA被调用了',state,value)
        state.sum += value
    }
}
​
//初始化数据
const state = {
   sum:0
}
​
//创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state,
})

2.组件中读取Vuex中的数据:$store.state.sum

3.组件修改Vuex中的数据:$store.dispatch('actions中的方法名',需要传入的数据) 或者

$store.commit('mutations中的方法名',需要传入的数据) 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

//实际使用vuex button { margin-left: 5px; }

getters的使用

1.概念:当state里面的数据需要加工的时候再使用 可以用getters

2.使用方法:直接在store/index.js 里面追加getters配置

下面代码和上面代码的不同之处

const getters = {
    bigSum(state){
        return state.sum * 10
    }
}
​
//创建并暴露store
export default new Vuex.Store({
    ......
    getters
})
//实际使用vuex
<template>
  <div>
    <h1>当前求和为:{{ $store.state.sum }}</h1> //这里读取了vuex里面的数据
    <h1>当前求和为:{{ $store.getters.bigSum }}</h1> //这里读取了state加工后的数据
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>
四个map方法的使用

引入

import { mapState, mapGetters } from "vuex"; //帮助生成代码的函数
  1. mapState方法: 用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法: 用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    ​
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法: 用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    ​
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法: 用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象

消息订阅与发布

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。 \