跳到主要内容

17 篇博文 含有标签「Vue」

查看所有标签

keep-alive的使用场景及其特点

  1. 用于Vue性能优化。
  2. 缓存组件。
  3. 频繁切换,不需要重复渲染。
  4. keep-alive有include和exclude属性,这两个属性决定了哪些组件可以进入缓存。
  5. keep-alive还有一个max属性,通过它可以设置最大缓存数,当缓存的实例超过max的时候,vue会删除最久没有使用的缓存,属于LRU缓存策略。
  6. keep-alive其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是activated和deactivated,它们分别在组件激活和失活的时候触发。

将组件放入keep-alive中即可实现组件的缓存。

<keep-alive>
<KeepaliveA v-if="state === 'A' " />
<KeepaliveB v-if="state === 'B' "/>
<KeepaliveC v-if="state === 'C' "/>
</keep-alive>

keep-alive的原理?

keep-alive在实现上,维护了一个key数组和一个缓存对象,这个key数组记录目前缓存的组件的key值,如果这个组件没有指定key值,会自动生成一个唯一的key值,缓存对象会以key值为键,vnode为值,用于缓存组件对应的虚拟DOM,在keep-alive的渲染函数中,其基本逻辑是判断当前渲染的vnode是否有对应的缓存,如果有则从缓存中读取到对应的组件实例,没有就把它缓存。

keep-alive如何根据不同场景来更新数据?

可以利用keep-alive提供的include和exclude指定缓存哪些组件不缓存哪些组件,然后配合vuex等状态管理工具实现动态控制。


JustinVue阅读需 2 分钟

如何动态加载组件?

  1. 引入可能被使用的组件
import slotDemo from './components/slotDemo'
import HelloWorld from './components/HelloWorld'
  1. 通过动态属性is来读取组件名
<component  :is="comName"/>

如何异步加载组件?

  1. 通过import在注册组件的时候引入组件
components: {
FormDemo: () => import(`../xxxComponent`)
}
  1. 通过条件渲染来加载组件
<FormDemo v-if="showFormDemo" />
<button @click="showFormDemo = true" >show form demo</button>

JustinVue阅读需 1 分钟

一、基本使用

由父组件向子组件中传递数据,子组件通过slot进行接收,不传递则显示的是默认的内容。

  • 父组件
<template>
<div id="app">
<slotDemo :url="website.url">
{{website.title}}
</slotDemo>
</div>
</template>

<script>
import slotDemo from './components/slotDemo'

export default {
name: 'App',
data() {
return {
website: {
url: 'https://www.baidu.com',
title: '这是根节点的title'
}
}
},
components: {
slotDemo
}
}
</script>
  • 子组件
<template>
<a :href="url">
<slot>
默认内容
</slot>
</a>
</template>

<script>
export default {
name: 'slotDemo',
props: ['url'],
}
</script>

二、父组件通过slot接收子组件传递过来的值

  1. 子组件通过slot进行动态属性传值
<template>
<a :href="url">
<slot :slotData = "website">
默认内容
</slot>
</a>
</template>

<script>
export default {
name: 'slotDemo',
props: ['url'],
data() {
return {
website: {
url: '这是子组件的URL',
title: '子组件的title'
}
};
}
};
</script>
  1. 父组件通过template v-slot进行接收
<template>
<div id="app">
<slotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</slotDemo>
</div>
</template>

三、具名插槽

image.png


JustinVue阅读需 1 分钟

Vue生命周期的主要阶段

创建前后

  1. beforeCreate:实例刚在内存中被创建出来,此时还没有初始化好data和methods属性。
  2. created:实例已经在内存中创建好,此时data和methods已经创建好,此时还没有开始编译模板。

载入前后

  1. beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面上。
  2. mounted:此时已经编译好模板,并挂载到了页面指定的容器中。

更新前后

  1. beforeUpdate:状态更新之前执行这个函数,此时data中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点。
  2. updated:实例更新完毕之后调用此函数,此时data中的状态值和界面上显示的数据都已经完成了更新,界面已经被重新渲染好了。

销毁前后

  1. beforeDestroy:实例销毁之前调用,在这一步,实例仍然可用。
  2. destroyed阶段:Vue实例销毁后调用,调用后,Vue实例所指向的内容都会解除绑定,所有的事件监听器都会被移除,所有的子实例也会被销毁。

Vue父子组件的生命周期

image.png

Vue不同生命周期函数的作用

image.png

问题汇总

RQ1:Vue虚拟DOM渲染在哪个生命周期?

执行到beforeMount的时候虚拟DOM已经渲染完成,等beforeMount这个钩子函数执行完之后真实DOM已经渲染完成。

RQ2:created和mounted的区别,操作dom是在哪个里面操作?

created在模板渲染成HTML前调用,通常初始化某些属性值,然后再渲染视图。

mounted在模板渲染成HTML之后再调用,通常是初始化页面完成后,对DOM的节点进行操作。

RQ3:发起http请求在哪个钩子? 绑定事件在哪个钩子?取消事件在哪个钩子?

发起HTTP请求一般在created这个钩子函数中,但是如果设计到需要页面加载完成之后调用,则需要在mounted这个钩子中写。

绑定事件在mounted钩子函数中。

取消事件在beforeDestroy钩子函数中。


JustinVue阅读需 2 分钟

一、$nextTick有什么用?

  1. Vue是异步渲染的框架。
  2. data改变之后,DOM不会立刻渲染。
  3. $nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。
  4. 连续多次的异步渲染,$nextTick只会执行最后一次渲染后的结果。

二、$nextTick的原理

\$nextTick主要通过事件循环中的任务队列的方式异步执行传入的回调函数,首先会判断当前的执行环境是否支持Promise,MutationObserver,setImmediate,setTimeout。如果支持则创建对应的异步方法,这里的MutationObserver并不是监听DOM,而是利用其微任务特性。需要注意的是更新DOM的方法也是通过nextTick进行调用的,因此就可以实现传入$.nextTick的回调函数在DOM渲染完成之后执行这些微任务。

三、循环调用的话nextTick里面有容错机制吗?

多次调用 nextTick 会将方法存入队列 callbacks 中,通过这个异步方法清空当前队列。


JustinVue阅读需 1 分钟

一、实现数据从data到view的单向数据绑定

  1. 当data发生变化的时候首先触发数据劫持的setter函数
  set(newVal) {
value = newVal
Observe(value)
// 通知每个订阅者更新自己的文本
dep.notify()
}

setter函数先改变新的值,然后通知每一个订阅者。

  1. Dep类的notify函数通知每一个订阅者watcher
  notify() {
this.subs.forEach(watcher => watcher.update())
}
  1. Watcher类的update函数获取最新的值并调用渲染函数。
  update() {
const value = this.key.split('.').reduce((newObj,k) => newObj[k],this.vm)
this.cb(value);
}
  1. 渲染函数执行渲染
new Watcher(vm,execResult[1],(newValue) => {
node.textContent = text.replace(regMustache,newValue);
})

二、实现文本框的单向数据绑定

在渲染函数中首先判断当前节点是否为input并且含有属性v-model,有则获取值并创建watcher实例。

// 判断当前的节点是否为input框
if (node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT') {
// 得到当前元素的所有属性节点
const attrs = Array.from(node.attributes);
const findResult = attrs.find(x => x.name === 'v-model')
if (findResult) {
// 获取当前v-model属性的值 v-model="name" v-model="info.a"
const expStr = findResult.value;
const value = expStr.split('.').reduce((newObj,k) => newObj[k],vm);
node.value = value;
// 创建Watcher的实例
new Watcher(vm,expStr,(newValue) => {
node.value = newValue;
})
}
}

三、实现文本框的双向数据绑定

实现文本框的双向数据绑定重点是在单向数据绑定的基础上,监听文本框的输入事件,拿到文本框的最新值,并将最新值更新到vm上即可。

// 判断当前的节点是否为input框
if (node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT') {
// 得到当前元素的所有属性节点
const attrs = Array.from(node.attributes);
const findResult = attrs.find(x => x.name === 'v-model')
if (findResult) {
// 获取当前v-model属性的值 v-model="name" v-model="info.a"
const expStr = findResult.value;
const value = expStr.split('.').reduce((newObj,k) => newObj[k],vm);
node.value = value;
// 创建Watcher的实例
new Watcher(vm,expStr,(newValue) => {
node.value = newValue;
})

// 监听文本框的input输入事件,拿到文本框的最新值,并把最新值更新到vm上即可
node.addEventListener('input',e => {
const keyArr = expStr.split('.');
const obj = keyArr.slice(0,keyArr.length-1).reduce((newObj,k) => newObj[k],vm);
obj[keyArr[keyArr.length - 1]] = e.target.value;
})
}
}

总结

Vue的双向数据绑定是数据劫持、模板编译、发布订阅模式等综合知识点的体现,是我们必须要学会并掌握的知识点。


JustinVue阅读需 2 分钟

需求分析

有时候,我们需要页面的所有数据都渲染完成后在获取数据,通过传统的方法无法获取到渲染后的数据,举个例子,页面中通过v-for渲染的li有12个,但是,我们却在异步获取数据的函数中无法通过document.querySelector拿到所有的数据,此时就需要使用nextTick.

案例分析

例如下面这个页面,我们想要实现的是先渲染页面,然后获取li的数量。

image.png

代码分析

请注意,下面的代码要写在获取口味的异步函数完成之后。(这一点很重要,直接放在mounted函数之中是没有作用的。)

  this.$axios.get(this.$config.apiUrl + "/flavorList").then((res) => {
console.log(res);
this.flavorList = res.data.result;
this.$nextTick(() => {
// 渲染整个页面之后,再执行下面的代码
this.initEvent();
});
});

原理分析

我们不仅要知道nextTick是如何使用的,还要知道nextTick的原理,这是一道面试常考的题目。

有时候,我们也许无法避免的需要直接对DOM进行操作,但是我们想要操作的是页面渲染完成后的结果,此时nextTick就可以帮助我们实现。

vue如何判断DOM是否更新完毕?

利用MutationObserver?而是事件循环。

MutationObserver是HTML5新增的一个API,这个API可以帮助我们监听DOM。但是通过vue实际上并不是通过MutationObserver,而是通过事件循环,让nextTick在UI render之后再执行,这样就能访问到更新后的DOM了。在时间循环中宏任务总是要等到微任务执行完毕之后再执行,当调用nextTick的时候,会在更新DOM的微任务队列后追加我们自己的回调函数,从而能够保证我们的回调函数是在DOM渲染完成之后被执行的。


Justinvue阅读需 2 分钟

全局绑定axios(在TypeScript中)

  1. 通过cnpm安装到项目中。
cnpm i axios --save
  1. 在main.ts中引入下面的接口和组件
import Axios,{AxiosInstance} from  'axios'
  1. 在main.ts中进行如下声明
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance
}
}
  1. 在方法中可以直接通过this.$axios进行调用。
requestData() {
this.$axios.get('https://s.itying.com/api/v1/login').then(res => {
console.log(res);
})
}

封装并全局绑定storage

封装storage

  1. 首先创建一个storage.ts文件

  2. 下面是storage.ts文件中的内容

export interface StorageInstance {
set(key: string,value: any): void;
get(key: string): any;
remove(key: string): void;
}

class StorageClass implements StorageInstance {
set(key: string, value: any): void {
localStorage.setItem(key,JSON.stringify(value));
}
get(key: string): any {
let temp = localStorage.getItem(key);
if (temp) {
return JSON.parse(temp);
}
return null;
}
remove(key: string): void {
localStorage.removeItem(key);
}
}
let Storge = new StorageClass();

export default Storage;

全局绑定storage

需要注意的是,我们全局绑定的是我们上一步暴露的storage。

  1. 引入已经封装好的storage。
import Storage,{StorageInstance} from './model/storage'
  1. 声明module
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance,
$storage: StorageInstance
}
}
  1. 挂载到全局
app.config.globalProperties.$storage = Storage
  1. 通过this.$storage即可访问
this.$storage.set("token",res.data.token)

封装接口地址

  1. 创建config.ts

  2. 在文件中暴露相关接口地址

export interface ConfigInstance {
apiUrl: string;
imgUrl: string;
}

class ConfigClass implements ConfigInstance {
apiUrl: string;
imgUrl: string;
constructor() {
this.apiUrl = 'https://s.itying.com/api/v1';
this.imgUrl = 'https://s.itying.com'
}
}

const Config = new ConfigClass();

export default Config;
  1. 在main.ts中进行声明与挂载
import Config,{ConfigInstance} from './model/config'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance,
$storage: StorageInstance,
$config: ConfigInstance
}
}
app.config.globalProperties.$config = Config
  1. 组件中获取只需通过this.$config.apiUrl即可。

总结

在vue中进行全局绑定是一个非常重要和常用的操作,以api请求地址为例,假如一个大型的vue项目每一个接口地址都是写死的,那么一旦api地址发生更改,这将给修改带来极大的困难,但是如果我们绑定到全局,修改起来就是十分简单的一件事情了,所以将一些常用属性和方法绑定到全局中是非常重要的。


Justinvue阅读需 2 分钟

一、安装

  1. 创建vue项目
vue create vue-test-demo
  1. 进入项目
cd vue-ts-demo
  1. 新增typescript
vue add typescript
  1. 跳过class语法

二、如何让组件能够使用TS语法?

  1. script标签的lang属性设置为ts。
<script lang="ts">
  1. 从vue中导入defineComponent
import { defineComponent } from 'vue';
  1. 组件对外暴露时需要通过defineComponent进行包裹。
export default defineComponent({
name: 'App',
components: {
Home
}
});

三、Vue中使用TS语法实例

定义接口并实现接口。

<script lang="ts">
import {defineComponent} from 'vue'
interface News{
title: string,
description: string,
count: number,
content?: string
}
let newsData: News= {
title: "这是一个新闻",
description: "这是新闻的描述",
count: 12
}
export default defineComponent({
data() {
return newsData
},
methods: {
setTitle(): void{
this.title = '123';
}
}
});
</script>

四、组合式API中使用TS

下文首先介绍reactive的使用注意事项。

第一种实现方式(参数)

interface User {
username: string;
age: number;
}
setup() {
let user: User = reactive({
username: "张三",
age: 20,
});
function setUsername(username: string) {
user.username = username;
}
return {
...toRefs(user),
setUsername
};
}

第二种实现方式(泛型)

  setup() {
let user = reactive<User>({
username: "张三",
age: 20,
});
function setUsername(username: string) {
user.username = username;
}
return {
...toRefs(user),
setUsername
};
}

第三种实现方式(通过as)

let user = reactive({
username: "张三",
age: 20,
}) as User;

下面介绍下ref的注意事项。

  1. ref不支持直接指定类型。

下面的是错误的形式。

let num: string = ref('666');
  1. ref支持泛型。
let num = ref<number | string>('666');

JustinVue阅读需 2 分钟

什么是antd?

antd是蚂蚁集团旗下开发的一款优秀的前端UI框架,目前这个框架支持Vue和React。这个框架主要用于开发企业级的后台产品,如果想开发移动端应用的话可以选择Ionic。

选择合适的官方文档

在antd vue官网上选择2.x的antd,因为这个版本对应的是vue3的。

image.png

使用步骤

  1. 在已有的antd项目中安装antd。
npm i --save ant-design-vue@next
  1. 在入口文件中引入antd以及antd.css,并挂载到vue身上。
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
app.use(Antd);
  1. 只需在组件的模板中直接使用即可。

添加图标

  1. 在script标签中引入图标
import {
HomeOutlined,
} from '@ant-design/icons-vue';
  1. 注册组件
  components: {
HomeOutlined
}
  1. 使用组件
<HomeOutlined />
  1. 可以通过行内样式的形式来改变组件的样式
<HomeOutlined style="color: blue" />
  1. 在按钮里加入图标(通过template)
<a-button type="primary" :size="size">
<template #icon>
<DownloadOutlined />
</template>
Download
</a-button>

注意:上面的这种写法依然要引入并注册。

表单组件

下面是vue结合antd的基础表单组件的实现效果和代码。

image.png

<template>
<div>
<ul class="input_list">
<li>姓名:<a-input v-model:value="userinfo.username"></a-input></li>
<li>年龄:<a-input v-model:value="userinfo.age"></a-input></li>
<li>
性别:
<a-radio-group v-model:value="userinfo.sex">
<a-radio value="男"></a-radio>
<a-radio value="女"></a-radio>
</a-radio-group>
</li>
<li>
爱好:
<span v-for="(item, index) in userinfo.hobbies" :key="index">
<a-checkbox v-model:checked="item.checked">{{
item.label
}}</a-checkbox>
</span>
</li>
<li>
城市:
<a-select
v-model:value="userinfo.selectedCity"
mode="tags"
style="width: 300px"
placeholder="选择城市"
:options="userinfo.options"
>
</a-select>
</li>
<li>
生日:
<a-date-picker v-model:value="userinfo.birthday" @change="pickerChange" />
</li>
</ul>

{{ userinfo }}
</div>
</template>

<script>
import { defineComponent } from "vue";
import moment from 'moment';
const dateFormat = "YYYY-MM-DD";
export default defineComponent({
data() {
return {
userinfo: {
username: "",
age: "",
sex: "男",
hobbies: [
{ label: "吃饭", checked: true },
{ label: "睡觉", checked: true },
{ label: "写代码", checked: false },
],
options: [
{ value: "北京" },
{ value: "上海" },
{ value: "广州" },
{ value: "深圳" },
],
selectedCity: ["北京"],
birthday: moment('2021-01-03', dateFormat),
},
};
},
methods: {
pickerChange(e) {
// console.log(e._d)
if (!e) return;
var oDate = new Date(e._d);
console.log(oDate.getTime());
},
},
});
</script>

<style lang="scss">
ul {
list-style: none;
}

.input_list {
padding: 10px;
li {
margin-bottom: 15px;
& > input {
width: 400px !important;
}
}
}
</style>

在Node.js中实现图片上传或文件

  1. 安装第三方依赖
npm i --save multer
  1. 引入multer模块
const multer = require('multer');
  1. 配置storage
var storage = multer.diskStorage({
//配置上传的目录
destination: async (req, file, cb)=>{
//1、获取当前日期 20200703
let day=sd.format(new Date(), 'YYYYMMDD');
// static/upload/20200703
let dir=path.join("static/upload",day)
//2、按照日期生成图片存储目录 mkdirp是一个异步方法
await mkdirp(dir)

cb(null, dir) //上传之前目录必须存在
},
//修改上传后的文件名
filename: (req, file, cb)=> {
//1、获取后缀名
let extname= path.extname(file.originalname);
//2、根据时间戳生成文件名
cb(null, Date.now()+extname)
}
  1. 使用multer
  • tools.js
const multer = require('multer');
const path = require('path');
const sd = require('silly-datetime');
const mkdirp = require('mkdirp')
let tools={
multer(){

var storage = multer.diskStorage({
//配置上传的目录
destination: async (req, file, cb)=>{
//1、获取当前日期 20200703
let day=sd.format(new Date(), 'YYYYMMDD');
// static/upload/20200703
let dir=path.join("static/upload",day)
//2、按照日期生成图片存储目录 mkdirp是一个异步方法
await mkdirp(dir)

cb(null, dir) //上传之前目录必须存在
},
//修改上传后的文件名
filename: (req, file, cb)=> {
//1、获取后缀名
let extname= path.extname(file.originalname);
//2、根据时间戳生成文件名
cb(null, Date.now()+extname)
}
})

var upload = multer({ storage: storage })

return upload;

},
md5(){

}
}

module.exports=tools
  • routers.js
router.post("/doAdd",tools.multer().single("pic"), (req, res) => {
//获取表单传过来的数据
res.send({
body: req.body,
file: req.file
});
})

JustinVue阅读需 3 分钟