脚手架
基本使用
- 模板语法
- 插值
- 指令
<template>
<div>
<p>文本插值 {{ message }}</p>
<p>JS 表达式 {{ flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)</p>
<p :id="dynamicId">动态属性 id</p>
<hr />
<p v-html="rawHtml">
<span>有 xss 风险</span>
<span>【注意】使用 v-html 之后,将会覆盖子元素</span>
</p>
<!-- 其他常用指令后面讲 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜体</i>',
dynamicId: `id-${Date.now()}`,
}
},
}
</script>
computed 和 watch
- computed: 计算属性,依赖其他属性计算值。
- watch: 监听属性变化,执行异步操作或开销较大的操作。
<template>
<div>
<input v-model="name" />
<input v-model="info.city" />
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
info: {
city: '北京',
},
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true, // 深度监听
},
},
}
</script>
<template>
<div>
<p>num {{ num }}</p>
<p>double1 {{ double1 }}</p>
<input v-model="double2" />
</div>
</template>
<script>
export default {
data() {
return {
num: 20,
}
},
computed: {
double1() {
return this.num * 2
},
double2: {
get() {
return this.num * 2
},
set(val) {
this.num = val / 2
},
},
},
}
</script>
- class 和 style
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 转换为驼峰式
color: 'red',
backgroundColor: '#ccc', // 转换为驼峰式
},
}
},
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
- 条件渲染
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a',
}
},
}
</script>
- 循环
<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 }}
</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>
- 事件
<template>
<div>
<p>{{ num }}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
// 事件修饰符: .stop .prevent .capture .self .once .passive
// 按键修饰符: .enter .tab .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right
export default {
data() {
return {
num: 0,
}
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log('event', event, event.__proto__.constructor) // 是原生的 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>
- 表单
<template>
<div>
<p>输入框: {{ name }}</p>
<input type="text" v-model.trim="name" />
<input type="text" v-model.lazy="name" />
<input type="text" v-model.number="age" />
<p>多行文本: {{ desc }}</p>
<textarea v-model="desc"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框 {{ checked }}</p>
<input type="checkbox" v-model="checked" />
<p>多个复选框 {{ checkedNames }}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
<p>单选 {{ gender }}</p>
<input type="radio" id="male" value="male" v-model="gender" />
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender" />
<label for="female">女</label>
<p>下拉列表选择 {{ selected }}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选) {{ selectedList }}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '双越',
age: 18,
desc: '自我介绍',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: [],
}
},
}
</script>
组件
- 生命周期
<template>
<div>
<Input @add="addHandler" />
<List :list="list" @delete="deleteHandler" />
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List,
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1',
},
{
id: 'id-2',
title: '标题2',
},
],
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title,
})
},
deleteHandler(id) {
this.list = this.list.filter((item) => item.id !== id)
},
},
created() {
// eslint-disable-next-line
console.log('index created')
},
mounted() {
// eslint-disable-next-line
console.log('index mounted')
},
beforeUpdate() {
// eslint-disable-next-line
console.log('index before update')
},
updated() {
// eslint-disable-next-line
console.log('index updated')
},
}
</script>
- props 类型和默认值
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{ item.title }}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
},
},
},
data() {
return {}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
},
},
created() {
// eslint-disable-next-line
console.log('list created')
},
mounted() {
// eslint-disable-next-line
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
// eslint-disable-next-line
console.log('list before update')
},
updated() {
// eslint-disable-next-line
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
},
}
</script>
$on 和 $emit
- $on: 绑定自定义事件
- $emit: 触发自定义事件
// event.js
import Vue from 'vue'
export default new Vue()
<template>
<div>
<input type="text" v-model="title" />
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: '',
}
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
},
},
}
</script>
高级特性
- 自定义 v-model
<template>
<!-- 例如:vue 颜色选择 -->
<input
type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
/>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和 model.event1 要对应起来
3. text1 属性对应起来
-->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1',
},
props: {
text1: String,
default() {
return ''
},
},
}
</script>
- nextTick
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{ item }}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c'],
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log(ulElem.childNodes.length)
})
},
},
}
</script>
refs (略)
插槽
- 基本插槽:
<slot></slot>
<template> <a :href="url"> <slot> 默认内容,即父组件没设置内容时,这里显示 </slot> </a> </template> <script> export default { props: ['url'], data() { return {} }, } </script>
- 具名插槽:
<slot name="name"></slot>
<template> <div> <slot name="header"></slot> <div>内容</div> <slot name="footer"></slot> </div> </template>
- 作用域插槽
<template> <a :href="url"> <slot :slotData="website"> {{ website.subTitle }} <!-- 默认值显示 subTitle ,即父组件不传内容时 --> </slot> </a> </template> <script> export default { props: ['url'], data() { return { website: { url: 'http://wangEditor.com/', title: 'wangEditor', subTitle: '轻量级富文本编辑器', }, } }, } </script> // 使用 <ScopedSlotDemo :url="website.url"> <template v-slot="slotProps"> {{slotProps.slotData.title}} </template> </ScopedSlotDemo>
- 基本插槽:
动态组件
- 动态组件:
<component :is="currentComponent"></component>
- 缓存组件:
<keep-alive></keep-alive>
- 异步组件:
const component = () => import('./component.vue')
- mixin:
mixins: [mixin1, mixin2]
,用于组件间共享代码。
vue3.x 新特性
- craeteApp:
const app = Vue.createApp({})
- emits 属性:
emits: ['add']
- 多事件监听:
@add="add1" @add="add2"
- fragment:
<template></template>
- teleport:
<teleport to="body"></teleport>
- suspense:
<suspense></suspense>
: 异步组件加载时的 loading - 移除了过滤器:
filters: { filter1() {} }
- 移除了 v-once:
<div v-once></div>
- 移除了 .sync 改为 v-model:
<input v-model="text1" />
- compisition api:
- setup:
setup() { return { text1: 'text1' } }
- ref:
const text1 = ref('text1')
- reactive:
const state = reactive({ text1: 'text1' })
- readonly:
const state = readonly({ text1: 'text1' })
- computed:
const text1 = computed(() => state.text1)
- watch:
watch(() => state.text1, (newVal, oldVal) => {})
- 生命周期:
onBeforeMount() {}
,onMounted() {}
,onBeforeUpdate() {}
,onUpdated() {}
,onBeforeUnmount() {}
,onUnmounted() {}
- watchEffect:
watchEffect(() => state.text1)
: 监听响应式数据的变化, 会立即执行一次
- setup:
- defineExpose:
defineExpose({ text1 })
: 暴露给父组件的数据 - 异步组件方式:
const component = defineAsyncComponent(() => import('./component.vue'))
- 使用
proxy
代替Object.defineProperty
,proxy
优点:可以监听对象的新增属性,删除属性,数组的索引和 length 属性 - 编译优化:
- hoistStatic:静态节点提升
- cacheHandlers:事件监听缓存
- PatchFlag:标记节点类型
- ssrOptimiztion:ssr 优化
- treeShake:摇树优化
周边工具
vuex:状态管理, vuex
state:状态
getters:计算属性
mutations:同步修改 state
actions:异步修改 state
modules:模块化
plugins:插件
- 用于 vuex:
- createLogger:打印日志
- createPersistedState:持久化。 vuex-persist
- 用于 vuex:
用于 vue:
- dispatch:触发 action
- commit:触发 mutation
- mapState:映射 state
- mapGetters:映射 getters
- mapMutations:映射 mutations
- mapActions:映射 actions
pinia:状态管理, pinia
- state:状态
- getters:计算属性
- actions:异步修改 state
- 插件
- 用于 pinia:
- createLogger:打印日志
- pinia-plugin-persistedstate:持久化。 pinia-plugin-persistedstate
- 用于 pinia:
- 用于 vue:
- useStore:获取 store
- mapState:映射 state
- mapGetters:映射 getters
- mapActions:映射 actions
vue-router:路由管理, vue-router
- 路由模式:hash、history
- 路由守卫:beforeEach、beforeResolve、afterEach
- 路由懒加载:
const component = () => import('./component.vue')
- 路由传参:params、query
- params:
/user/:id
,this.$route.params.id
- query:
/user?id=1
,this.$route.query.id
- params:
- 路由跳转:
this.$router.push({path: '/user'})
,this.$router.push({name: 'user'})
,this.$router.push({path: '/user', query: {id: 1}})
- 路由重定向:
{path: '/user', redirect: '/user/1'}
,{path: '/user', redirect: {name: 'user'}}
- 路由别名:
{path: '/user', alias: '/user/1'}
,{path: '/user', alias: {name: 'user'}}
- 路由嵌套:
children: [{path: 'profile', component: Profile}]
,<router-view></router-view>
- 路由元信息:
{path: '/user', meta: {title: '用户'}}
,this.$route.meta.title
- 路由过渡动画:
<transition></transition>
axios:网络请求, axios
// axios 简单封装
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:3000',
timeout: 1000,
})
instance.interceptors.request.use(
(config) => {
// 请求前
return config
},
(error) => {
// 请求错误
return Promise.reject(error)
}
)
instance.interceptors.response.use(
(response) => {
// 响应后
return response.data
},
(error) => {
// 响应错误
return Promise.reject(error)
}
)
- element-ui:UI 组件库, element-ui
- element-plus:UI 组件库, element-plus
- ant-design-vue:UI 组件库, ant-design-vue
- vue-i18n:国际化, vue-i18n
- nprogress:进度条, nprogress
- dayjs:日期处理, dayjs
- lodash:工具库, lodash
- moment:日期处理, moment
- echarts:图表, echarts
- v-charts:图表, v-charts
- vue-echarts:图表, vue-echarts
- vueUse:vueUse, vueUse 中文
TypeScript 整合
- 选项式 Api 中使用:
<template>
<div>
<h2>9-6Vue选项式API中如何使用TS</h2>
<div>count: {{ count }}</div>
<div>msg: {{ msg }}</div>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
<childCom
:msg="msg"
:count="count"
:list="list"
@custom-click="handleClick"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import childCom from './ChildCom.vue'
type Data = string
type Count = number | string
interface List {
name: string
age: number
}
export default defineComponent({
components: {
childCom,
},
data() {
return {
count: 0 as Count,
msg: 'Hello World' as Data,
list: [] as List[],
}
},
mounted() {
this.list.push({ name: 'zhangsan', age: 18 })
},
computed: {
doubleCount(): Count {
if (typeof this.count === 'number') {
return this.count * 2
} else {
return this.count
}
},
},
methods: {
handleClick(data: string) {
console.log('我是父组件' + data)
},
},
setup() {
return {}
},
})
</script>
<style scoped></style>
<template>
<div>
<h2>我是子组件</h2>
<div>count: {{ count }}</div>
<div>msg: {{ msg }}</div>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
<button @click="handleClick">子组件按钮</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
export default defineComponent({
props: {
msg: String,
count: [Number, String],
list: {
type: Array as PropType<{ name: string; age: number }[]>,
default: () => [],
},
},
// emits: ['custom-click'],
emits: {
'custom-click': (data: string) => data.length > 0,
},
methods: {
handleClick() {
this.$emit('custom-click', '我是子组件')
},
},
setup() {
return {}
},
})
</script>
<style scoped></style>
- 组合式 Api 中使用:
<template>
<div>
<h2>9-8Vue组合式API中如何使用TS</h2>
<div>count: {{ count }}</div>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
<div>doubleCount: {{ doubleCount }}</div>
<ChildSetup :count="count" :list="list" @custom-click="handleClick" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import ChildSetup from './ChildSetup.vue'
type Count = number | string
let count = ref<Count>(0)
count.value = 'hello composition api'
interface List {
name: string
age: number
}
let list = ref<List[]>([])
list.value.push({ name: 'zhangsan', age: 18 })
let doubleCount = computed(() => {
if (typeof count.value === 'number') {
return count.value * 2
} else {
return count.value + count.value
}
})
const handleClick = (data: string) => {
count.value = 100
console.log(data)
}
</script>
<style scoped></style>
<template>
<div>
<h2>我是子组件</h2>
<div>count: {{ count }}</div>
<div>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
<button @click="handleClick">调用父组件方法</button>
</div>
</template>
<script setup lang="ts">
import { defineProps, toRefs, defineEmits } from 'vue'
// import type { PropType } from 'vue'
interface Props {
count: string | number
list: {
name: string
age: number
}[]
}
let props = defineProps<Props>()
interface Emits {
(e: 'custom-click', data: string): void
}
let emits = defineEmits<Emits>()
let { count, list } = toRefs(props)
const handleClick = () => {
emits('custom-click', '我是子组件')
}
</script>
<style scoped></style>
- vue-router 中使用:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
// meta 类型检测
declare module 'vue-router' {
interface RouteMeta {
// 是可选的
isAuth?: boolean
// 必须的
requiresAuth: boolean
}
}
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView,
meta: {
isAuth: true,
requiresAuth: true,
},
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
},
{
path: '/optionApi',
name: 'optionApi',
component: () =>
import(/* webpackChunkName: "optionApi" */ '../views/OptionApi.vue'),
},
{
path: '/compositionApi',
name: 'compositionApi',
component: () =>
import(
/* webpackChunkName: "compositionApi" */ '../views/CompositionApi.vue'
),
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
- vuex 中使用:
import { InjectionKey } from 'vue'
import { createStore, Store, useStore as BaseUseStore } from 'vuex'
//modules
import users, { UsersState } from './modules/users'
// 定义 state 类型
export interface State {
count: number
}
interface StateAll extends State {
users: UsersState
}
// 定义注入 key
export const key: InjectionKey<Store<StateAll>> = Symbol()
export const store = createStore<State>({
state: {
count: 100,
},
getters: {
doubleCount(state) {
return state.count * 2
},
},
mutations: {
add(state, payload: number) {
state.count += payload
},
},
modules: {
users,
},
})
export default store
import type { MutationTree, ActionTree, GetterTree } from 'vuex'
import type { State } from '../index'
export interface UsersState {
username: string
age: number
}
const state: UsersState = {
username: 'xiaoming',
age: 20,
}
const mutations: MutationTree<UsersState> = {
// change(state){
// }
}
const actions: ActionTree<UsersState, State> = {}
const getters: GetterTree<UsersState, State> = {
doubleAge(state) {
return state.age * 2
},
}
export default {
namespaced: true,
state,
mutations,
actions,
getters,
}
- pinia 中使用:
import { defineStore } from 'pinia'
interface Counter {
count: number
}
export const useCounterStore = defineStore({
id: 'counter',
state: (): Counter => ({
count: 0,
}),
actions: {
increment(n: number) {
this.count += n
},
},
})
原理
响应式原理
- 核心 api:
- vue2.x:
Object.defineProperty
- 缺点:
- 无法监听数组变化
- 深度监听需要递归遍历
- 初始化时需要遍历对象所有属性
- 缺点:
- vue3.x:
Proxy
- vue2.x:
- 核心 api:
vdom 与 diff 算法
vdom
- 什么是 vdom: 用 js 对象来描述真实 dom。
- 为什么要使用 vdom: 为了提高渲染性能。
- vdom 的优缺点
- 优点:
- 跨平台,一套代码可以运行在浏览器、小程序、node 等平台。
- 性能高,通过 diff 算法,最小化 dom 操作。
- 缺点:
- 首次渲染大量 dom 时,由于需要创建 vdom,性能不如 innerHTML。
- 需要额外的编译过程,增加了开发成本。
- 优点:
vue 使用 vdom:
- 模板编译成 render 函数, h 函数返回 vdom
- render 函数返回 vdom。
- vdom 与旧的 vdom 进行 diff 操作
- 更新真实 dom
diff 算法策略:
- 同层比较,不跨级比较
- tag 不相同,直接替换
- tag 相同,key 相同,复用 dom
- tag 相同,key 不同,创建新 dom,删除旧 dom
模板编译
模板编译过程
- parse 解析模板,生成 ast
- optimize 优化 ast
- generate 生成 render 函数
模板编译原理
- parse 解析模板,生成 ast
- 1.1 parse 解析模板,生成 ast
- 1.2 优化 ast
- 1.3 生成 render 函数
- optimize 优化 ast
- 2.1 静态节点标记
- 2.2 静态根节点标记
- 2.3 优化静态根节点
- generate 生成 render 函数
- 3.1 生成字符串形式的代码
- 3.2 生成 render 函数
初次渲染
- 模板编译成 render 函数
- render 函数返回 vdom
更新过程
- render 函数返回新的 vdom
- vdom 与旧的 vdom 进行 diff 操作
- 更新真实 dom
异步渲染
- 汇总数据变化,一次性更新视图
- 通过 nextTick 实现异步渲染
路由原理
- hash 模式
- 监听 hashchange 事件
- 根据当前 hash 值,渲染对应组件
- history 模式
- 监听 popstate 事件
- 根据当前 path 值,渲染对应组件
- hash 模式
面试题
为何 v-for 中要用 key?
- 为了提高性能,减少 dom 操作
- 为了方便 vue 进行虚拟 dom 的 diff 操作,是否是
sameNode
,提高渲染性能
- 为了方便 vue 进行虚拟 dom 的 diff 操作,是否是
vue 常见性能优化方式?
- 合理使用 v-if 和 v-show
- 合理使用 computed 和 watch
- 合理使用 keep-alive
- 合理使用异步组件
- 合理使用路由懒加载
- 自定义事件,dom 事件,及时销毁
- 合理使用 v-for 和 key, 避免和 v-if 一起使用
- 合理使用 style 和 class
- data 中的数据,不要定义过多的数据,会增加内存开销
vue 常见的响应式原理?
- vue2.x:
Object.defineProperty
- vue3.x:
Proxy
- vue2.x:
Vue3.x 比 Vue2.x 有哪些优势?
- 性能更好
- 体积更小
- 更易于维护
- 更易于使用
- 更好的支持 TS
CompositionApi 与 React Hooks 有何区别?
- CompositionApi 是 vue3.x 中的 api,React Hooks 是 react 中的 api
- CompositionApi 是函数式的,React Hooks 是声明式的
- CompositionApi setup 只会执行一次,React Hooks 每次渲染都会执行
- CompositionApi 无 useMemo(缓存数据),useCallback(缓存函数),useRef(缓存 dom),React Hooks 有
- CompositionApi 无需遵循顺序,React Hooks 需要遵循顺序