脚手架
基本使用
- 模板语法 - 插值
- 指令
 
<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 需要遵循顺序
 
 
