> For the complete documentation index, see [llms.txt](https://jackmeng.gitbook.io/note/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jackmeng.gitbook.io/note/frontend/vue/vue2.md).

# Vue2

#### 渐进式

Vue一个大特点就是**渐进式**,意思就是你可以渐渐地用Vue,而React几乎做不到这一点

动态构建用户界面

1. 你可以继续操作DOM
2. 你可以很方便地做SEO
3. 你可以局部做单页面
4. 你可以整体做单页面

#### Features

1. 遵循MVVM
2. 编码简洁,体积小,运行效率高,适合PC/移动端开发
3. 本身只关注UI,可以轻松的引入Vue插件或其他第三方库

#### MVVM

![](/files/-LcZ7eQ9nwpZct7MfSvL)

#### 插值表达式(仅支持单一表达式)

* `{{ number + 1 }}`
* `{{ obj.name }}`
* `{{ ok ? 'YES' : 'NO' }}`
* `{{ message.split('').reverse().join('') }}`
* `<div :id="`list-${id}`"></div>`

```vue
<span :title="toTitleDate(date)">
{{ formatDate(date) }}
</span>
```

#### Vue 属性

* el
* data
* template
* methods
* components(global / local)
  * 全局定义时命名不能重复
* filters(global / local)
  * {{ message | filter }}
  * `<div v-bind="msg | filter"></div>`
* watch -> single data
  * 深层
  * 回调触发时机
  * 停止
  * 即时
  * this.$watch

    ```vue
    //可以直接写methods方法名字符串
    watch: {
      currentBranch: 'fetchData'
    }
    ```
* \==computed -> multiple data==（**不要在计算函数中做异步请求或者更改 DOM**）
  * VS watch
    * `computed`是计算一个新属性,并将该属性挂载到`vm`上,而`watch`是监听已经存在且已挂载到`vm`上的属性, 计算属性不需要在 data 里面提前定义,watch 则需要,所以用`watch`同样可以监听`computed`计算属性的变化(其它还有 `data`、`props`)
    * `computed`本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问`computed`属性,才会计算新的值,而`watch`则是当数据发生变化便会调用执行函数
    * 从使用场景上说,`computed`适用一个数据被多个数据影响,而`watch`适用一个数据影响多个数据,`watch`适合异步或开销大的场景
  * VS method
    * `methods`没有缓存
    * `computed`不可传参,`methods`可以
    * `computed`通过`getter` 、`setter`传newValue
* \==class==
  * `<p :class="{ active: isActive, 'text-danger': hasError }">xxx是字符串</p>`
  * `<p :class="classObject">xxx是对象</p>`
  * `<p :class="['bClass', 'cClass']">xxx是数组</p>`
  * `<div :class="[isActive ? activeClass : '', errorClass]"></div>`
  * `<div :class="[{ active: isActive }, errorClass]"></div>`
* \==style==
  * `<p :style="{ color: activeColor, fontSize: fontSize + 'px' }"></p>`
  * `<div :style="{ 'font-size': fontSize + 'px' }"></div>`
  * `<p :style="styleObject"></p>`
  * `<p :style="[baseStyles, overridingStyles]">test style2</p>`
  * `<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>`

#### Vue 实例属性

* $el、$options、$data
* \==$attrs、$listeners== （除了class、style、v-on等）、$props

  ```vue
  <!-- 想要所有像 class 和 v-on 监听器这样的透传 attribute 都应用在内部的 <button> 上而不是外层的 <div> 上。通过设定 inheritAttrs: false 和使用 v-bind="$attrs" 来实现：  -->
  <div class="btn-wrapper">
    <button class="btn" v-bind="$attrs">click me</button>
  </div>
  ```
* $root、$parent、$children
* $refs(操作DOM)

  ```vue
  <script>
  export default {
    mounted() {
      this.$refs.input.focus()
    }
  }
  </script>

  <template>
    <input ref="input" />
  </template>
  ```
* 组件上的ref代表的是子组件实例
* expose

  ```javascript
  export default {
    expose: ['publicData', 'publicMethod'],
    data() {
      return {
        publicData: 'foo',
        privateData: 'bar'
      }
    },
    methods: {
      publicMethod() {
        /* ... */
      },
      privateMethod() {
        /* ... */
      }
    }
  }
  ```
* DOM更新时机

  ```javascript
  import { nextTick } from 'vue'

  export default {
    methods: {
      increment() {
        this.count++
        //适用场景
        //1. 在`created`生命周期执行DOM操作
  		  //2. 在数据变化后需要进行基于DOM结构的操作
        nextTick(() => {
          // 访问更新后的 DOM
        })
      }
    }
  }
  ```

#### directive

* v-once、v-cloak

  ```vue
  //如果没有彻底解决问题，则在根元素加上style="display: none;" :style="{display: 'block'}"
  [v-cloak] {
      display: none;
  }
  ```
* v-model
  * 原生展开 ： `<input :value="val" @input="val=$event.target.value" />`
  * 自定义组件展开： `<CustomInput :modelValue="searchText" @update:modelValue="newValue => searchText = newValue" />`

    ```vue
    <!-- 为了使组件正常工作 -->
    <!-- 方式一：CustomInput.vue -->
    <script>
    export default {
      props: ['modelValue'],
      emits: ['update:modelValue']
    }
    </script>

    <template>
      <input
        :value="modelValue"
        @input="$emit('update:modelValue', $event.target.value)"
      />
    </template>

    <!-- 方式二：CustomInput.vue -->
    <script>
    export default {
      props: ['modelValue'],
      emits: ['update:modelValue'],
      computed: {
        value: {
          get() {
            return this.modelValue
          },
          set(value) {
            this.$emit('update:modelValue', value)
          }
        }
      }
    }
    </script>

    <template>
      <input v-model="value" />
    </template>

    //定义别名
    <MyComponent v-model:title="bookTitle" />

    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)"
        >
      `
    })
    ```
* v-for、v-if、v-else、v-else-if、v-show
* v-if vs v-show
* \==v-if > v-for==

  ```vue
  <!-- 抛出一个错误，因为属性 todo 此时没有在该实例上定义-->
  <li v-for="todo in todos" v-if="!todo.isComplete">
    {{ todo.name }}
  </li>

  <!-- 方式一套一层 -->
  <template v-for="todo in todos">
    <li v-if="!todo.isComplete">
      {{ todo.name }}
    </li>
  </template>

  <!-- 方式二 使用计算属性 -->
  ```
* v-text、v-html
* v-slot、v-pre
* \==v-bind ==> :==
  * `<div v-bind="objectOfAttrs"></div>`
* \==v-on ==> @==
  * 在内联事件处理器中访问事件参数

    ```vue
    <!-- 使用特殊的 $event 变量 -->
    <button @click="warn('Form cannot be submitted yet.', $event)">
      Submit
    </button>

    <!-- 使用内联箭头函数 -->
    <button @click="(event) => warn('Form cannot be submitted yet.', event)">
      Submit
    </button>
    ```
  * 事件修饰符`stop、prevent、self`

    ```vue
    <!-- 单击事件将停止传递 -->
    <a @click.stop="doThis"></a>
    <!-- 提交事件将不再重新加载页面 -->
    <form @submit.prevent="onSubmit"></form>
    <a @click.stop.prevent="doThat"></a>
    <form @submit.prevent></form>
    <!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
    <!-- 例如：事件处理器不来自子元素 -->
    <div @click.self="doThat">...</div>
    ```
  * `once、capture、passive`与\[原生 `addEventListener` 事件相对应

    ```vue
    <!-- 添加事件监听器时，使用 `capture` 捕获模式 -->
    <!-- 例如：指向内部元素的事件，在被内部元素处理前，先被外部处理 -->
    <div @click.capture="doThis">...</div>

    <!-- 点击事件最多被触发一次 -->
    <a @click.once="doThis"></a>

    <!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
    <!-- 以防其中包含 `event.preventDefault()` -->
    <div @scroll.passive="onScroll">...</div>
    ```
  * 按键修饰符`enter、tab、delete、esc、space、up、down、left、right`

    ```vue
    <input @keyup.enter="submit" />
    <input @keyup.page-down="onPageDown" />
    ```
  * 系统修饰符`ctrl、alt、shift、exact、meta`

    ```vue
    <!-- Alt + Enter -->
    <input @keyup.alt.enter="clear" />

    <!-- Ctrl + 点击 -->
    <div @click.ctrl="doSomething">Do something</div>

    <!-- 当按下 Ctrl 时，即使同时按下 Alt 或 Shift 也会触发 -->
    <button @click.ctrl="onClick">A</button>

    <!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
    <button @click.ctrl.exact="onCtrlClick">A</button>

    <!-- 仅当没有按下任何系统按键时触发 -->
    <button @click.exact="onClick">A</button>
    ```
  * 鼠标修饰符`left、right、middle`
  * 自定义事件
* \==自定义指令==（只有当所需功能只能通过直接的 DOM 操作来实现时，才应该使用自定义指令。其他情况下应该尽可能地使用 `v-bind` 这样的内置指令来声明式地使用模板，这样更高效，也对服务端渲染更友好）
  * hooks
    * bind 指令第一次绑定到元素时调用，只执行一次，可用于一次性初始化设置
    * inserted 元素插入父节点时调用
    * update 所有 VNode 更新时调用，可能发生在子 VNode 之前
    * componentUpdated 指令所在组件在 VNode 和其子 VNode 更新后调用
    * unbind 指令与元素解绑时调用
  * 参数
    * `el` 指令绑定元素，可操作 DOM
    * `binding` 指令描述对象
      * `value`：传递给指令的值。例如在 `v-my-directive="1 + 1"` 中，值是 `2`
      * `oldValue`：之前的值，仅在 `beforeUpdate` 和 `updated` 中可用。无论值是否更改，它都可用
      * `arg`：传递给指令的参数 (如果有的话)。例如在 `v-my-directive:foo` 中，参数是 `"foo"`
      * `modifiers`：一个包含修饰符的对象 (如果有的话)。例如在 `v-my-directive.foo.bar` 中，修饰符对象是 `{ foo: true, bar: true }`
      * `instance`：使用该指令的组件实例
      * `dir`：指令的定义对象
    * `vnode` 代表绑定元素的底层 VNode
    * `prevNode`：之前的渲染中代表指令所绑定元素的 VNode。仅在 `beforeUpdate` 和 `updated` 钩子中可用
* \==表单输入绑定==
  \*

  ```
  ![t](/files/RNTYrUDCL1PNooLKWXqe)
  ```

  * 基础用法

    ```javascript
    <input type='checkbox' v-model="name" true-value="xxx" false-value="yyy">
    ```
  * 值绑定
  * 表单事件修饰符`v-model:lazy、v-model:number、v-model:trim`
  * 自定义model

    ```vue
    model: {
        prop: "phoneInfo", // 默认 value
        event: "change" // 默认 input
    },
    props: {
        phoneInfo: Object,
        zipCode: String
    }

    <PersonalInfo v-model="phoneInfo" :zip-code.sync="zipCode" />

    <PersonalInfo
        :phone-info="phoneInfo"
        :zip-code="zipCode"
        @change="val => (phoneInfo = val)"
        @update:zipCode="val => (zipCode = val)"
    />
    ```

#### ==组件(global / local)==

* 属性
  * \==传递props==
    * 单向数据流
    * props初始化先于data,可挂载到子实例data上
    * 校验(null和undefined会通过任何类型验证)

      ```vue
      props: {
      	//null和undefined会通过任何类型验证
      	propA: Number,
      	propB: [String, Number],
      	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'].indexOf(value) !== -1
      		}
      	},
          propG: {
      		type: Array,
               default: () => [] 
      	}
      }


      //对于多类型场景
      <template>
        <button :style="computedWidth">{{ computedWidth }}</button>
      </template>

      <script>
        export default {
          props: {
            width: [String, Number]
          },
          computed: {
            computedWidth () {
              let o = {}
              if (typeof this.width === 'string') o.width = this.width
              if (typeof this.width === 'number') o.width = this.width + 'px'
              return o
            }
          }
        }
      </script>
      ```
  * 原生属性自动挂载到组件根元素上，可设置`inheritAttrs = false`不继承
  * Non-props

    ```javascript
    //parent
    <Child style="color:red" :msg="msg">
      
    //Child
    //当子组件有父节点的时候，non-props才会挂载到子组件根节点，否则不挂
    //通过如下方式挂载到平级的某个节点上
    //挂载所有
    <div v-bind="$attrs">Hello world</div>
    <div >Hello world2</div>

    //只挂载style
    <div v-bind:style="$attrs.style">Hello world</div>
    <div >Hello world2</div>

    //业务逻辑上只使用msg
    mounted() {
      console.log(this.$attrs.msg)
    }
    ```
  * .sync

    ```vue
    //子组件
    this.$emit('update:title', newTitle)

    //父组件
    <text-document
      v-bind:title="doc.title"
      v-on:update:title="doc.title = $event"
    ></text-document>

    //to
    <text-document v-bind:title.sync="doc.title"></text-document>`
    ```
  * 特殊属性 `class / style / ref / key`
* 事件
  * 修饰符事件，应用于原生html元素
  * 普通事件

    ```vue
    <!-- 父组件 -->
    <BlogPost
      ...
      @enlarge-text="postFontSize += 0.1"
     />

    <!-- 子组件 -->
    <template>
      <div class="blog-post">
        <h4>{{ title }}</h4>
        <button @click="$emit('enlarge-text')">Enlarge text</button>
      </div>
    </template>
    ```
  * emits

    ```vue
    <!-- BlogPost.vue -->
    <script>
    export default {
      props: ['title'],
      emits: ['enlarge-text']
    }
    </script>


    //emits校验
    export default {
      emits: {
        // 没有校验
        click: null,
         //校验父组件的add方法 <Child @add="xxx">
    		add: (value) => value < 20 ? true : false
        // 校验 submit 事件
        submit: ({ email, password }) => {
          if (email && password) {
            return true
          } else {
            console.warn('Invalid submit event payload!')
            return false
          }
        }
      },
      methods: {
        submitForm(email, password) {
          this.$emit('submit', { email, password })
        }
      }
    }
    ```

#### 插槽

* 默认内容
* 具名插槽

  ```vue
  <BaseLayout>
    <template #header>
      <h1>Here might be a page title</h1>
    </template>

    <!-- 隐式的默认插槽 -->
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>

    <template #footer>
      <p>Here's some contact info</p>
    </template>
  </BaseLayout>
  ```
* 作用域插槽(父组件需要访问子组件的data的时候)
  * 默认插槽

    ```vue
    <!-- <MyComponent> 的模板 -->
    <div>
      <slot :text="greetingMessage" :count="1"></slot>
    </div>

    <MyComponent v-slot="{ text, count }">
      {{ text }} {{ count }}
    </MyComponent>
    ```
  * 具名插槽

    ```vue
    <!-- <MyComponent> 的模板 -->
    <slot name="header" message="hello"></slot>

    <MyComponent>
      <template #header="headerProps">
        {{ headerProps }}
      </template>

      <template #default="defaultProps">
        {{ defaultProps }}
      </template>

      <template #footer="footerProps">
        {{ footerProps }}
      </template>
    </MyComponent>
    ```
* 动态组件

  ```javascript
  <keep-alive>
    <component :is="showComponent" />
  </keep-alive>
  ```
* 异步组件

  ```javascript
  app.component('async-component', Vue.defineAsyncComponent(
  	() => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            template: `<div>这是一个异步组件</div>`
          })
        })
      })
    }
  ))
  ```
* DOM 模板解析注意事项
  * 大小写
  * 闭合标签
  * 元素位置显示
* 穿透scoped
  * 在模板中使用两次style标签

    ```vue
    <style lang="scss">
    		/*添加要覆盖的样式*/
    </style>
    <style lang="scss" scoped>
    		/* local styles */
    </style>
    ```
  * 使用`>>>`或`/deep/`操作符(Sass之类的预处理器无法正确解析`>>>`)

    ```vue
    <style lang="scss">
    .box >>> input {
      width: 166px;
      text-align: center;
    }

    .box {
      /deep/ input {
        width: 166px;
        text-align: center;
      }
    }
    </style>
    ```

#### 生命周期

* `beforeCreate/created、beforeMount/mounted、beforeUpdate/updated、beforeDestroyed/destroyed、errorCaptured`

  ![](/files/RJuStzoPdtSQkMSMm8J7)
* `activated/deactivated、errorCaptured`

#### Vue优化

* 编码阶段
  * 尽量减少data中的数据，data中的数据都会增加getter和setter，会收集对应的watcher
  * v-if和v-for不能连用
  * 如果需要使用v-for给每项元素绑定事件时使用事件代理
  * SPA 页面采用keep-alive缓存组件
  * 在更多的情况下，使用v-if替代v-show
  * key保证唯一
  * 使用路由懒加载、异步组件
  * 防抖、节流
  * 第三方模块按需导入
  * 长列表滚动到可视区域动态加载
  * 图片懒加载
* SEO优化
  * 预渲染
  * 服务端渲染SSR
* 打包优化
  * 压缩代码
  * Tree Shaking/Scope Hoisting
  * 使用cdn加载第三方模块
  * 多线程打包happypack
  * splitChunks抽离公共文件
  * sourceMap优化
* 用户体验
  * 骨架屏
  * PWA
  * 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等
