当前位置: 首页 > article >正文

Vue API 盲点解析

在了解了一些实用的开发技巧和编码理念后,我们在项目的开发过程中难免也会遇到因为不熟悉 Vue API 而导致的技术问题,而往往就是这样的一些问题消耗了我们大量的开发时间,造成代码可读性下降、功能紊乱甚至 bug 量的增加,其根本原因还是自己对 Vue API 的 “无知”。

本文将介绍 Vue 项目开发中比较难以理解并可能被你忽视的 API,唯有知己知彼,才能百战不殆,本篇幅有点长,建议读完主体部分,再阅读拓展部分。

 

使用 performance 开启性能追踪

Performance API 是浏览器提供的一组接口,用于测量和监控网页性能,包括页面加载时间、资源加载时间等。通过 Performance API,开发者可以获取关于性能的详细信息,以帮助优化网站和应用的表现。Performance API 通常被用来分析和提升用户体验。

Performance API 主要有以下几个部分:

  1. Performance Timing:用于获取关于页面加载过程的时间信息。
  2. Performance Navigation:用于获取页面导航的相关信息。
  3. Performance Resource Timing:用于获取关于页面中每个资源(如图片、脚本、样式等)加载的详细信息。
  4. Performance Mark and Measure:用于手动标记和测量代码执行的时间。
  5. Performance Observer:用于异步监听性能相关的事件。

performance API 是 Vue 全局配置 API 中的一个,我们可以使用它来进行网页性能的追踪,我们可以在入口文件中添加:

if (process.env.NODE_ENV !== 'production') {
    Vue.config.performance = true;
}

来开启这一功能,该 API(2.2.0 新增)功能只适用于开发模式和支持 performance.mark API 的浏览器上,开启后我们可以下载 vue Performance Devtool 这一 chrome 插件来看查看各个组件的加载情况,如图:

从中我们可以清晰的看到页面组件在每个阶段的耗时情况,而针对耗时比较久的组件,我们便可以对其进行相应优化。

在Vue的源码中便是通过Performance API 来获取网页性能数据的,其中便包括了performance.mark() 、PerformanceObserver()performance.now()、performance.getEntriesByType('measure')performance.getEntriesByName('measureName')

  1. performance.now():

    • 用于获取当前时间的高精度时间戳,通常用于测量代码块的执行时间。
    • 示例:
      const start = performance.now();
      // 代码执行
      const end = performance.now();
      console.log(`执行时间: ${end - start}毫秒`);
  2. performance.mark():

    • 用于手动为性能监控设置标记,可以标记代码执行的开始和结束时间。
    • 示例:
      performance.mark('start');
      // 执行某段代码
      performance.mark('end');
      performance.measure('My Measure', 'start', 'end');
  3. performance.getEntriesByType('measure')

    • 用于获取之前设置的性能测量数据,以便进行分析。
    • 示例:
      const measures = performance.getEntriesByType('measure');
      measures.forEach(measure => {
          console.log(`${measure.name}: ${measure.duration} 毫秒`);
      });
  4. PerformanceObserver:

    • 允许异步观察性能相关的事件,如标记和测量。
    • 示例:
      const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
              console.log(`新性能条目: ${entry.name}, 持续时间: ${entry.duration}`);
          }
      });
      observer.observe({ entryTypes: ['mark', 'measure'] });

5. performance.getEntriesByName()

语法:

performance.getEntriesByName(name[, type])
  • name: 要检索的性能条目的名称(字符串)。
  • type (可选): 性能条目的类型,可以是 'mark''measure' 或省略(将返回所有类型的条目)。

示例:

// 创建标记
performance.mark('myMark');

// 获取标记
const marks = performance.getEntriesByName('myMark');
marks.forEach(mark => {
    console.log(`标记名称: ${mark.name}, 时间戳: ${mark.startTime} 毫秒`);
});

熟练的使用 performance 我们可以查看并分析网页的很多数据,为我们项目优化提供保障。

拓展1

performance API的其他方法

1. Performance Timing

通过 performance.timing 可以获取页面加载过程中的时间戳信息。

const timing = performance.timing;
const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
console.log(`页面加载时间: ${pageLoadTime}ms`);

2. Performance Navigation

通过 performance.navigation 可以获取有关页面导航的信息。

const nav = performance.navigation;

if (nav.type === PerformanceNavigation.TYPE_RELOAD) {
    console.log('页面是通过刷新加载的');
}

3. Performance Resource Timing

通过 performance.getEntriesByType('resource') 可以获取加载的资源信息。

const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
    console.log(`资源: ${resource.name}`);
    console.log(`加载时间: ${resource.duration}ms`);
});

4. Performance Mark and Measure

语法:

performance.mark(markName)

performance.measure(measureName[, startMark[, endMark]])
  • markName: 标记的名称(字符串),用于唯一标识该标记。
  • measureName: 测量的名称(字符串),用于标识该测量。
  • startMark (可选): 开始测量的标记名称(字符串)。如果没有提供,测量将从页面加载开始。
  • endMark (可选): 结束测量的标记名称(字符串)。如果没有提供,测量将持续到页面卸载。

可以使用 performance.mark()performance.measure() 来手动标记和测量代码执行时间。

// 创建一个标记
performance.mark('start');

// 模拟一些异步操作,例如设置一个超时
setTimeout(() => {
    // 创建结束标记
    performance.mark('end');

    // 可以在这里进行测量
    performance.measure('My Measure', 'start', 'end');

    // 获取测量结果
    const measures = performance.getEntriesByName('My Measure');
    measures.forEach(measure => {
        console.log(`测量名称: ${measure.name}, 持续时间: ${measure.duration} 毫秒`);
    });

}, 1000); // 模拟操作延迟 1 秒

5. Performance Observer

Performance Observer 允许你异步地监听性能相关的事件。

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(`新性能条目: ${entry.name}, 持续时间: ${entry.duration}`);
    }
});

observer.observe({ entryTypes: ['mark', 'measure'] });

6. Performance API 的其他方法

  • performance.now(): 返回一个高分辨率时间戳,以毫秒为单位,可以用于测量短时间的性能。

    const start = performance.now();
    // 执行某些逻辑
    const end = performance.now();
    console.log(`执行时间: ${end - start}ms`);
  • performance.clearMarks(): 清除指定名称的标记。

    performance.clearMarks('start');
  • performance.clearMeasures(): 清除指定名称的测量。

    performance.clearMeasures('My Measure');

使用 errorHandler 来捕获异常

在浏览器异常捕获的方法上,我们熟知的一般有:try ... catch Promise window.onerror, 这也是原生 JavaScript 提供给我们处理异常的方式。在 Vue 3 中,你可以使用多种方法来捕获和处理异常。以下是一些常见的方法,包括全局错误处理和局部错误处理。

1. 全局异常处理

Vue 3 提供了全局错误处理的方法,可以通过 app.config.errorHandler 来捕获未处理的错误。你可以在创建 Vue 实例时进行配置。

示例代码

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
    console.error('捕获到错误:', err);
    console.info('错误信息:', info);
    // 在这里可以添加逻辑,比如发送错误信息到服务器
};

app.mount('#app');

2. 组件内的错误处理

在组件中,你可以通过实现 errorCaptured 生命周期钩子来捕获子组件的错误。这个钩子会在子组件的错误被捕获时被调用。

示例代码

<template>
  <div>
    <ChildComponent />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent,
  },
  errorCaptured(err, vm, info) {
    console.error('捕获到子组件错误:', err);
    console.info('错误信息:', info);
    // 可以选择继续传播错误或停止传播
    // return false; // 停止传播
  },
};
</script>

3. 使用 try...catch 捕获错误

在你的方法中,你可以使用 try...catch 来捕获同步和异步的错误。

示例代码

<template>
  <div>
    <button @click="performTask">执行任务</button>
  </div>
</template>

<script>
export default {
  methods: {
    async performTask() {
      try {
        // 可能会抛出错误的代码
        const result = await this.someAsyncFunction();
        console.log(result);
      } catch (error) {
        console.error('捕获到错误:', error);
        // 在这里可以添加逻辑,比如发送错误信息到服务器
      }
    },
    someAsyncFunction() {
      return new Promise((resolve, reject) => {
        // 故意抛出一个错误
        reject(new Error('这是一个异步错误!'));
      });
    },
  },
};
</script>

4. 处理 Promise 错误

当你使用 Promise 时,你需要在 .catch() 中处理错误:

<template>
  <div>
    <button @click="executePromise">执行 Promise</button>
  </div>
</template>

<script>
export default {
  methods: {
    executePromise() {
      this.somePromiseFunction()
        .then(result => {
          console.log(result);
        })
        .catch(error => {
          console.error('捕获到Promise错误:', error);
          // 处理错误
        });
    },
    somePromiseFunction() {
      return new Promise((resolve, reject) => {
        // 故意抛出一个错误
        reject(new Error('这是一个Promise错误!'));
      });
    },
  },
};
</script>

使用 nextTick 将回调延迟到下次 DOM 更新循环之后执行

在某些情况下,我们改变页面中绑定的数据后需要对新视图进行一些操作,而这时候新视图其实还未生成,需要等待 DOM 的更新后才能获取的到,在这种场景下我们便可以使用 nextTick 来延迟回调的执行。比如未使用 nextTick 时的代码:

<template>
    <ul ref="box">
        <li v-for="(item, index) in arr" :key="index"></li>
    </ul>
</template>

<script>
export default {
    data() {
        return {
            arr: []
        }
    },
    mounted() {
    	this.getData();
    },
    methods: {
        getData() {
            this.arr = [1, 2, 3];
            this.$refs.box.getElementsByTagName('li')[0].innerHTML = 'hello';
        }
    }
}
</script>

上方代码我们在实际运行的时候肯定会报错,因为我们获取 DOM 元素 li 的时候其还未被渲染,我们将方法放入 nextTick 回调中即可解决该问题:

this.$nextTick(() => {
    this.$refs.box.getElementsByTagName('li')[0].innerHTML = 'hello';
})

当然你也可以使用 ES6 的 async/await 语法来改写上述方法:

methods: {
    async getData() {
        this.arr = [1, 2, 3];
        
        await this.$nextTick();
        
        this.$refs.box.getElementsByTagName('li')[0].innerHTML = 'hello';
    }
}

再举一个例子:在完成 AJAX 请求后更新数据并确保 DOM 已更新

<template>
  <div>
    <h1 ref="header">{{ title }}</h1>
    <button @click="fetchData">获取数据</button>
  </div>
</template>

<script>
import { ref, nextTick } from 'vue';

export default {
  setup() {
    const title = ref('初始标题');
    const header = ref(null); // 添加 ref 来获取 h1 元素

    const fetchData = async () => {
      // 模拟 AJAX 请求
      await new Promise(resolve => setTimeout(resolve, 1000));
      title.value = '新获取的数据标题';

      // 使用 nextTick 确保 DOM 已更新
      await nextTick();

      // 使用 ref 访问更新后的 DOM
      console.log('当前标题:', header.value.innerText);
    };

    return {
      title,
      fetchData,
      header, // 返回 header
    };
  },
};
</script>

接下来我们来分析一下在Vue源码中,nextTick是如何实现延迟回调到下次 DOM 更新循环之后执行的功能。

nextTick 的工作原理

  1. 微任务队列nextTick 会将回调函数推入一个微任务队列,微任务会在当前 JavaScript 事件循环的执行栈清空后、下一个宏任务之前执行。这样可以确保在 DOM 更新完成后再执行相关的逻辑。

  2. DOM 更新的调度:在 Vue 中,当数据变化导致组件重新渲染时,Vue 会先将变化记录在一个队列中,并在下一个 tick(时机)中批量更新 DOM。所有的 DOM 更新操作都会在微任务队列中完成。

  3. 使用 Promise 或 MutationObserver:Vue 的 nextTick 方法可以通过多种方式实现,例如使用 Promise.resolve(), setTimeout(), 或者 MutationObserver。这些方式都能确保传入的回调在 DOM 更新之后被调用。

在Vue 的 nextTick 方法实现的三种方式中,前两种方式相信大家都比较熟悉,其都具备延迟执行的功能,我们也可以直接替换 nextTick 为这两种方式中的一种,同样可以解决问题。这里主要介绍下 MutationObserver 这一 HTML5 特性。

MutationObserver

那么什么是 MutationObserver 呢? MutationObserver 是一个用于监控 DOM 变动的 JavaScript API,允许你观察一个或多个 DOM 节点的变化,比如添加、删除、修改子节点,属性变化等。它是一个非常强大的工具,适合用于响应 DOM 的变化,而不仅仅是依赖于事件的处理。

主要特点

  • 异步观察MutationObserver 的回调是在 DOM 更新后异步执行的,这意味着它不会阻塞主线程,允许高效的更新和处理。
  • 批量处理:当多个变更发生时,MutationObserver 会将它们合并到一个回调中,这样可以减少调用的次数,提高性能。
  • 高效:相比于使用传统的事件监听器(如 DOMSubtreeModified),MutationObserver 提供了更高效的方式来监控 DOM 变化。

使用方法

  1. 创建观察者实例:通过 new MutationObserver(callback) 创建观察者,并传入一个回调函数,该函数会在观察的对象发生变化时被调用。
  2. 配置观察选项:定义需要观察的变更类型,例如子节点的变化、属性的变化等。
  3. 开始观察:使用 observe(targetNode, config) 方法开始观察目标节点。
  4. 停止观察:当不再需要监控时,可以使用 disconnect() 方法停止观察。

示例:

// 选择要观察的 DOM 元素
const targetNode = document.getElementById('myElement');

// 创建一个MutationObserver实例并传入回调函数,这个回调函数会在观察到变化时被调用,并接收两个参数:变化记录列表和观察者实例
const observer = new MutationObserver((mutationsList, observer) => {

    // 循环遍历 mutationsList 数组,数组包含所有发生的变更记录
    for (let mutation of mutationsList) {

        // 通过检查 mutation.type ,可以确定变化的类型
        if (mutation.type === 'childList') {
            console.log('子节点变化:', mutation);
        }
        if (mutation.type === 'attributes') {
            console.log('属性变化:', mutation);
        }
    }
});

// 配置观察选项
const config = {
    childList: true,      // 观察子节点的变化
    attributes: true,     // 观察属性的变化
    subtree: true,         // 观察后代节点
    characterData:true    // 观察文本节点的变化
};

// 开始观察指定的目标节点 (targetNode) 的 DOM 变化,按照给定的配置 (config) 进行监控
observer.observe(targetNode, config);

// 修改目标节点以测试观察者
targetNode.appendChild(document.createElement('div')); // 子节点变化
targetNode.setAttribute('data-changed', 'true');      // 属性变化

// 停止观察
// observer.disconnect();

使用 Proxy 或 setter/getter 来实现深度遍历和立即调用功能

在 JavaScript 中,使用 Proxy 或者 setter/getter 方法可以实现对象属性的深度遍历和变化时的立即调用功能。下面我会分别展示如何使用这两种方法来实现这一功能。

1. 使用 Proxy 实现深度遍历和立即调用

Proxy 是一个强大的工具,可以轻松创建一个代理对象,监控对象的读取和写入操作。下面是一个使用 Proxy 实现深度监听的示例:

function createDeepWatcher(obj, callback) {
    // 如果不是对象,直接返回
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    return new Proxy(obj, {
        set(target, property, value) {
            // 在设置属性之前,打印变化
            console.log(`设置属性: ${property}, 新值: ${value}`);
            target[property] = createDeepWatcher(value, callback); // 递归处理深层对象
            callback(target); // 立即调用回调
            return true; // 返回 true,表示操作成功
        },
        get(target, property) {
            const value = target[property];
            // 返回代理处理的深层对象
            return createDeepWatcher(value, callback);
        }
    });
}

// 示例回调函数
function onChange(updatedObject) {
    console.log('对象发生变化:', updatedObject);
}

// 测试对象
const obj = {
    name: 'Alice',
    details: {
        age: 30,
        address: {
            city: 'Wonderland',
            zip: '12345'
        }
    }
};

// 创建深度监听的对象
const watchedObj = createDeepWatcher(obj, onChange);

// 测试: 修改属性
watchedObj.name = 'Bob'; // 立即调用 onChange
watchedObj.details.age = 31; // 立即调用 onChange
watchedObj.details.address.city = 'Wonderland City'; // 立即调用 onChange

2. 使用 setter/getter 实现深度遍历和立即调用

使用 Object.defineProperty (obj, key, option) getter/setter 也可以实现类似的功能。下面是一个示例:

function createDeepWatcher(obj, callback) {
    const handler = {
        set(target, property, value) {
            console.log(`设置属性: ${property}, 新值: ${value}`);
            target[property] = createDeepWatcher(value, callback); // 递归处理深层对象
            callback(target); // 立即调用回调
            return true; // 返回 true,表示操作成功
        },
        get(target, property) {
            const value = target[property];
            return createDeepWatcher(value, callback); // 递归处理深层对象
        }
    };

    const proxy = new Proxy(obj, handler);
    
    // 使用 Object.defineProperty 为每个属性定义 getter/setter
    Object.keys(obj).forEach(key => {
        Object.defineProperty(proxy, key, {
            get() {
                return createDeepWatcher(target[key], callback);
            },
            set(value) {
                console.log(`设置属性: ${key}, 新值: ${value}`);
                target[key] = createDeepWatcher(value, callback);
                callback(target);
            },
            enumerable: true,
            configurable: true
        });
    });

    return proxy;
}

// 示例回调函数
function onChange(updatedObject) {
    console.log('对象发生变化:', updatedObject);
}

// 测试对象
const obj = {
    name: 'Alice',
    details: {
        age: 30,
        address: {
            city: 'Wonderland',
            zip: '12345'
        }
    }
};

// 创建深度监听的对象
const watchedObj = createDeepWatcher(obj, onChange);

// 测试: 修改属性
watchedObj.name = 'Bob'; // 立即调用 onChange
watchedObj.details.age = 31; // 立即调用 onChange
watchedObj.details.address.city = 'Wonderland City'; // 立即调用 onChange

解释

  • Proxy 方法

    • createDeepWatcher 函数使用 Proxy 包装对象,并定义 get 和 set 行为。
    • 在设置属性时,将新值递归包装成代理对象。
    • 调用回调函数以响应对象的变化。
  • setter/getter 方法

    • 通过 Object.defineProperty 为每个属性定义 get 和 set 方法。
    • 在 set 方法中,跟踪新值并调用回调。
    • 在 get 方法中,确保返回的值被包装为代理对象。

需要注意的事:

  • 性能:使用 Proxy 和 setter/getter 方法都会引入一定的性能开销,特别是在深层嵌套对象的情况下。
  • 兼容性Proxy 是 ES6 的特性,确保你的运行环境支持。

拓展2

proxy的简易解析

Proxy 是 JavaScript 中一个强大的对象,用于定义基本操作(如属性查找、赋值、枚举、函数调用等)的自定义行为。它允许你创建一个对象的代理,拦截并重定义这些操作。使用 Proxy,你可以实现对象的深度监听、验证、日志、数据绑定等功能。

Proxy 的基本用法:

创建一个 Proxy

要创建一个 Proxy,你需要两个参数:

  1. 目标对象 (target):你想要代理的对象。
  2. 处理器对象 (handler):一个对象,其属性是特定操作的拦截器。
// 目标对象
const target = {
    message: 'Hello, world!'
};

// 处理器对象
const handler = {
    get: function(target, property) {
        console.log(`Getting ${property}`);
        return target[property];
    },
    set: function(target, property, value) {
        console.log(`Setting ${property} to ${value}`);
        target[property] = value;
        return true;
    }
};

// 创建代理
const proxy = new Proxy(target, handler);

// 使用代理
console.log(proxy.message); // Getting message
proxy.message = 'Hi, there!'; // Setting message to Hi, there!

 

set拦截器和get拦截器

“set拦截器”,允许你使用Proxy时为处理属性设置操作而定义的处理器方法。“get拦截器”,允许你拦截并自定义对对象属性的读取操作。

在使用JavaScript的 Proxy对象及其处理程序(handlers)时,参数如targetpropertyvalue有着特定的意义,特别是在不同的陷阱(traps)方法中。以下是对这些参数的解释:

  • target: 这是目标对象,即你想要拦截其操作的对象。当你创建一个代理实例时,这个对象作为第一个参数传递给Proxy构造函数。例如,如果你正在拦截对某个对象的属性访问或修改,那么这个对象就是你的target

  • property: 这是一个字符串或符号,表示你尝试访问或修改的目标对象上的属性键。无论你是试图获取属性值、设置新值还是检查属性的存在性,这个参数都代表了该属性的名字。

  • value: 在某些陷阱中(比如set陷阱),这是指你想要设置给指定属性的新值。它允许你在属性被设置之前对其进行验证、转换或其他任何类型的预处理。

set陷阱为例,下面是一个简单的示例来说明这些参数的用途:

let targetObject = {
    message: 'Hello, world!'
};

let handler = {
    set: function(target, property, value) {
        console.log(`Setting '${property}' to '${value}'`);
        // 在这里可以进行一些验证或者数据处理
        target[property] = value;
        return true; // 表示成功设置属性
    }
};

let proxy = new Proxy(targetObject, handler);

proxy.message = 'Goodbye, world!'; // 输出: Setting 'message' to 'Goodbye, world!'
console.log(proxy.message); // 输出: Goodbye, world!

在这个例子中:
- targer 指的是 targetObject,也就是我们通过代理进行操作的实际对象。
- property 将是 "message",因为这是我们尝试设置值的属性名称。
- value 则是 "Goodbye, world!",这是我们将要赋予 message 属性的新值。

通过这种方式,Proxy和它的陷阱提供了一种强大的方式来定义自定义行为,用于基本的操作,如属性访问、赋值等。

receiver参数的概念与应用场景

在JavaScript中,receiver是一个与Proxy对象和Reflect API紧密相关的概念。它通常指的是当使用代理对象时,作为上下文(即this值)的对象。具体来说,receiver参数出现在Proxy的陷阱方法(如getset等)以及Reflect相关方法中。下面将详细介绍receiver的概念及其应用场景。

receiverProxy中的作用

当你定义一个Proxy并为其设置陷阱时,比如getset陷阱,receiver参数会传递给这些陷阱函数。这个参数代表的是当前执行上下文,即调用属性访问或修改操作的那个对象。在大多数情况下,这将是代理对象本身,但在某些复杂的情况下,它可能指向其他对象。

例如,在继承链中,如果子对象通过原型链访问父对象的一个属性,那么在这个过程中,receiver将会是子对象而非父对象或代理对象。这是因为属性访问是相对于子对象发生的,因此需要保持正确的上下文以便正确地处理属性查找。

const target = { name: 'target' };
const proxy = new Proxy(target, {
  get: function (target, key, receiver) {
    console.log(receiver === proxy); // true 或者 false 取决于具体情况
    return Reflect.get(target, key, receiver);
  }
});

receiverReflect中的应用

Reflect API提供了与Proxy陷阱相对应的方法,并允许你在非代理对象上执行相同的操作。在Reflect的一些方法中(如Reflect.get),你也可以传入一个receiver参数来指定操作发生时的上下文。

这意味着你可以控制在获取属性值或者调用方法时,哪个对象会被当作this值。这对于确保内部方法正确地引用了外部对象的属性非常有用。

const obj = {
  name: 'zhangmazi',
  get aliasName() {
    return this.name; // 这里的this指向receiver
  }
};

const proxy = new Proxy(obj, {
  get(target, property, receiver) {
    return Reflect.get(target, property, receiver);
  }
});

console.log(proxy.aliasName); // 输出 'zhangmazi'

在这个例子中,当我们访问proxy.aliasName时,aliasName的getter方法内的this实际上是proxy对象,而不是原始的obj对象,这是因为我们在get陷阱中使用了receiver参数传递给了Reflect.get

总的概括大致为:

  • receiver 在Proxy陷阱和Reflect方法中扮演着重要角色,它决定了在属性访问或方法调用期间使用的上下文。
  • 它使得我们可以灵活地控制如何处理对代理对象及其原型链上的属性访问,特别是在复杂的继承结构中尤为重要。
  • 正确使用receiver可以帮助我们更好地管理对象的行为,尤其是在涉及动态计算属性值或维护正确的this绑定时。

 

对低开销的静态组件使用 v-once

Vue 提供了 v-once 指令用于只渲染元素和组件一次,一般可以用于存在大量静态数据组件的更新性能优化,注意是大量静态数据,因为少数情况下我们的页面渲染会因为一些静态数据而变慢。当你希望在初始渲染后不再更新某些部分时,可以使用 v-once。这对于静态内容的性能优化非常有用,因为它可以减少 Vue 的虚拟 DOM 的比较和更新开销。

使用场景

  • 你有一些静态数据,不会在未来的更新中改变。
  • 你想提高性能,尤其是在渲染复杂组件时。

示例

<template>
  <div>
    <h1 v-once>{{ title }}</h1>
    <p>{{ description }}</p>
    <button @click="updateDescription">Update Description</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: 'Hello, Vue!',
      description: 'This will change.'
    };
  },
  methods: {
    updateDescription() {
      this.description = 'The description has changed!';
    }
  }
};
</script>

在上面的示例中,<h1> 标签使用了 v-once 指令。虽然 description 会在点击按钮时更新,但 title 的内容不会随着数据变化而更新,因为它已经被标记为一次性渲染。

优势

  1. 性能提升:减少了 Vue 在虚拟 DOM 中的比较,尤其是在大型应用中,这可以显著提高性能。
  2. 简化代码:更清晰地表明哪些部分的内容是静态的。

注意事项

  • 使用 v-once 后,相关内容将不会再更新,因此要确保内容确实是静态的。
  • 过度使用 v-once 可能会导致代码变得难以维护,尤其是在需要频繁更改的内容上误用了此指令的情况下
  • 对于动态数据,你仍然需要使用正常的数据绑定。

结合其他技术

除了单独使用 v-once,还可以结合其他 Vue 特性来进一步优化性能。例如,与 v-memo 结合使用,可以缓存计算结果,适用于根据特定依赖进行缓存的内容。此外,还可以通过保持传给子组件的 props 稳定来减少不必要的更新。对v-meno感兴趣的小伙伴可以去读一下这篇文章。

使用vue-server-renderer实现服务端渲染

vue-server-renderer 是 Vue.js提供的一个库,专门用于支持服务端渲染(Server-Side Rendering, SSR)。它允许开发者在服务器上渲染 Vue 组件为 HTML 字符串,然后将这些字符串发送到客户端。这不仅可以改善首次加载时间,还可以优化搜索引擎优化(SEO),因为搜索引擎爬虫可以直接读取完整的 HTML 内容。

服务器端渲染(SSR)

服务器端渲染是指在服务器上生成 HTML 内容,并将其发送到客户端。客户端接收到完整的 HTML 后,可以立即展示内容,而不需要等待 JavaScript 加载和执行。这种方式对于 SEO 友好,因为搜索引擎可以直接抓取服务器返回的 HTML 内容。

基本用法

要使用 vue-server-renderer,你需要首先安装它以及 Vue:

npm install vue vue-server-renderer

接下来,创建一个简单的 Vue 应用:

// app.js
import { createSSRApp } from 'vue';

export function createApp() {
  const app = createSSRApp({
    template: `<div>Hello World</div>`
  });
  return { app };
}

然后,创建一个服务器来处理请求并进行渲染:

// server.js
import express from 'express';
import { createServer } from 'vue-server-renderer';
import { createApp } from './app';

const server = express();

server.get('*', (req, res) => {
  const { app } = createApp();
  const renderer = createServer(app);

  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error');
      return;
    }
    res.end(html);
  });
});

server.listen(8080, () => {
  console.log('Server is running at http://localhost:8080');
});

运行你的服务器:

node server.js

流式渲染

除了同步渲染外,vue-server-renderer 还支持流式渲染,这对于大型应用来说尤其有用,因为它可以减少内存消耗并提高性能。你可以通过 renderToStream() 方法实现这一点。

需要注意的点

  • vue-server-renderer 只能在 Node.js 环境中工作,因为它依赖于一些 Node.js 原生模块 。
  • 在服务端渲染时,只有 beforeCreate 和 created 生命周期钩子会被调用。其他生命周期钩子(如 mounted)只会在客户端执行 。
  • 为了避免状态共享问题,应该为每个请求创建一个新的 Vue 应用实例 。

拓展3

使用 errorHandler 捕获异常堆栈后如何解析 source-map 信息?

在前端开发中,当我们捕获到异常堆栈信息后,尤其是在线上环境中,这些异常堆栈通常是经过压缩和混淆后的代码产生的。为了能够准确地定位到源代码中的错误位置,我们需要使用 source-map 文件来还原原始的源代码映射关系。

使用 errorHandler 捕获异常

首先,在 Vue 应用中,我们可以通过配置 Vue.config.errorHandler 来捕获应用内的所有未处理的异常:

Vue.config.errorHandler = function (err, vm, info) {
  console.error('Error:', err);
  console.log('Vue instance:', vm);
  console.log('Info:', info);
};

这里的 err 是具体的错误对象,vm 是当前发生错误的 Vue 实例,而 info 包含了关于错误的额外信息,比如它发生在哪个生命周期钩子中。

解析 Source Map

一旦你捕获到了异常信息,下一步就是解析这个异常堆栈。这通常涉及到几个步骤:

  1. 收集 Source Map 文件:确保你的生产环境打包时生成了 .map 文件,并且这些文件可以被安全地访问或上传至服务器端。

  2. 加载 Source Map 文件:你需要根据报错信息中的 URL 加载对应的 .map 文件。如果 Source Map 文件存储在服务器上,你可以通过 HTTP 请求获取它们。

  3. 使用 Source Map 库进行解析:可以使用 Mozilla 的 source-map 库来帮助解析 Source Map 文件。以下是一个简单的例子展示如何使用该库来解析堆栈跟踪:

const sourceMap = require('source-map');

// 假设你已经得到了 source map 文件的内容
const rawSourceMap = /* ... */;

// 创建 SourceMapConsumer 实例
new sourceMap.SourceMapConsumer(rawSourceMap).then((consumer) => {
  // 解析原始的错误位置
  const originalPosition = consumer.originalPositionFor({
    line: errorLine, // 错误所在的行号
    column: errorColumn // 错误所在的列号
  });

  console.log(originalPosition); // 输出原始源码的位置信息
});
  1. 集成到监控系统:将上述逻辑集成到你的前端监控系统中,使得每次捕获到异常时都能自动解析并记录原始的源码位置。这对于快速定位线上问题至关重要 。

除了本文介绍的 Vue 盲点外,还有哪些需要注意并容易忽略的 API?

在 Vue 3 的组合式 API 中,有一些功能和特性是开发者在使用时容易忽略的。以下是一些需要注意并容易忽略的 API 和概念:

1. watchEffect

watchEffect 是一个自动追踪其依赖的函数,它会在其所依赖的响应式数据发生变化时重新执行。这可以替代传统的 watch,并在某些情况下提供更简洁的代码。

import { ref, watchEffect } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const doubled = ref(0);

    watchEffect(() => {
      doubled.value = count.value * 2; // 自动追踪 count
    });

    return { count, doubled };
  },
};

2. provide 和 inject

provideinject 用于在组件树中传递数据,允许你在祖先组件中提供数据,并在后代组件中注入使用。这对于深层嵌套组件尤其有用。

// 父组件
import { provide, ref } from 'vue';

export default {
  setup() {
    const theme = ref('dark');
    provide('theme', theme);
  }
};

// 子组件
import { inject } from 'vue';

export default {
  setup() {
    const theme = inject('theme');
    return { theme };
  }
};

 

3. teleport

teleport 允许你将某些内容渲染到 DOM 的其它位置。这对于模态框、通知等非常有用。

<template>
  <teleport to="body">
    <div class="modal">This is a modal!</div>
  </teleport>
</template>

 

4. v-model 的多重用法

在 Vue 3 中,v-model 的用法有所变化,支持多个 v-model 绑定。可以为每个 v-model 指定名称。

html
<template>
  <ChildComponent v-model:foo="foo" v-model:bar="bar" />
</template>

 

5. nextTick

nextTick 是一个异步函数,用于在下次 DOM 更新循环结束后执行回调。这对于在更改数据后对 DOM 状态做进一步操作时非常有用。

import { nextTick } from 'vue';

const updateData = () => {
  data.value = newValue;
  nextTick(() => {
    // 在 DOM 更新后执行
    console.log('DOM updated!');
  });
};

 

6. defineAsyncComponent 和 Suspense

defineAsyncComponent用于定义异步组件,可以在需要时再加载组件。这对于大型应用程序的性能优化非常重要。Suspense 是一个用于处理异步组件加载的特性。

<template>
  // 创建一个包裹异步组件的容器
  <Suspense>

    // 展示异步加载完成后的内容
    <template #default>
      <AsyncComponent />
    </template>

    // 展示加载中的内容
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() =>
  import('./MyAsyncComponent.vue')
);

export default {
  components: {
    AsyncComponent
  }
};
</script>

7. ref 和 reactive 的区别

在使用响应式数据时,ref 用于基本数据类型,而 reactive 通常用于对象和数组。记住这两者的区别可以帮助你在选择时更加得心应手。

import { ref, reactive } from 'vue';

const count = ref(0); // 基本数据类型
const state = reactive({ count: 0 }); // 对象

8. emit 和 $emit 的区别

在组合式 API 中,使用 emit 函数来发出事件,而在选项式 API 中使用 $emit。确保在组合式 API 中使用正确的方式。

// 使用 emit
setup(props, { emit }) {
  const handleClick = () => {
    emit('custom-event', { data: 'someData' });
  };
}

// 选项式 API 中
methods: {
  handleClick() {
    this.$emit('custom-event', { data: 'someData' });
  }
}

9. ref 的解包

在使用 ref 时,需要注意在模板中直接使用 ref 时会自动解包(即去掉 .value)。但是在 JavaScript 中使用时,仍需使用 .value

const count = ref(0);

// 在模板中可以直接使用 count
// 在 JavaScript 中使用时需要 count.value

setup函数

在 Vue 3 中,setup 函数是组合式 API 的核心部分。它是一个组件的选项之一,用于在组件实例被创建之前执行逻辑。setup 函数的主要目的是提供一个地方来声明响应式状态、计算属性和方法,它在组件生命周期的早期阶段被调用。

基本使用

setup 函数是在组件实例被创建之前执行的一个函数,它允许开发者定义响应式数据、计算属性、方法以及生命周期钩子等。该函数接收两个参数:propscontext

  • props: 包含了父组件传递给当前组件的所有属性。
  • context: 提供了一些有用的属性,比如 attrsslotsemit 和 expose 等,它们分别代表透传属性、插槽、事件触发器和暴露公共接口的方法 。

访问 Props 和 Emit Events

setup 函数中,可以直接访问 props 参数来获取父组件传递的数据,并且可以通过 context.emit 方法向父组件发送事件。

export default {
  props: ['title'],
  setup(props, context) {
    console.log(props.title);
    context.emit('someEvent', 'data');
  }
}

 或者这种写法:

setup(props, { emit }) {
  // 使用 props
  console.log(props.someProp);

  const handleClick = () => {
    emit('custom-event', { data: 'someData' });
  };

  return { handleClick };
}

使用响应式数据

通过引入 refreactive 函数,可以在 setup 函数内部创建响应式状态。

import { ref } from 'vue';
export default {
  setup() {
    const count = ref(0);
    return { count };
  }
}

生命周期钩子

setup 函数中,可以使用 onMountedonUnmounted 等 Composition API 提供的生命周期钩子。

import { onMounted, onUnmounted } from 'vue';
export default {
  setup() {
    onMounted(() => console.log('Component has been mounted!'));
    onUnmounted(() => console.log('Component will be unmounted!'));
  }
}

<script setup>

对于单文件组件(SFC),Vue 3 还提供了<script setup>语法糖,这是一种更加简洁的写法,使得组合式API在SFC中更加符合人体工程学。

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
  <button @click="count++">{{ count }}</button>
</template>

 需要注意点的:

  • setup 函数本身不具有对组件实例的访问权限,即在 setup 内部访问 this 将会是 undefined 。
  • 应当避免在 setup 函数内解构 props 对象,因为这样做会导致失去响应性 。
  • setup 函数应该是同步的,只有在特定情况下(如配合 Suspense 组件)才可能使用异步 setup 。

http://www.kler.cn/a/503251.html

相关文章:

  • java项目启动时,执行某方法
  • 风水算命系统架构与功能分析
  • 用 Python 从零开始创建神经网络(十九):真实数据集
  • Apache JMeter 压力测试使用说明
  • fisco bcosV3 Table智能合约开发
  • VScode python 远程调试
  • 针对服务器磁盘爆满,MySql数据库始终无法启动,怎么解决
  • CVPR 2024 3D方向总汇包含(3DGS、三维重建、深度补全、深度估计、全景定位、表面重建和特征匹配等)
  • PHP:构建高效Web应用的强大工具
  • 网络安全 | 人工智能在网络安全中的应用与挑战
  • 第一次作业三种方式安装mysql(Windows和linux下)作业
  • 安装Kubernetes,容器为containerd
  • 学习软件工程产品质量模型
  • R语言贝叶斯方法在生态环境领域中的高阶技术
  • C++基础篇——string 类型
  • 【C盘清理】C盘清理工具、Unity缓存文件转移
  • CMU卡内基梅隆大学「软体机器人动态手旋转笔」
  • docker-compose部署kafka 3.3.1 kraft
  • 计算机网络之---DNS协议
  • 《使用人工智能心脏磁共振成像筛查和诊断心血管疾病》论文精读
  • 【STM32-学习笔记-4-】PWM、输入捕获(PWMI)
  • 基于微信小程序的汽车销售系统的设计与实现springboot+论文源码调试讲解
  • IntelliJ IDEA Type Hierarchy Scope Pattern 学习指南
  • qt 窗口(window/widget)绘制/渲染顺序 QPainter QPaintDevice Qpainter渲染 失效 无效 原因
  • 【Python项目】图像信息隐藏技术的实现
  • Python入门10:高阶函数