目录

基础知识

项目要点

export default defineCOnfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  base: process.env.NODE_ENV === 'production' ? '/bms/' : '/' 
})

各种工具

Vue原理

<template>
  <div>{{ message }}</div>
</template>
// 以上模板经过编译后成为:
render() {
  return h('div', this.message)
}

// h(标签名, 属性, 子内容) 是 createElement 的缩写,用于创建虚拟 DOM 节点。 
// 传统写法(Options API + Composition API)
<script>
import { ref, reactive } from 'vue'
import MyComponent from './MyComponent.vue'

export default {
  components: { MyComponent },
  setup() {
    const count = ref(0)
    const message = reactive({
      text: 'Hello'
    })

    function increment() {
      count.value++
    }

    // 必须手动 return 才能在模板中使用
    return {
      count,
      message,
      increment
    }
  }
}
</script>

<template>
  <div>
    <h1>{{ message.text }}</h1>
    <p>计数:{{ count }}</p>
    <button @click="increment">+1</button>
    <MyComponent />
  </div>
</template>

// 使用 <script setup>(推荐)
<script setup>
import { ref, reactive } from 'vue'
import MyComponent from './MyComponent.vue'

// 不需要 setup() 函数
// 不需要 return
// 所有顶层变量、函数自动暴露给模板

const count = ref(0)
const message = reactive({
  text: 'Hello'
})

function increment() {
  count.value++
}
</script>

<template>
  <div>
    <h1>{{ message.text }}</h1>
    <p>计数:{{ count }}</p>
    <button @click="increment">+1</button>
    <MyComponent />
  </div>
</template>
// 选项式API:逻辑割裂——一个功能(如“计数器”)的代码分散在多个选项中
export default {
  data() { ... },      // 所有数据放这里
  methods: { ... },     // 所有方法放这里
  computed: { ... },    // 所有计算属性
  watch: { ... },       // 所有监听
  mounted() { ... }     // 所有生命周期
}

// 选项式API:一个功能的相关代码集中在一起
setup() {
  // 🔹 计数器功能
  const count = ref(0)
  const square = computed(() => count.value ** 2)
  const increment = () => count.value++
  watch(() => count.value, logChange)
  onMounted(() => init())

  // 🔹 另一个功能...
  const user = ref(null)
  const loadUser = () => { ... }

  return { count, square, increment, user }
}

Vue2基础

let body = document.querySelector('body')
let node = document.createElement('script')
node.src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js";
let original = document.getElementsByTagName('script')
if(original.length === 0){
  body.appendChild(node);
}
var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data,
  watch: {}
})
vm.$data === data  // true
vm.$el === document.getElementById('example')` // true
// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
  // 这个回调将在 `vm.a` 改变后调用
})
🔥warning

不要在选项 property 或回调上使用箭头函数,比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())。因为箭头函数并没有 this,this 会作为变量一直向上级词法作用域查找,直至找到为止,经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

生命周期钩子图

模板语法

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 提供的函数将用作计算属性的 getter 函数
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
    newMsg: {
    // 计算属性也可以有 setter
      set: function(newValue) {
        message = newValue;
      }
    }
  }
})
vm.reversedMessage  // 调用 getter
vm.newMsg = 'sss'   // 调用 setter
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
      firstName: function (newValue, oldValue) {
        this.fullName = newValue + ' ' + this.lastName
      }
  }
})
<div
  class="static"
  v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div class="static" v-bind:class="classObject"></div>
data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}
// 渲染结果
<div class="static active"></div>
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
// 渲染为:
<div class="active text-danger"></div>

// 根据条件切换列表中的 class,可以用三元表达式:
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
// 直接在数组语法中使用对象语法:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
Vue.component('my-component', {
  template: '<p class="foo bar">Hi</p>'
})
<my-component class="baz boo"></my-component>
// HTML 将被渲染为:
<p class="foo bar baz boo">Hi</p>
// 对于带数据绑定 class 也同样适用:
<my-component v-bind:class="{ active: isActive }"></my-component>
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
  activeColor: 'red',
  fontSize: 30
}

// 直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

// 同样适用于数组
<div v-bind:style="[baseStyles, overridingStyles]"></div>
<h1 v-if="awesome">Vue is awesome!</h1>

// 也可以用 v-else, v-else-if 添加一个“else-if 块”:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
🔥warning

v-else v-else-if 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})
<ul id="example-1">
  <li v-for="(item, index) in|of items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
// 结果
// · Foo
// · Bar
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})
💡tip

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。即数组和对象不能响应式更新。使用 Vue.set 来添加新属性来确保 Vue 能够检测到这些变化

<ul id="v-for-object" class="demo">
  <li v-for="(item, key, index) in object">
    {{ index }}. {{ key }}: {{ item }}
  </li>
</ul>
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})
// 结果
// 0.title: How to do lists in Vue
// 1.author: Jane Doe
// 2.publishedAt: 2016-04-10
ℹ️note

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>
💡tip

2.2.0+ 的版本里,当在组件上使用 v-for 时,key 现在是必须的。

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>

methods: {
  warn: function (message, event) {
    // 现在我们可以访问原生事件对象
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
<!-- 67按键码对应的是C,只有按下C时才调用 `vm.submit() -->
<input v-on:keyup.67="submit">

<!-- 系统修饰键 .ctrl .alt .shift .meta -->
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

<!-- .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。-->
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>

<!-- 鼠标按钮修饰符 .left .right .middle -->

表单输入绑定

组件

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
// 使用组件
<div id="components-demo">
  <button-counter></button-counter>
</div>
❗️important

一个组件的 data 选项必须是一个函数,这样每个实例才有自己独立的数据副本,相互之间不会影响。data 函数在每次组件实例化时被调用,返回一个对象

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }

// 使用 v-bind 来动态传递 prop
<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
></blog-post>
ℹ️note

每个组件必须只有一个根元素

template: 
'<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>'
// 子组件通过 $emit('enlarge-text', value) 触发 enlarge-text 事件,并抛出一个值 value
<button v-on:click="$emit('enlarge-text', value)">
  Enlarge text
</button>
// 父组件使用 v-on 监听子组件实例的任意事件,使用 $event 接收子组件抛出的值
<blog-post
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

// 如果事件处理器是一个函数,子组件抛出的值作为第一个参数传入这个函数
<blog-post
  v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
  onEnlargeText: function (enlargeAmount) { // enlargeAmount = value
    this.postFontSize += enlargeAmount
  }
}
❗️important
<input v-model="searchText">
// 等价于:
<input  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"> // 这里的 $event 是原始的 DOM 事件
<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})
// 使用组件
<custom-input v-model="searchText"></custom-input>
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})
Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})
// 使用插槽
<alert-box>
  Something bad happened.
</alert-box>
// 结果
// Error!Something bad happened.

组件注册

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
// 只可在当前组件内使用
<component-a></component-a>

// 如果希望 ComponentA 在 ComponentB 中可用
var ComponentA = { /* ... */ }
var ComponentB = {
  components: {
    'component-a': ComponentA
  },
}
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
  components: {
    ComponentA,  // 等价于 ComponentA: ComponentA 
    ComponentC
  },
}
// ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用了。
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  // 其组件目录的相对路径
  './components',
  // 是否查询其子目录
  false,
  // 匹配基础组件文件名的正则表达式
  /Base[A-Z]\w+\.(vue|js)$/
)

requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)

  // 获取组件的 PascalCase 命名
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )

  // 全局注册组件
  Vue.component(
    componentName,
    // 如果这个组件选项是通过 `export default` 导出的,
    // 那么就会优先使用 `.default`,
    // 否则回退到使用模块的根。
    componentConfig.default || componentConfig
  )
})
💡tip

全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

Prop

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}
<!-- 静态传递 -->
<blog-post title="My journey with Vue"></blog-post>

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

<!-- 传入一个数字 -->
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>

<!-- 传入一个布尔值 -->
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>

<!-- 传入一个数组 -->
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>

<!-- 传入一个对象 -->
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
post: {
  id: 1,
  title: 'My Journey with Vue'
}

<blog-post v-bind="post"></blog-post>
// 等价于:
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>
Vue.component('my-component', {
  props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number, Boolean, Array, Object, Date, Function, Symbol],
    // 必填的字符串
    propC: {
      type: String,
      required: true
    },
    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100
    },
    // 带有默认值的对象
    propE: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].includes(value)
      }
    }
  }
})
🔥warning

prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
// 你可以使用:
Vue.component('blog-post', {
  props: {
    author: Person
  }
})
// 来验证 author prop 的值是否是通过 new Person 创建的。
// 即 传递给子组件的 prop 必须是一个 Person 的实例
// 即 person instanceof Person 返回 true
<blog-post :author="person"></blog-post>
Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs" // v-bind:placeholder="Enter your username"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
  `
})

// 使用子组件 $attrs={placeholder :'Enter your username'}
<base-input
  label="Username:"
  v-model="username"
  required
  placeholder="Enter your username"
></base-input>
ℹ️note

注意 inheritAttrs: false 选项不会影响 styleclass 的绑定。styleclass 依然绑定到根元素上。

// 子组件中通过事件来为 `title` prop 更新值
this.$emit('update:title', newTitle)
// 父组件中接收该值来更新与 `title` 绑定的 `doc.title` 
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

插槽

// 组件模板
<a v-bind:href="url" class="nav-link">
  <slot></slot>
</a>
// 使用组件
<navigation-link url="/profile">
  Your Profile
</navigation-link>
// 渲染结果
<a href="/profile" class="nav-link">
  Your Profile
</a>

// 插槽内可以包含任何模板代码,包括 HTML, 甚至其它的组件:
<navigation-link url="/profile">
  <!-- 添加一个 Font Awesome 图标 -->
  <span class="fa fa-user"></span>
  <!-- 添加一个图标的组件 -->
  <font-awesome-icon name="user"></font-awesome-icon>
  Your Profile
</navigation-link>
💡tip

如果 <navigation-link> 的 template 中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

<navigation-link url="/profile">
  Clicking here will send you to: {{ url }}
  <!--  这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
  _传递给_ <navigation-link> 的而不是
  在 <navigation-link> 组件*内部*定义的。 -->
  <!-- 这里的数据可以正常访问 -->
  Logged in as {{ user.name }}
</navigation-link>
<button type="submit">
  <slot>Submit</slot>
</button>

<!-- 不提供插槽内容 -->
<submit-button></submit-button>
<!-- 渲染结果 -->
<button type="submit">
  Submit
</button>

<!-- 提供插槽内容 -->
<submit-button>
  Save
</submit-button>
<!-- 渲染结果 -->
<button type="submit">
  Save
</button>
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
<!--   不带 name 的 <slot> 出口会带有隐含的名字“default”。 -->
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

<!-- 使用插槽 在 <template> 元素上使用 v-slot 指令,并提供其名称-->
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>
  <!-- 
  任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。 
  或者使用更明确的写法
  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>
  -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

<!-- 渲染结果 -->
<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>
ℹ️note

注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。

<span>
<!-- user prop 被称为插槽 prop -->
  <slot v-bind:user="user"> 
    {{ user.lastName }}
  </slot>
</span>
<!-- slotProps = { 
        user : { 
          firstName: 'Zhu', 
          lastName: 'mengyu'
        } 
     } -->
<current-user>
<!--   default 是插槽的默认名 -->
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

<!-- 也可以省略 default,对应默认插槽 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

<!-- 对插槽 prop 使用解构 -->
<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

<!-- 还允许你重命名 prop,例如将 user 重命名为 person -->
<current-user v-slot="{ user : person }">
  {{ person.firstName }}
</current-user>

<!-- 可以为插槽 prop 提供默认值(后备内容),以防它们是 undefined: -->
<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>
🔥warning

默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

混入(mixins)

// 定义一个混入对象
var myMixin = {
  data: function () {
    return {
      msg: 'zmy'
    }
  }
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义使用混入对象的组件
var Component1 = Vue.extend({
  mixins: [myMixin],
  data: function () {
    return {
      bar: 'def'
    }
  },
  methods: {
    say: function () {
      console.log('Hi!')
    }
  }
})
var Component2 = Vue.extend({
  mixins: [myMixin],
  data: function () {
    return {
      foo: 'def'
    }
  },
})

// component1 包含 msg, bar, say(), hello()
var component1 = new Component1()
// component2 包含 msg, foo, helo()
var component2 = new Component2()
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
  // 返回合并后的值
}
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

渲染函数

var component = {
  render: function (createElement) {
    return createElement(
      'div', // 标签名
      'Hello Vue!' // 子节点
    )
  }
}

var vm = new Vue(component).$mount('#app')
render: function (createElement) {
  return createElement(
    'div',
    {
      attrs: { id: 'myDiv' }, // 静态属性
      class: 'container',     // 类名
      style: { color: 'red' }, // 内联样式
      on: {                  // 监听器
        click: this.handleClick
      }
    },
    [                     // 子节点可以是数组形式
      createElement('span', 'Span Text'),
      'Text Node'
    ]
  )
}
// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字', //文本节点
    createElement('h1', '一则头条'), // 子元素 h1
    createElement(MyComponent, {    // 子元素 MyComponent 是一个组件
      props: {
        someProp: 'foobar'
      }
    })
  ]
)
ℹ️note
  • 只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for:
  • 渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑

Router

let router = useRouter();
let route = useRoute();
- router.push('/')
- router.replace('/')
- router.go(-1) 返回上一层
- router.go(1) 前进一层
- router.go(0) 刷新页面
- router.go(2) 前进两层
<el-table ref="multipleTableRef" :data="tableData" style="width:100%" 
   max-height="500px" @selection-change="handleSelectionChange">
<el-table-column label="操作">
    <template #default="attr">
        <!-- 当前行的索引 scoped.$index 和当前行数据 scoped.row -->
        <el-button size="small" @click="handleEdit(attr.$index, attr.row)">编辑</el-button>
        <el-button size="small" type="danger" @click="handleDelete(attr.$index, attr.row)">删除</el-button>
    </template>
</el-table-column>
ℹ️note

在 template 中不需要 .value 访问响应式变量,在 script 中需要

index 代表这个组件在地址栏中的路由路径:http://localhost:5173/company/DeptInfo, 点击组件时会路由到对应的组件 deptInfo.vue(定义在 index.js 中,不区分大小写)

<el-menu-item index="/company/DeptInfo">部门管理</el-menu-item>
<el-menu-item index="/company/EmployeeInformation">员工管理</el-menu-item>
  1. 创建路由器实例:路由器实例是通过调用 createRouter() 函数创建的
import { createMemoryHistory, createRouter } from 'vue-router'

import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'

const routes = [
  { path: '/', component: HomeView },
  { path: '/about', component: AboutView },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})
  1. 注册路由器插件:一旦创建了我们的路由器实例,我们就需要将其注册为插件,这一步骤可以通过调用 use() 来完成。
createApp(App)
  .use(router)
  .mount('#app')
// 等价地:
const app = createApp(App)
app.use(router)
app.mount('#app')
  1. 访问路由器和当前路由

路由守卫:路由切换的钩子函数,而路由变化时被调用

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})
// 根据路由在元信息中的 requiresCamera 属性确保用户访问摄像头的权限:
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})
router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')

// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject('global') // 'hello injections'
  // a pinia store
  const userStore = useAuthStore()
  // ...
})
const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

Pinia

ℹ️note
  • store 是一个用 reactive 包裹的对象,这意味着不需要在 getter 之后写 .value,但是,就像 setup 中的 props 一样,我们不能对其进行解构:
  • 为了从 Store 中提取属性同时保持其响应式,您需要使用 storeToRefs(): const { name, doubleCount } = storeToRefs(store) 直接结构会破坏响应式 const { name, doubleCount } = store
import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 推荐使用 完整类型推断的箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断其类型
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})
export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})
export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})