脚手架

基本使用

  • 模板语法
    • 插值
    • 指令
<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): 监听响应式数据的变化, 会立即执行一次
  • defineExpose: defineExpose({ text1 }): 暴露给父组件的数据
  • 异步组件方式: const component = defineAsyncComponent(() => import('./component.vue'))
  • 使用 proxy 代替 Object.definePropertyproxy 优点:可以监听对象的新增属性,删除属性,数组的索引和 length 属性
  • 编译优化:
    • hoistStatic:静态节点提升
    • cacheHandlers:事件监听缓存
    • PatchFlag:标记节点类型
    • ssrOptimiztion:ssr 优化
    • treeShake:摇树优化

周边工具

  • vuex:状态管理, vuexopen in new window

    • state:状态

    • getters:计算属性

    • mutations:同步修改 state

    • actions:异步修改 state

    • modules:模块化

    • plugins:插件

    • 用于 vue:

      • dispatch:触发 action
      • commit:触发 mutation
      • mapState:映射 state
      • mapGetters:映射 getters
      • mapMutations:映射 mutations
      • mapActions:映射 actions
  • pinia:状态管理, piniaopen in new window

    • state:状态
    • getters:计算属性
    • actions:异步修改 state
    • 插件
    • 用于 vue:
      • useStore:获取 store
      • mapState:映射 state
      • mapGetters:映射 getters
      • mapActions:映射 actions
  • vue-router:路由管理, vue-routeropen in new window

    • 路由模式:hash、history
    • 路由守卫:beforeEach、beforeResolve、afterEach
    • 路由懒加载:const component = () => import('./component.vue')
    • 路由传参:params、query
      • params:/user/:idthis.$route.params.id
      • query:/user?id=1this.$route.query.id
    • 路由跳转: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:网络请求, axiosopen in new window

// 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)
  }
)

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
        • 缺点:
            1. 无法监听数组变化
            1. 深度监听需要递归遍历
            1. 初始化时需要遍历对象所有属性
      • vue3.x: Proxy
  • vdom 与 diff 算法

    • vdom

      • 什么是 vdom: 用 js 对象来描述真实 dom。
      • 为什么要使用 vdom: 为了提高渲染性能。
      • vdom 的优缺点
        • 优点:
            1. 跨平台,一套代码可以运行在浏览器、小程序、node 等平台。
            1. 性能高,通过 diff 算法,最小化 dom 操作。
        • 缺点:
            1. 首次渲染大量 dom 时,由于需要创建 vdom,性能不如 innerHTML。
            1. 需要额外的编译过程,增加了开发成本。
    • vue 使用 vdom:

        1. 模板编译成 render 函数, h 函数返回 vdom
        1. render 函数返回 vdom。
        1. vdom 与旧的 vdom 进行 diff 操作
        1. 更新真实 dom
    • diff 算法策略:

        1. 同层比较,不跨级比较
        1. tag 不相同,直接替换
        1. tag 相同,key 相同,复用 dom
        1. tag 相同,key 不同,创建新 dom,删除旧 dom
  • 模板编译

    • 模板编译过程

        1. parse 解析模板,生成 ast
        1. optimize 优化 ast
        1. generate 生成 render 函数
    • 模板编译原理

        1. parse 解析模板,生成 ast
        • 1.1 parse 解析模板,生成 ast
        • 1.2 优化 ast
        • 1.3 生成 render 函数
        1. optimize 优化 ast
        • 2.1 静态节点标记
        • 2.2 静态根节点标记
        • 2.3 优化静态根节点
        1. generate 生成 render 函数
        • 3.1 生成字符串形式的代码
        • 3.2 生成 render 函数
    • 初次渲染

        1. 模板编译成 render 函数
        1. render 函数返回 vdom
    • 更新过程

        1. render 函数返回新的 vdom
        1. vdom 与旧的 vdom 进行 diff 操作
        1. 更新真实 dom
    • 异步渲染

      • 汇总数据变化,一次性更新视图
      • 通过 nextTick 实现异步渲染
  • 路由原理

    • hash 模式
        1. 监听 hashchange 事件
        1. 根据当前 hash 值,渲染对应组件
    • history 模式
        1. 监听 popstate 事件
        1. 根据当前 path 值,渲染对应组件

面试题

  • 为何 v-for 中要用 key?

      1. 为了提高性能,减少 dom 操作
      1. 为了方便 vue 进行虚拟 dom 的 diff 操作,是否是sameNode,提高渲染性能
  • 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
  • Vue3.x 比 Vue2.x 有哪些优势?

      1. 性能更好
      1. 体积更小
      1. 更易于维护
      1. 更易于使用
      1. 更好的支持 TS
  • CompositionApi 与 React Hooks 有何区别?

      1. CompositionApi 是 vue3.x 中的 api,React Hooks 是 react 中的 api
      1. CompositionApi 是函数式的,React Hooks 是声明式的
      1. CompositionApi setup 只会执行一次,React Hooks 每次渲染都会执行
      1. CompositionApi 无 useMemo(缓存数据),useCallback(缓存函数),useRef(缓存 dom),React Hooks 有
      1. CompositionApi 无需遵循顺序,React Hooks 需要遵循顺序

思维笔记

参考资料

Vue 基础open in new window