响应式原理不同:Vue2 基于 Object.defineProperty劫持对象属性;Vue3 基于 Proxy劫持整个对象,支持数组、新增/删除属性监听。
编程范式不同:Vue2 选项式API,代码分散;Vue3 组合式API,逻辑聚合、复用性更强。
生命周期差异:Vue3 废弃 beforeCreate/created,统一在setup中编写,生命周期函数需手动导入。
性能优化升级:Vue3 新增静态提升、PatchFlag标记、事件缓存,大幅减少DOM比对开销。
TS支持:Vue3 原生TS重构,类型推导完善;Vue2对TS适配简陋。
可监听数组所有原生变更(新增、删除、下标修改),无需Vue2重写数组方法的hack方案。
可监听对象新增、删除属性,无需 $set手动处理响应式。
// 极简Vue双向绑定实现class MiniVue {constructor() {this.data = { name: '' }this.observe(this.data)}// 数据劫持observe(obj) {Object.keys(obj).forEach(key => {let value = obj[key]Object.defineProperty(obj, key, {get() {return value},set(newVal) {value = newVal// 数据更新,同步刷新视图document.getElementById('input').value = newValdocument.getElementById('text').innerText = newVal}})})}}// 实例化使用const vm = new MiniVue()// 视图变更同步更新数据document.getElementById('input').addEventListener('input', (e) => {vm.data.name = e.target.value})
onBeforeMount:DOM未挂载,无法操作DOM
onMounted:DOM挂载完成,适合接口请求、初始化定时器、操作DOM
onBeforeUpdate:数据更新前触发
onUpdated:数据&DOM更新完成,可获取最新DOM结构
onBeforeUnmount:组件卸载前,清除定时器、解绑事件,防止内存泄漏
onUnmounted:组件完全卸载
父子通信:父传子 props、子传父 emit
祖孙跨级通信:provide / inject
兄弟/全局跨级:Vuex / Pinia 全局状态管理
简单轻量跨级:mitt 事件总线
特殊通信:$refs 获取子组件实例、$attrs 透传属性
v-if:真实销毁/重建DOM节点,切换开销大、初始渲染开销小,适合极少切换、一次性渲染场景
v-show:仅通过CSS display属性切换显隐,DOM不销毁,初始开销大、切换开销小,适合频繁切换场景
state:全局数据源,存储公共状态
mutations:唯一同步修改state的入口
actions:处理异步逻辑,通过commit调用mutations
getters:全局计算属性,缓存加工state数据
modules:模块化拆分,解决state臃肿问题
// store/index.js Vuex完整配置import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {userName: 'Vuex测试用户',count: 0},mutations: {setCount(state, num) {state.count = num},setUserName(state, name) {state.userName = name}},actions: {// 异步模拟接口请求async getUserInfo({ commit }) {const res = await Promise.resolve('后端异步获取用户名')commit('setUserName', res)}},getters: {userText: (state) => `${state.userName},当前计数:${state.count}`},modules: {}})
// 组件中使用Vuexexport default {methods: {// 同步修改状态changeCount() {this.$store.commit('setCount', 100)},// 异步修改状态async getUser() {await this.$store.dispatch('getUserInfo')}}}
对比维度 | Vuex | Pinia |
|---|---|---|
适配版本 | Vue2主力,Vue3兼容 | Vue3官方推荐,完全替代Vuex |
核心结构 | 五大核心,结构繁琐冗余 | 舍弃mutations,仅保留state/getters/actions,极简 |
同步异步 | 同步、异步强制拆分 | actions支持同步+异步,无需拆分 |
TS支持 | 类型推导繁琐,需额外适配 | 原生TS重构,自动类型推导 |
模块化 | 需手动开启命名空间,嵌套复杂 | 天然模块化,每个仓库独立隔离 |
使用体验 | 代码冗余、写法繁琐 | API极简,支持直接修改state |
// stores/user.ts Pinia仓库import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {// 全局状态state: () => ({userName: '前端开发者',age: 22}),// 计算属性getters: {userInfo: (state) => `${state.userName}-${state.age}岁`},// 同步/异步方法统一存放actions: {// 同步修改setName(name: string) {this.userName = name},// 异步修改async fetchUser() {const res = await Promise.resolve('异步请求用户数据')this.userName = '异步更新用户名'}}})
类组件:基于class定义,自带生命周期、存在this指向问题、代码冗余复杂
函数组件:纯函数设计,无this干扰,配合Hooks实现状态和生命周期,代码简洁轻量化
现状:目前企业级React项目全部采用函数组件+Hooks开发
useState:定义组件响应式状态,实现数据更新视图刷新
useEffect:处理组件副作用(接口请求、定时器、DOM操作、事件监听)
useRef:获取真实DOM、保存组件不变的变量
useMemo:缓存复杂计算结果,避免每次渲染重复计算
useCallback:缓存函数,避免子组件无效重渲染
useContext:实现跨层级组件状态共享
空依赖数组:组件挂载、卸载时各执行一次
有依赖项:依赖项数值/引用变化,重新执行副作用
无依赖参数:组件每次渲染都会执行
React.memo:组件缓存,props未变化时,组件不重渲染
useCallback:缓存父组件传递的函数props,保证引用地址不变
useMemo:缓存复杂计算结果,避免重复计算
同层比对:仅对比同级节点,不跨层级比对,贴合业务场景
类型优先校验:节点类型不同直接销毁重建,不做复用
Key唯一匹配:通过key绑定节点身份,实现节点精准复用、移位、删除
Key作用:标记节点唯一性,辅助Diff精准匹配节点,减少无效DOM渲染,提升更新性能。
index弊端:列表存在删除、前置新增、排序时,index会偏移,导致节点复用错乱、视图数据错位、产生多余DOM更新。
Diff粒度:Vue Diff更精细,可对比属性、样式、文本;React仅做节点层级比对。
更新机制:Vue依靠响应式精准定位更新组件;React自上而下全量渲染,需手动优化。
编译优化:Vue3有PatchFlag、静态提升等编译优化,只比对动态节点;React无编译优化,纯运行时Diff。
造成性能损耗,违背Diff复用节点的设计初衷。
编程思想:Vue 模板驱动、声明式编程;React JSX语法、函数式编程
响应式机制:Vue 自动依赖收集,精准更新;React 自上而下强制渲染,需手动优化
更新粒度:Vue 组件级精准更新;React 整树自上而下渲染
上手难度:Vue低门槛、易上手;React工程化更强、门槛更高
受控组件:表单值由state管控,完全可控,企业开发主流
非受控组件:表单值由DOM管控,通过ref取值,适合简单一次性场景
import { useState, useRef } from 'react'// 受控组件function ControlledInput() {const [value, setValue] = useState('')return (<inputvalue={value}onChange={(e) => setValue(e.target.value)}placeholder="受控组件"/>)}// 非受控组件function UnControlledInput() {const inputRef = useRef(null)const getValue = () => {console.log(inputRef.current.value)}return (<><inputref={inputRef}placeholder="非受控组件"/><buttononClick={getValue}>获取值</button></>)}
import { useState } from 'react'// 父组件function Parent() {const [msg, setMsg] = useState('父组件原始数据')return <Childmsg={msg}changeMsg={setMsg} />}// 子组件function Child({ msg, changeMsg }) {return (<div><p>接收数据:{msg}</p><buttononClick={() => changeMsg('父组件数据已更新')}>修改数据</button></div>)}
import { useState, useEffect } from 'react'// 自定义防抖Hooksexport default function useDebounceValue(value, delay = 500) {const [debounceVal, setDebounceVal] = useState(value)useEffect(() => {const timer = setTimeout(() => {setDebounceVal(value)}, delay)return () => clearTimeout(timer)}, [value, delay])return debounceVal}// 业务组件使用function SearchInput() {const [val, setVal] = useState('')const debounceVal = useDebounceValue(val)return <inputvalue={val}onChange={e => setVal(e.target.value)} placeholder="防抖搜索"/>}
useMemo:缓存计算结果,避免复杂计算重复执行
useCallback:缓存函数本身,配合React.memo避免子组件无效更新
let name = 'window'const obj = {name: 'obj对象',fn: function() {console.log(this.name)},arrowFn: () => {console.log(this.name)}}obj.fn() // 隐式绑定:obj对象const f = obj.fnf() // 默认绑定:windowobj.arrowFn() // 箭头函数:继承外层this = window
call:立即执行,参数逐个传入
apply:立即执行,参数以数组形式传入
bind:不立即执行,返回新函数,支持柯里化传参
function test(a, b) {console.log(this.name, a, b)}const obj = { name: '测试对象' }test.call(obj, 10, 20)test.apply(obj, [30, 40])const newTest = test.bind(obj, 50, 60)newTest()// 经典妙用:数组取最大值const arr = [1,9,3,7,5]console.log(Math.max.apply(null, arr))
function debounce(fn, delay = 300) {let timer = nullreturn function(...args) {if(timer) clearTimeout(timer)timer = setTimeout(() => {fn.apply(this, args)timer = null}, delay)}}
function throttle(fn, interval = 300) {let flag = falsereturn function(...args) {if(flag) returnflag = truefn.apply(this, args)setTimeout(() => {flag = false}, interval)}}
// ES6最优最简写法function uniqueArr(arr) {return [...new Set(arr)]}// 传统遍历兜底写法function uniqueArr2(arr) {const res = []arr.forEach(item => {if(!res.includes(item)) res.push(item)})return res}
// 递归手写扁平化function flatArr(arr) {let res = []arr.forEach(item => {Array.isArray(item) ? res = res.concat(flatArr(item)) : res.push(item)})return res}// 原生极简APIarr.flat(Infinity)
function deepClone(obj) {// 基础类型直接返回if(typeof obj !== 'object' || obj === null) return obj// 区分数组和对象const newObj = Array.isArray(obj) ? [] : {}// 递归遍历拷贝for(let key in obj) {if(obj.hasOwnProperty(key)) {newObj[key] = deepClone(obj[key])}}return newObj}
// 1. 冒泡排序(基础必背)function bubbleSort(arr) {const len = arr.lengthfor(let i = 0; i < len - 1; i++) {for(let j = 0; j < len - 1 - i; j++) {if(arr[j] > arr[j+1]) {[arr[j], arr[j+1]] = [arr[j+1], arr[j]]}}}return arr}// 2. 快速排序(进阶最优排序 O(nlogn))function quickSort(arr) {if(arr.length < 2) return arrconst pivot = arr[0]const left = [], right = []for(let i = 1; i < arr.length; i++) {arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i])}return quickSort(left).concat(pivot, quickSort(right))}
扩展性:interface 支持声明合并;type 不支持重复声明
能力范围:type 支持联合、交叉、字面量等复杂类型;interface仅用于定义对象结构
继承方式:interface 通过extends继承;type 通过&交叉继承
使用场景:组件Props、对象结构用interface;复杂类型、工具类型用type
// interface声明合并interface User { name: string }interface User { age: number }// 最终合并:{name:string, age:number}// type联合类型type Status = 'success' | 'error' | 'loading'
// 基础泛型function getArr<T>(arr: T[]): T[] {return arr}// 泛型约束:限制必须包含length属性function getLength<T extends {length: number}>(val: T): number {return val.length}getLength('123') // 合法getLength([1,2,3]) // 合法getLength(123) // 不合法,number类型没有length属性
void:函数无返回值,仅修饰函数返回
never:永远无返回值(报错、死循环),代表类型不存在
any:关闭TS类型校验,项目禁止滥用
unknown:安全的any,必须类型校验后才能使用
const obj = { name: '前端' }// 可选链:安全读取深层属性,不报错console.log(obj?.age?.num)// 空值合并:仅null/undefined生效,0、''、false不覆盖const n1 = 0 ?? 100 // 0const n2 = null ?? 100 // 100// 对比||:会误判0、空字符串const n3 = 0 || 100 // 100
联合类型 |或,满足其一即可(多选一)
交叉类型& 且,合并所有属性,必须全部满足
// 联合类型type Status = 'success' | 'error'// 交叉类型interface A { name: string }interface B { age: number }type C = A & Bconst person: C = { name: 'test', age: 22 }