尼采般地抒情

尼采般地抒情

尼采般地抒情

音乐盒

站点信息

文章总数目: 321
已运行时间: 1782

官方文档:Vue.js

一、Virtual DOM, Render, diff

  • 利用JavaScript脚本操作DOM的行为是性能损耗的大头,故产生虚拟DOM的概念。初始化在内存中得到相应的虚拟DOM树,然后将该结果一次性作用于真实DOM,逻辑层的变动导致部分视图层的改变这渲染逻辑也是经过虚拟DOM加上diff得到一次性结果然后作用于真实DOM
  • 结构渲染方面,vue文件中的template利用vue的render函数以及AST语法树解析来生成虚拟DOM树对象
  • 当视图发生更新,需要比对更新前以及更新后的虚拟DOM差异,这里面的比对的逻辑叫做diff算法,最终diff出来的结果一次性只更新需要更新的真实DOM节点

二、globalProperties

  • 响应式变量是利用ES6的Proxy(Vue3)来实现的
  • 计算属性和侦听器
    • 计算属性和方法之间,计算方法具备缓存能力,而方法不具备
    • 默认计算属性是只读的,不过可以对具体计算属性写成get set的形式组织
    • 计算属性适合:多个值影响一个值的应用
    • 侦听器适合:一个值影响多个值的应用
    • 侦听器支持异步,计算属性不支持
  • 属性:name, props, emits, components, directives, computed, methods, watch
  • 方法:data, mounted,
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>learn vue</title>
    <script src="./vue.global.js"></script>
  </head>
  <body>
    <div id="app">
      <div>parent count: {{count}}</div>
      <my-head
        :title="headerTitle"
        @custom-event="handleCustomEvent"
        v-model="count"
        ></my-head>
    </div>
    <script>
      let app = Vue.createApp({
        data() {
          return {
            headerTitle: "header-title",
            count: "1",
          };
        },
        methods: {
          handleCustomEvent(data) {
            console.log(data);
          },
        },
      });
      // local component
      const HeaderLocalComponent = {
        template: `header-local-component`,
      };
      // global component
      app.component("my-head", {
        props: {
          title: {
            type: String,
          },
          modelValue: {
            type: String,
          },
        }, // or props: ["title"]
        emits: ["custom-event", "update:modelValue"],
        template: `
          <header>
            <h1>{{headTitle}}</h1>
            <p>message: {{message}}</p>
            <p>reverse message: {{reverseMessage}}</p>
            <header-local-component></header-local-component>
            <input type="text" :value="modelValue" @input="handleInput" />
          </header>
        `,
        components: {
          HeaderLocalComponent,
        },
        data() {
          return {
            message: "hello world",
            headTitle: this.title, // props data -> responsive...
          };
        },
        mounted() {
          setTimeout(() => {
            this.message = "hello world2";
            this.headTitle = "head change self title...";
            this.$emit("custom-event", {
              info: "child to parent.",
            });
          }, 2000);
        },
        computed: {
          // reverseMessage() {
          //   return this.message.split(" ").reverse().join(" ");
          // },
          reverseMessage: {
            get() {
              return this.message.split(" ").reverse().join(" ");
            },
            set(value) {
              this.message = value;
            },
          },
        },
        methods: {
          handleInput(e) {
            this.$emit("update:modelValue", e.target.value);
          },
        },
        watch: {
          modelValue(newValue, oldValue) {
            console.log("modelValue change...", oldValue, newValue);
          },
        },
      });
      const vm = app.mount("#app");
    </script>
  </body>
</html>

custom globalProperties

自定义全局属性

app.config.globalProperties.$something = something

使用

this.$something

$attrs

  1. 组件默认传递属性,如果子组件没有接收,那么会作为默认属性添加到dom身上
  2. 如果要去除上述的默认传递效果,可以添加inheritAttrs参数
app.component("my-head", {
  data() {
    return {
      message: "hello world",
    };
  },
  inheritAttrs: false
  ···
});
  1. 如果添加了inheritAttrs参数,但又需要对指定dom添加自定义属性
<h1 v-bind:info="$attrs.info"></h1>
  1. this.$attrs也可以进行通信

$refs

  1. ref属性作用在元素上,this.$refs.xxx获取的是原生DOM
  2. ref属性作用在组件上,this.$refs.xxx获取的是组件实例对象,可以调用其方法或是响应式变量
console.log('作用在DOM上的ref: ', this.$refs.domRef)
console.log('作用在组件上的ref: ', this.$refs.homeHomeChild1Ref)

  1. 可以间接进行组件间的通信

$nextTick

  1. 在DOM渲染之后的执行时机,同updated生命周期
mounted() {
  console.log('全局变量$globalVar: ', this.$globalVar)
  this.$nextTick(() => {
    console.log('作用在DOM上的ref: ', this.$refs.domRef)
    console.log('作用在组件上的ref: ', this.$refs.homeHomeChild1Ref)
  })
}
  1. 有两种写法,一种是hook风格,另一种是Promise链式调用风格

$event

原生DOM事件传参(内部封装)

  1. 不传参数:只传递函数名,methods中的函数形参可传默认e
  2. fun($event, param)
<button @click="clickButton1">不传参数</button>
<button @click="clickButton2('data')">传1个参数</button>
<button @click="clickButton3($event, 'data')">传2个参数</button>

三、逻辑复用

组件(components)

<template>
  <div class="box">HomeChild1组件</div>
  <HomeChild1 :msg="var4" @customEvent="customEvent" />
</template>

<script>
  import HomeChild1 from '../components/HomeChild1.vue'

  export default {
    name: 'HomeView',
    components: {
      HomeChild1
    },
    data() {
      return {
        var4: 'HomeView组件传递给HomeChild1组件的值',
      }
    },
    methods: {
      customEvent(data) {
        console.log('customEvent', data)
      }
    }
  }
</script>
  1. 组件的注册以及使用
  2. 根组件以及普通组件:根组件template比html结构中的子内容优先级更高(覆盖渲染)
  3. 全局组件以及局部组件的选项式写法

自定义指令(v-)

自定义指令也是逻辑复用的常见形式之一,比如对节点赋予拖拽功能,基本语法如下:

<template>
  <div class="box">自定义指令</div>
  <input v-focus />
</template>

<script>
export default {
  name: 'HomeView',
  directives: {
    focus: {
      mounted: (el) => el.focus()
    }
  }
}
</script>

插件

定义

app.use(
  {
    install: (app, options) => {
      app.config.globalProperties.$translate = (key) => {
        return key.split('.').reduce((o, i) => {
          if (o) return o[i]
        }, options)
      }
    }
  },
  {
    greetings: {
      hello: 'Hello, world!'
    }
  }
)

使用

<template>
  {{ $translate('greetings.hello') }}
</template>

mixins

  • 一种复用逻辑的抽离的方式,但是更为推荐组合式的hook来复用代码逻辑,vue的最新文档甚至去除了mixins相关介绍
  • 使用:minins: [module]
  • 缺点
    • 无法差异化
    • 可存在同名函数(代码风格)

四、生命周期

初始化实例时触发

  1. beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用
    1. 响应式数据没有,dom未渲染
  1. created:在实例创建完成后被立即调用
    1. 响应式数据有了,dom未渲染
    2. 一般在这里异步请求初始化数据
  1. beforeMount:在挂载开始之前被调用
    1. 响应式数据有了,dom未渲染
  1. mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子
    1. 响应式数据有了,dom已渲染
  1. activated(KeepAlive相关
  2. deactivated(KeepAlive相关

更新数据时触发

  1. beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前
    1. 响应式数据变了,但是dom还没有更新
  1. updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
    1. 响应式数据变了,dom也更新了

卸载实例时触发

  1. beforeDestroy:实例销毁之前调用
    1. 响应式数据还有,dom也有
  1. destroyed:实例销毁后调用
    1. 响应式数据还有,dom没了
  1. errorCaptured
  2. renderTracked(开发模式才有,且SSR没有
  3. renderTriggered(开发模式才有,且SSR没有
  4. serverPrefetch(SSR才有

五、内置组件

component

  • 配合:is实现动态切换组件的行为
<template>
  <div class="box1">内置组件</div>
  <button @click="temp = 'TemplateChild1'">TemplateChild1</button>
  <button @click="temp = 'TemplateChild2'">TemplateChild2</button>
  <component :is="temp" />
</template>

<script>
import TemplateChild1 from '../components/TemplateChild1.vue'
import TemplateChild2 from '../components/TemplateChild2.vue'
export default {
  name: 'TemplateView',
  data() {
    return {
      temp: 'TemplateChild1'
    }
  },
  components: {
    TemplateChild1,
    TemplateChild2
  }
}
</script>

KeepAlive

在上述组件切换过程中,被切换组件的状态会被清除,也就是输入框会输入框输入的数据会被清空,如果需要保留状态,外面需要套一层keep-alive组件

<KeepAlive>
  <component :is="temp" />
</KeepAlive>

defineAsyncComponent

动态组件中(component),会一次性加载所有组件,如果需要按需加载则使用defineAsyncComponent,

<template>
  ···
</template>

<script>
import { defineAsyncComponent } from 'vue'
export default {
  name: 'TemplateView',
  data() {
    return {
      temp: 'TemplateChild1'
    }
  },
  components: {
    TemplateChild1: defineAsyncComponent(() => import('../components/TemplateChild1.vue')),
    TemplateChild2: defineAsyncComponent(() => import('../components/TemplateChild2.vue'))
  }
}
</script>

Suspense

内置边界加载中组件

<template>
  <div class="box1">内置组件</div>
  <button @click="temp = 'TemplateChild1'">TemplateChild1</button>
  <button @click="temp = 'TemplateChild2'">TemplateChild2</button>
  <KeepAlive>
    <Suspense>
      <component :is="temp" />
      <template #fallback>
        <div>loading...</div>
      </template>
    </Suspense>
  </KeepAlive>
</template>

Teleport

“传送门”逻辑,相当于指定要渲染的DOM至指定文档树位置,有益于DOM结构合理

如果DOM结构不合理,会造成一些样式上不必要的写法

<Teleport to="body"><div>Teleport组件</div></Teleport>

如果不使用vue内置组件teleport,传送门的同类型实现逻辑则是利用原生DOM api以及Vue的createApp结合实现

<script>
  import { createApp } from 'vue'
  import { Component } from 'xxxx'
  const target = document.createElement('div')
  document.body.append(target)
  createApp(Component).mount(target)
</script>

Transition

vue内置动画组件

<transition name="slide" mode="out-in">
  <keep-alive>
    <component :is="compo"></component>
  </keep-alive>
</transition>
.fade-enter-from {
  opacity: 0;
}
.fade-enter-to {
  opacity: 1;
}
.fade-enter-active {
  transition: 1s;
}
.fade-leave-from {
  opacity: 1;
}
.fade-leave-to {
  opacity: 0;
}
.fade-enter-active {
  transition: 1s;
}
.slide-enter-from {
  opacity: 0;
  transform: translateX(20px);
}
.slide-enter-to {
  opacity: 1;
  transform: translateX(0);
}
.slide-leave-from {
  opacity: 1;
  transform: translateX(0);
}
.slide-leave-to {
  opacity: 0;
  transform: translateX(20px);
}
.slide-leave-active {
  transition: 1s;
}

评论区

什么都不舍弃,什么也改变不了