Vue3 学习笔记

有 Vue2 的基础,笔记只记载之前不熟悉的知识

一、Vue 基本知识

1. Vue3 基本指令

1.1 v-pre

v-pre用于跳过元素和它的子元素的编译过程,显示原始的Mustache标签:

跳过不需要编译的节点,加快编译的速度;

<template>
	<div>
        <div v-pre>{{message}}</div>
		<div>{{message}}</div>
    </div>
</template>
  • 使用v-pre,将不会渲染message对应的内容,直接显示文本{{message}}

1.2 v-bind 用法

1.2.1 动态绑定属性名称

<template>
    <div>
        <div :[name]="value"></div>
        <!-- 上述渲染后相当于下方 -->
        <div abc="cba"></div>
    </div>
</template>
<script>
export default {
  data(){
    return {
	  name: 'abc',
	  value: 'cba'
    }
  }
}
</script>

1.2.2 v-bind绑定一个对象

绑定对象后,会将对象解构,赋值到相应的标签上。

作用于组件时,可一次性将子组件需要的props进行传递

<template>
    <div v-bind="info">哈哈哈哈</div>
    <div :="info">哈哈哈哈</div>
    <!-- 上述渲染后相当于下方 -->
    <div name="fct" age="18" height="1.88">哈哈哈哈</div>
</template>

<script>
export default {
  data() {
    return {
      info: {
        name: "fct",
        age: 18,
        height: 1.88
      }
    }
  }
}
</script>

1.3 v-for 中 key 的作用

主要是Diff算法运用

  1. key 这个特殊的 attribute 主要作为 Vue 的 虚拟 DOM 算法 提示,在比较新旧节点列表时用于识别 vnode

  2. 在没有 key 的情况下,Vue 将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。

    源码如图:

    image-20220824182920503
  3. 如果传了 key,则将根据 key 的变化顺序来重新排列元素,并且将始终移除/销毁 key 已经不存在的元素。

    源码如图:

    image-20220824182821053

  4. 同一个父元素下的子元素必须具有唯一的 key。重复的 key 将会导致渲染异常。

2. 侦听器 watch

2.1 函数形式

ownName为需要侦听的变量的名称,如:要侦听 data 中 userName 的改变,对应的 侦听器为 userName(new, old){}

但是当需要侦听的为对象或其他引用类型的值时,使用函数形式的侦听器只能侦听引用类型数据的地址改变,而不能侦听内部值的改变。

{
      data() {
        return {
          userInfo: { userName: 'fct', age: "18" },
          ownName: 'changqing'
        }
      },
      watch: {
        // 侦听 ownName
        ownName(newValue, oldValue) {
          // 处理逻辑
        },
        // 侦听 userInfo 对象
        userInfo(newValue, oldValue) {
          // 处理逻辑
        },
        // 侦听 userInfo 对象中的 userName的改变
        "userInfo.userName": function(newValue, oldValue) {
          // 处理逻辑
        }
      }
}

2.2 对象形式

  • 相当于函数形式:
watch: {
  userInfo: {
	handler(newValue, oldValue){
	  // 处理逻辑
	}
  }
}
  • 深度侦听
watch: {
  userInfo: {
	handler(newValue, oldValue){
	  // 处理逻辑
	},
    deep: true,	  // 深度侦听,能侦听到对象内值的改变
  }
}
  • 立即执行(第一次渲染后就执行一次侦听器,而不是等到侦听的值改变再执行第一次)
watch: {
  userInfo: {
	handler(newValue, oldValue){
	  // 处理逻辑
	},
    deep: true,	  // 深度侦听,能侦听到对象内值的改变
    immediate: true  // 立即执行
  }
}

2.3 $watch API

使用$watchAPI来侦听:

  • 第一个参数:侦听的源
  • 第二个参数:侦听的回调函数callback
  • 第三个参数配置选项对象:如deep、immediate
created(){
    this.$watch('userInfo', (newValue, oldValue) => {
        // 处理逻辑
    },{
      deep: true,
      immediate: true
    })
}

3. 组件

3.1 全局注册组件

// app 是全局根实例,APPObj是配置对象
const app = Vue.createApp(appObj);

// 全局注册组件
app.component('my-component', {
    // 配置对象 
})

二、Vue 组件化

1. $attrs

前提:当父组件传递参数给子组件时:

<son-component class="title" title="标题1" content="内容"></son-component>

对应的子组件有相应的props来接受titlecontent属性,而class属性属于非props属性,Vue默认会将非props属性传递给子组件的根元素来继承。而Vue3中组件可以不用设置根元素来包裹,所以当在子组件不设置根元素的话,vue控制台输出警告。

那如何消除警告?

  • 给子组件设置根节点元素。
  • 使用 $attrs取捕获非props属性,并加以使用
<template>
  <h2 :class="$attrs.class">陈平安</h2>
  <p>{{ title }}</p>
  <p>{{ content }}</p>
</template>

<script>
export default {
  props: ['title', 'content'],
  mounted() {
    console.log(this.$attrs);
    // { class: 'title' }
  }
};
</script>

2.子组件与父组件通信

Vue3 和 Vue2有区别:

Vue3 需要提前注册声明自定义事件。可以使用数组对象形式

export default {
 emits: ['自定义事件名称'],
 // 对象语法允许我们对触发事件的参数进行验证:
 emits: {
     '自定义事件名称1':null,
     '自定义事件名称2': (plaload1,....) => {
 		// 通过返回值为 `true` 还是为 `false` 来判断
 		// 验证是否通过
		}
 }
}

父组件:

<template>
  <div class="hello">
    <h2>{{ count }}</h2>
    <!-- 在子组件上注册事件接收 -->
    <count-operator @add="add" @reduce="reduce"></count-operator>
  </div>
</template>
<script>
import CountOperator from './CountOperator.vue';
export default {
  components: { CountOperator },
  data() {
    return {
      count: 0
    };
  },
  methods: {
    add() {
      this.count++;
    },
    reduce() {
      this.count--;
    }
  }
};
</script>

子组件

<template>
  <div>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
export default {
  // 声明自定义事件
  emits: ['add', 'reduce'],
  methods: {
    increment() {
      // 触发自定义add
      this.$emit('add');
    },
    decrement() {
      // 触发自定义reduce
      this.$emit('reduce');
    }
  }
};
</script>

3. 非父子组件通信

3.1 provide、inject

用于一些深度嵌套的组件,孙组件想要获取父组件的部分内容;

父组件有一个 provide 选项来提供数据;

孙组件有一个 inject 选项来开始使用这些数据;

3.1.1 组件结构

App.vue----->Home.vue----->HomeNavBar.vue

App.vue

export default {
    provide: {
        fct: '我是app组件'
    }
}

HomeNavBar.vue

export default {
  inject: ['fct']
}

3.1.2 provide 的函数形式

如果Provide中提供的一些数据是来自data,那么我们可能会想要通过this来获取。

直接在provide对象中使用 this 会报错,是因为 provide 中的 this 指向的不是vue实例,而是当前vue组件的script标签的根this,而根 this 为 undefined。

要正确使用this,则需要更改provide为函数形式:

<script>
export default {
    data() {
        return {
            names: ['a', 'b']
        }
    },
    provide() {
      return {
        fct: '我是app组件',
        length: this.names.length
      }
    }
}
</script>

3.1.3 处理响应式数据

在上例的基础上,当我们在names数组中新增数组项,而provide提供给孙组件的数据没有变化,不是响应式数据。

这时我们需要Vue提供的响应式函数来解决:computed函数

<script>
import { computed } from 'vue'
export default {
    data() {
        return {
            names: ['a', 'b']
        }
    },
    provide() {
      return {
        fct: '我是app组件',
        length: computed(() => this.names.length)
      }
    }
}
</script>

在孙组件中,取值需要通过.value取值。因为computed函数返回的是一个ref对象

3.2 全局事件总线 mitt库

Vue3从实例中移除了 $on、$off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:

  • Vue3官方有推荐一些库,例如 mitttiny-emitter

  • 这里我们主要讲解一下mitt库的使用;

3.2.1 安装:

npm i mitt

3.2.2 使用:

  1. 封装工具eventbus.js
import mitt from 'mitt';

const emitter = mitt();

export default emitter;
  1. 触发事件

App.vue

<script>
import emitter from './utils/eventBus';
export default {
  methods: {
      clickEvent() {
        emitter.emit('fct', { name: 'fct' });
      }
  }
};
</script>
  1. 监听事件

其他组件

<script>
import emitter from './utils/eventBus';
export default {
  created() {
    // 监听单一事件
    emitter.on('fct', (data) => {
      console.log('fct-', data);
    });
    // 监听所有事件
    emitter.on('*', (type, data) => {
      console.log('* event', type, data);
    });
  },
};
</script>
  1. Mitt事件的取消
// 取消mitter中所有的监听
emitter.all.clear()

// 定义一个函数
function onFoo() {}
emitter.on('foo', onFoo);	// 监听
emitter.off('foo', onFoo);	// 取消监听

4. 插槽

插槽 Slots

5.动态组件

动态组件

动态组件有时会配合keep-alive使用,因为当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。通过 KeepAlive组件 强制被切换掉的组件仍然保持“存活”的状态。

6. 异步组件

异步组件

常用:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

搭配 Suspense 使用

异步组件可以搭配内置的 <Suspense> 组件一起使用, Suspense 中查看。

可进行设定异步组件加载失败后展示的组件

7. $refs、$parent、$root

$refs获取在template模板中设置有ref属性的标签或组件。

<!-- 模板引用 -->
<input ref="input">

<script>
  this.$refs.input.focus()
</script>

$parent获得当前组件的父组件的引用。

$root获得当前组件的所在的根组件。

Vue3已经移除$children属性

8.组件的v-model

💥💥💥

环境为:Vue3

1. 原生元素上的 v-model

<p>{{ msg }}</p>
<input v-model="msg" />

<!-- 上面的代码其实等价于下面这段 (编译器会对 v-model 进行展开): -->
<input :value="msg" @input="msg = $event.target.value" />

2. 组件上的 v-model

2.1 组件v-model的展开

<template>
  <p>{{ msg }}</p>
  <!-- 组件上的v-model -->
  <CustomeInput v-model="msg"></CustomeInput>
</template>
<script>
import CustomeInput from './components/CustomeInput.vue';
export default {
  name: 'App',
  components: {
    CustomeInput
  },
  data() {
    return {
      msg: '我是付常涛'
    };
  }
};
</script>

v-model展开如下:

<CustomInput
  :modelValue="msg"
  @update:modelValue="newValue => msg = newValue"
/>

✨✨✨

v-model="msg",默认将属性modelValue当做 props 传进组件,并接收组件内名为update:modelValue的自定义事件。

2.2 组件内部

通过点击按钮,可以发现App组件和CustomeInput组件中的msg/modelValue发生了改变。

<template>
  <p>
    <strong>{{ modelValue }}</strong>
  </p>
  <p><button @click="update">更改msg</button></p>
</template>

<script>
export default {
  // props 接收
  props: ['modelValue'],
  // 注册自定义事件
  emits: ['update:modelValue'],
  methods: {
    update() {
      this.$emit('update:modelValue', 'fct');
    }
  }
};
</script>

2.3 在自定义组件内与父组件的 msg 保持同步

在自定义组件中使用input,实现组件内输入的内容动态绑定App组件的msg

2.3.1 v-model 手动展开
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>
2.3.2 v-model + 计算属性
<template>
 <input v-model="modelValueComputed" />
</template>
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    modelValueComputed: {
      get() {
        return this.modelValue;
      },
      set(value) {
        this.$emit('update:modelValue', value);
      }
    }
  }
}
</script>

3. 组件上多个 v-model

当组件上有一个v-model时,都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件。

当一个组件上有多个v-model时如何区分传入组件的prop?

v-model其实有参数:

v-model='msg'其实等于v-model:modelValue='msg'

组件上的多个v-model:

// App.vue
<h2>{{ msg }}</h2>
<h2>{{ title }}</h2>
<MyComponent v-model='msg' v-model:title="title" />

子组件中不仅需要声明modelValueprop 还得声明titleprop。并通过触发 update:title 事件更新父组件值。

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
  <input
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>
<script>
export default {
  props: ['title', 'modelValue'],
  emits: ['update:title', 'update:modelValue']
}
</script>
posted @ 2022-12-28 18:22  青柠i  阅读(119)  评论(0编辑  收藏  举报