分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

news2024/12/25 14:27:14

2022年11月23日首发于掘金,现在同步到公众号。

11. 前言

大家好,我是若川。推荐点右上方蓝字若川视野把我的公众号设为星标。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》,目前是掘金关注人数(4.6k+人)第一的专栏,写有20余篇源码文章。


我们开发业务时经常会使用到组件库,一般来说,很多时候我们不需要关心内部实现。但是如果希望学习和深究里面的原理,这时我们可以分析自己使用的组件库实现。有哪些优雅实现、最佳实践、前沿技术等都可以值得我们借鉴。

相比于原生 JS 等源码。我们或许更应该学习,正在使用的组件库的源码,因为有助于帮助我们写业务和写自己的组件。

如果是 Vue 技术栈,开发移动端的项目,大多会选用 vant 组件库,目前(2022-11-20)star 多达 20.5k,最新版本是 v4.0.0-rc7。我们可以挑选 vant 组件库学习,我会写一个组件库源码系列专栏[1],欢迎大家关注。

  • vant 4 即将正式发布,支持暗黑主题,那么是如何实现的呢

  • 跟着 vant4 源码学习如何用 vue3+ts 开发一个 loading 组件,仅88行代码

  • 分析 vant4 源码,如何用 vue3 + ts 开发一个瀑布流滚动加载的列表组件?

这次我们来学习倒计时组件,`countdown`[2]

学完本文,你将学到:

1. 如何开发一个更优雅的毫秒级渲染的倒计时组件
2. 学会使用 requestAnimationFrame
3. 等等

22. 准备工作

看一个开源项目,我们可以先看 README.md[3] 再看 github/CONTRIBUTING.md[4]

2.1 克隆源码

You will need Node.js >= 14[5] and pnpm[6].

# 推荐克隆我的项目
git clone https://github.com/lxchuan12/vant-analysis
cd vant-analysis/vant

# 或者克隆官方仓库
git clone git@github.com:vant-ui/vant.git
cd vant

# 安装依赖,如果没安装 pnpm,可以用 npm i pnpm -g 安装,或者查看官网通过其他方式安装
pnpm i

# 启动服务
pnpm dev

执行 pnpm dev 后,这时我们打开倒计时组件 http://localhost:5173/#/zh-CN/count-down

33. 倒计时组件可谓是十分常用

在各种电商类或者其他的移动端页面中,倒计时真的是太常见了。我们自己也基本能够快速的写一个倒计时组件。代码实现参考这里,主要是 JavaScript。码上掘金倒计时初步代码@若川[7]

代码中,我直接使用的 setInterval 和每秒钟执行一次。把倒计时的时候减去1s,当倒计时毫秒数不足时用 clearInterval 清除停止定时器。

但如果要实现毫秒级的倒计时这种方法行不通。 另外 setInterval 这种做法,并不是最优的。 那么,vant 倒计时组件中,是如何处理毫秒级和实现倒计时呢。

带着问题我们直接找到 countdown demo 文件:vant/packages/vant/src/count-down/demo/index.vue。为什么是这个文件,我在之前文章跟着 vant4 源码学习如何用 vue3+ts 开发一个 loading 组件,仅88行代码分析了其原理,感兴趣的小伙伴点击查看。这里就不赘述了。

44. 利用 demo 调试源码

组件源码中的 TS 代码我不会过多解释。没学过 TS 的小伙伴,推荐学这个TypeScript 入门教程[8]。 另外,vant 使用了 @vue/babel-plugin-jsx[9] 插件来支持 JSX、TSX

a5c311d5a99453468f26db714afef15a.png
倒计时组件
// vant/packages/vant/src/count-down/demo/index.vue
<script setup lang="ts">
import VanGrid from '../../grid';
import VanGridItem from '../../grid-item';
import VanCountDown, { type CountDownInstance } from '..';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site';
import { showToast } from '../../toast';

const t = useTranslate({
  'zh-CN': {
    reset: '重置',
    pause: '暂停',
    start: '开始',
    finished: '倒计时结束',
    millisecond: '毫秒级渲染',
    customStyle: '自定义样式',
    customFormat: '自定义格式',
    manualControl: '手动控制',
    formatWithDay: 'DD 天 HH 时 mm 分 ss 秒',
  },
});

const time = ref(30 * 60 * 60 * 1000);
const countDown = ref<CountDownInstance>();

// 开始
const start = () => {
  countDown.value?.start();
};
// 暂停
const pause = () => {
  countDown.value?.pause();
};
// 重置
const reset = () => {
  countDown.value?.reset();
};
const onFinish = () => showToast(t('finished'));
</script>

<template>

  <!-- 基本使用 -->
  <demo-block :title="t('basicUsage')">
    <van-count-down :time="time" />
  </demo-block>

  <!-- 自定义渲染 -->
  <demo-block :title="t('customFormat')">
    <van-count-down :time="time" :format="t('formatWithDay')" />
  </demo-block>

  <!-- 毫秒级渲染 -->
  <demo-block :title="t('millisecond')">
    <van-count-down millisecond :time="time" format="HH:mm:ss:SS" />
  </demo-block>

  <!-- 自定义样式-->
  <demo-block :title="t('customStyle')">
    <van-count-down :time="time">
      <template #default="currentTime">
        <span class="block">{{ currentTime.hours }}</span>
        <span class="colon">:</span>
        <span class="block">{{ currentTime.minutes }}</span>
        <span class="colon">:</span>
        <span class="block">{{ currentTime.seconds }}</span>
      </template>
    </van-count-down>
  </demo-block>

  <!-- 手动控制 -->
  <demo-block :title="t('manualControl')">
    <van-count-down
      ref="countDown"
      millisecond
      :time="3000"
      :auto-start="false"
      format="ss:SSS"
      @finish="onFinish"
    />
    <van-grid clickable :column-num="3">
      <van-grid-item icon="play-circle-o" :text="t('start')" @click="start" />
      <van-grid-item icon="pause-circle-o" :text="t('pause')" @click="pause" />
      <van-grid-item icon="replay" :text="t('reset')" @click="reset" />
    </van-grid>
  </demo-block>
</template>

demo 文件中,我们可以看出 import VanCountDown, { type CountDownInstance } from '..';,引入自 vant/packages/vant/src/count-down/index.ts。我们继续来看入口 index.ts

55. 入口 index.ts

主要就是导出一下类型和变量等。

// vant/packages/vant/src/count-down/index.ts
import { withInstall } from '../utils';
import _CountDown from './CountDown';

export const CountDown = withInstall(_CountDown);
// 默认导出
// import xxx from 'vant'
export default CountDown;
export { countDownProps } from './CountDown';
export type { CountDownProps } from './CountDown';
export type {
  CountDownInstance,
  CountDownThemeVars,
  CountDownCurrentTime,
} from './types';

declare module 'vue' {
  export interface GlobalComponents {
    VanCountDown: typeof CountDown;
  }
}

withInstall 函数在之前文章5.1 withInstall 给组件对象添加 install 方法 也有分析,这里就不赘述了。

我们可以在这些文件,任意位置加上 debugger 调试源码。

截两张调试图。

调试 Countdown setup

55a26164d6cc349dd8ed40981ec6ffec.png
调试 setup

调试 useCountDown

3926d794d63b8cced88398df5997cf6a.png
调试 useCountDown

我们跟着调试,继续分析 Countdown

66. 主文件 Countdown

// vant/packages/vant/src/count-down/CountDown.tsx
import { watch, computed, defineComponent, type ExtractPropTypes } from 'vue';

// Utils
import {
  truthProp,
  makeStringProp,
  makeNumericProp,
  createNamespace,
} from '../utils';
import { parseFormat } from './utils';

// Composables
import { useCountDown } from '@vant/use';
import { useExpose } from '../composables/use-expose';

const [name, bem] = createNamespace('count-down');

export const countDownProps = {
  time: makeNumericProp(0),
  format: makeStringProp('HH:mm:ss'),
  autoStart: truthProp,
  millisecond: Boolean,
};

export type CountDownProps = ExtractPropTypes<typeof countDownProps>;

export default defineComponent({
  name,

  props: countDownProps,

  emits: ['change', 'finish'],

  setup(props, { emit, slots }) {
    // 代码省略,下文叙述
  },
});

6.1 setup 部分

这一部分主要使用了useCountDown

setup(props, { emit, slots }) {
  // useCountDown 组合式 API
  const { start, pause, reset, current } = useCountDown({
    // 传入的时间毫秒数,+ 是字符串转数字
    time: +props.time,
    // 毫秒级渲染
    millisecond: props.millisecond,
    // 回调事件,onChange, onFinish
    onChange: (current) => emit('change', current),
    onFinish: () => emit('finish'),
  });

  // 格式化时间
  const timeText = computed(() => parseFormat(props.format, current.value));

  // 重置,重新开始
  const resetTime = () => {
    reset(+props.time);

    if (props.autoStart) {
      start();
    }
  };

  watch(() => props.time, resetTime, { immediate: true });

  // 导出 start、pause、reset
  useExpose({
    start,
    pause,
    reset: resetTime,
  });

  return () => (
    // 有传入插槽,使用插槽,支持自定义样式,传入解析后的时间对象
    <div role="timer" class={bem()}>
      {slots.default ? slots.default(current.value) : timeText.value}
    </div>
  );
},

6.2 useExpose 暴露

import { getCurrentInstance } from 'vue';
import { extend } from '../utils';

// expose public api
export function useExpose<T = Record<string, any>>(apis: T) {
  const instance = getCurrentInstance();
  // 合并到 getCurrentInstance().proxy 上
  if (instance) {
    extend(instance.proxy as object, apis);
  }
}

通过 ref 可以获取到 Countdown 实例并调用实例方法,详见组件实例方法[10]

Vant 中的许多组件提供了实例方法,调用实例方法时,我们需要通过 ref 来注册组件引用信息,引用信息将会注册在父组件的 $refs 对象上。注册完成后,我们可以通过 this.$refs.xxx 或者

const xxxRef = ref();
xxxRef.value.xxx();

访问到对应的组件实例,并调用上面的实例方法。

77. useCountDown 组合式 API

7.1 parseTime 解析时间

// vant/packages/vant-use/src/useCountDown/index.ts
import {
  ref,
  computed,
  onActivated,
  onDeactivated,
  onBeforeUnmount,
} from 'vue';
import { raf, cancelRaf, inBrowser } from '../utils';

export type CurrentTime = {
  days: number;
  hours: number;
  total: number;
  minutes: number;
  seconds: number;
  milliseconds: number;
};

export type UseCountDownOptions = {
  time: number;
  // 毫秒
  millisecond?: boolean;
  onChange?: (current: CurrentTime) => void;
  onFinish?: () => void;
};

const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

// 解析时间
function parseTime(time: number): CurrentTime {
  const days = Math.floor(time / DAY);
  const hours = Math.floor((time % DAY) / HOUR);
  const minutes = Math.floor((time % HOUR) / MINUTE);
  const seconds = Math.floor((time % MINUTE) / SECOND);
  const milliseconds = Math.floor(time % SECOND);

  return {
    total: time,
    days,
    hours,
    minutes,
    seconds,
    milliseconds,
  };
}

以上这大段代码,parseTime 是主要函数,解析时间,生成天数、小时、分钟、秒、毫秒的对象。

7.2 useCountDown 真实逻辑

真实逻辑这一段可以不用细看。可以调试时再细看。

主要就是利用 Date.now() 会自己走的原理。

初始化开始:结束时间 = 当前时间戳 + 剩余时间
获取:剩余时间 = 结束时间 - 当前时间戳
加上自己定时器逻辑循环
剩余时间就是真实流逝的时间
如果是毫秒级渲染,就直接赋值剩余时间
如果不是,那就判断是同一秒才赋值

设计的十分巧妙,看到这里,我们可能感慨:不得不佩服。

// 简化版 一
const useCountDown = (options) => {
  let endTime;
  let remain = options.time;
  const getCurrentRemain = () => Math.max(endTime - Date.now(), 0);
  const start = () => {
    endTime = Date.now() + remain;
  }
  const setRemain = (value) => {
    remain = value;
  };
  return {
    start,
  }
}
const { start } = useCountDown({time: 3 * 1000});
start();

码上掘金倒计时简化版二

码上掘金倒计时简化版二@若川[11]

// vant/packages/vant-use/src/useCountDown/index.ts
function isSameSecond(time1: number, time2: number): boolean {
  return Math.floor(time1 / 1000) === Math.floor(time2 / 1000);
}

export function useCountDown(options: UseCountDownOptions) {
  let rafId: number;
  let endTime: number;
  let counting: boolean;
  let deactivated: boolean;

  const remain = ref(options.time);
  const current = computed(() => parseTime(remain.value));

  const pause = () => {
    counting = false;
    cancelRaf(rafId);
  };

  const getCurrentRemain = () => Math.max(endTime - Date.now(), 0);

  const setRemain = (value: number) => {
    remain.value = value;
    options.onChange?.(current.value);

    if (value === 0) {
      pause();
      options.onFinish?.();
    }
  };

  const microTick = () => {
    rafId = raf(() => {
      // in case of call reset immediately after finish
      if (counting) {
        setRemain(getCurrentRemain());

        if (remain.value > 0) {
          microTick();
        }
      }
    });
  };

  const macroTick = () => {
    rafId = raf(() => {
      // in case of call reset immediately after finish
      if (counting) {
        const remainRemain = getCurrentRemain();

        if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
          setRemain(remainRemain);
        }

        if (remain.value > 0) {
          macroTick();
        }
      }
    });
  };

  const tick = () => {
    // should not start counting in server
    // see: https://github.com/vant-ui/vant/issues/7807
    if (!inBrowser) {
      return;
    }

    if (options.millisecond) {
      microTick();
    } else {
      macroTick();
    }
  };

  const start = () => {
    if (!counting) {
      endTime = Date.now() + remain.value;
      counting = true;
      tick();
    }
  };

  const reset = (totalTime: number = options.time) => {
    pause();
    remain.value = totalTime;
  };

  // 组件被卸载之前被调用
  onBeforeUnmount(pause);

  // 激活
  onActivated(() => {
    if (deactivated) {
      counting = true;
      deactivated = false;
      tick();
    }
  });

  onDeactivated(() => {
    if (counting) {
      pause();
      deactivated = true;
    }
  });

  // 返回方法和当前时间对象
  return {
    start,
    pause,
    reset,
    current,
  };
}

我们继续来看 rafcancelRaf,是如何实现的。

88. raf、cancelRaf、inBrowser 实现

// 判断是不是浏览器环境,你可能会问,为啥要判断?因为 SSR (服务端渲染)不是浏览器环境。
export const inBrowser = typeof window !== 'undefined';

// Keep forward compatible
// should be removed in next major version
export const supportsPassive = true;

export function raf(fn: FrameRequestCallback): number {
  return inBrowser ? requestAnimationFrame(fn) : -1;
}

export function cancelRaf(id: number) {
  if (inBrowser) {
    cancelAnimationFrame(id);
  }
}

// double raf for animation
export function doubleRaf(fn: FrameRequestCallback): void {
  raf(() => raf(fn));
}

上文代码,主要一个 APIrequestAnimationFrame、cancelAnimationFrame

我们这里简单理解为 window.requestAnimationFrame() 中的回调函数,每 16.67ms 执行一次回调函数即可。

也就是类似 setTimeout、clearTimeout

const timeId = setTimeout( () => {
  // 16.67ms 执行一次
  console.log('16.67ms 执行一次');
}, 16.67);

clearTimeout(timeId);

也可以自行搜索这个 API 查阅更多资料。比如 MDN 上的解释。

mdn window.requestAnimationFrame[12]

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。

备注: 若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 window.requestAnimationFrame()

99. 支持格式化时间,默认 HH:mm:ss

9.1 parseFormat 处理格式化

再来看看,组件中,是如何格式化时间的。这个值得我们参考。我们很多时候可能都是写死天数、小时等文案。不支持自定义格式化。

// vant/packages/vant/src/count-down/utils.ts
import { padZero } from '../utils';
import { CurrentTime } from '@vant/use';

export function parseFormat(format: string, currentTime: CurrentTime): string {
  const { days } = currentTime;
  let { hours, minutes, seconds, milliseconds } = currentTime;

  // 有 DD 参数,补零替换,没有则小时数加上天数
  if (format.includes('DD')) {
    format = format.replace('DD', padZero(days));
  } else {
    hours += days * 24;
  }

  // 有 HH 参数,补零替换,没有则分钟数加上小时数
  if (format.includes('HH')) {
    format = format.replace('HH', padZero(hours));
  } else {
    minutes += hours * 60;
  }

  // 有 mm 参数,补零替换,没有则秒数加上分钟数
  if (format.includes('mm')) {
    format = format.replace('mm', padZero(minutes));
  } else {
    seconds += minutes * 60;
  }

  // 有 mm 参数,补零替换,没有则毫秒数加上秒数
  if (format.includes('ss')) {
    format = format.replace('ss', padZero(seconds));
  } else {
    milliseconds += seconds * 1000;
  }

  // 毫秒数 默认补三位数,按照格式最终给出对应的位数
  if (format.includes('S')) {
    const ms = padZero(milliseconds, 3);

    if (format.includes('SSS')) {
      format = format.replace('SSS', ms);
    } else if (format.includes('SS')) {
      format = format.replace('SS', ms.slice(0, 2));
    } else {
      format = format.replace('S', ms.charAt(0));
    }
  }

  // 最终返回格式化的数据
  return format;
}

9.2 padZero 补零

// vant/packages/vant-compat/node_modules/vant/src/utils/format.ts
// 补零操作
export function padZero(num: Numeric, targetLength = 2): string {
  let str = num + '';

  while (str.length < targetLength) {
    str = '0' + str;
  }

  return str;
}

行文自此,我们就分析完了毫秒级渲染的倒计时组件的实现。

1010. 总结

我们来简单总结下。通过 demo 文件调试,入口文件,主文件,useCountDown 组合式 API,插槽等。 分析了自定义格式、毫秒级渲染、自定义样式(利用插槽)等功能的实现。

其中毫秒级渲染,主要就是利用 Date.now() 和 (window.requestAnimationFrame)每 16.67ms 执行一次回调函数。

大致流程如下:

初始化开始:结束时间 = 当前时间戳 + 剩余时间
获取:剩余时间 = 结束时间 - 当前时间戳
加上自己定时器逻辑循环(`window.requestAnimationFrame`)每 16.67ms 执行一次回调函数
剩余时间就是真实流逝的时间
如果是毫秒级渲染,就直接赋值剩余时间
如果不是,那就判断是同一秒才赋值

看完这篇源码文章,再去看 CountDown 组件文档[13],可能就会有豁然开朗的感觉。再看其他组件,可能就可以猜测出大概实现的代码了。

如果是使用 reactTaro 技术栈,感兴趣也可以看看 taroify CountDown 组件的实现 文档[14],源码[15]

如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力

参考资料

[1]

组件库源码系列专栏: https://juejin.cn/column/7140264842954276871

[2]

countdown: https://vant-contrib.gitee.io/vant/v4/#/zh-CN/count-down

[3]

README.md: https://github.com/youzan/vant

[4]

github/CONTRIBUTING.md: https://github.com/youzan/vant/blob/main/.github/CONTRIBUTING.md

[5]

Node.js >= 14: https://nodejs.org

[6]

pnpm: https://pnpm.io

[7]

码上掘金倒计时初步代码@若川: https://code.juejin.cn/pen/7167966535649230883

[8]

TypeScript 入门教程: http://ts.xcatliu.com/

[9]

@vue/babel-plugin-jsx: https://www.npmjs.com/package/@vue/babel-plugin-jsx

[10]

组件实例方法: https://vant-contrib.gitee.io/vant/v4/#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa

[11]

码上掘金倒计时简化版二@若川: https://code.juejin.cn/pen/7168892330752081928

[12]

mdn window.requestAnimationFrame: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

[13]

CountDown 组件文档: https://vant-contrib.gitee.io/vant/#/zh-CN/count-down

[14]

文档: https://taroify.gitee.io/taroify.com/components/countdown

[15]

源码: https://github.com/mallfoundry/taroify/tree/main/packages/core/src/countdown

53f0207f060aeb90963528965a6fe078.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,持续组织了一年多源码共读活动,帮助5000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

299afe5f7ddfd85e6f3a274dd71a52ee.jpeg

扫码加我微信 lxchuan12、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 lxchuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354224.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浙江工商大学2023年硕士研究生 入学考试初试成绩查询通知及说明

根据往年的情况&#xff0c;2023浙江工商大学MBA考试初试成绩可能将于2月21日下午两点公布&#xff0c;为了广大考生可以及时查询到自己的分数&#xff0c;杭州达立易考教育为大家汇总了信息。一、成绩查询考生可以登录中国研究生招生信息网&#xff08;http://yz.chsi.com.cn/…

MySQL - 介绍

前言 本篇介绍认识MySQL&#xff0c;重装mysql操作 如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流&#xff0c;共同进步&#xff01; 本文开始 1.什么是数据库? 数据库: 一种通过SQL语言操作管理数据的软件; 重装数据库的卸载数据库步骤 : ① 停止MySQL服…

分享96个HTML体育竞技模板,总有一款适合您

分享96个HTML体育竞技模板&#xff0c;总有一款适合您 96个HTML体育竞技模板下载链接&#xff1a;https://pan.baidu.com/s/1k2vJUlbd2Boduuqqa0EWMA?pwdj8ji 提取码&#xff1a;j8ji Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 北京奥运火炬PSD模板 奥运…

CCNP350-401学习笔记(101-150题)

101、Refer to the exhibit SwitchC connects HR and Sales to the Core switch However, business needs require that no traffic from the Finance VLAN traverse this switch. Which command meets this requirement? A. SwitchC(config)#vtp pruning B. SwitchC(config)#…

信息时代企业的核心特征-读《硅谷之谜》

引言 几年前读完《浪潮之巅》上下部之后买的书&#xff0c;后来一直搁置没读&#xff0c;直到最近&#xff0c;每天晚上读一点&#xff0c;才把读完&#xff0c;虽然它说自己是《浪潮之巅》的续集&#xff0c;但是内容其实和《浪潮之巅》关系不大&#xff0c;直接读也没有什么问…

再学C语言38:指针操作

C提供了6种基本的指针操作 示例代码&#xff1a; #include <stdio.h>int main(void) {int arr[5] {1, 2, 3, 4, 5};int * p1, *p2, *p3;p1 arr; // 把一个地址赋给指针p2 &arr[2]; // 把一个地址赋给指针printf("指针指向的地址&#xff0c;指针指向地址中…

Yaklang websocket劫持教程

背景 随着Web应用的发展与动态网页的普及&#xff0c;越来越多的场景需要数据动态刷新功能。在早期时&#xff0c;我们通常使用轮询的方式(即客户端每隔一段时间询问一次服务器)来实现&#xff0c;但是这种实现方式缺点很明显: 大量请求实际上是无效的&#xff0c;这导致了大量…

matlab离散系统仿真分析——电机

目录 1.电机模型 2.数字PID控制 3.MATLAB数字仿真分析 3.1matlab程序 3.2 仿真结果 4. SIMULINK仿真分析 4.1simulink模型 4.2仿真结果 1.电机模型 即&#xff1a; 其中&#xff1a;J 0.0067&#xff1b;B 0.10 2.数字PID控制 首先我们来看一下连续PID&#xff1…

[一键CV] Blazor 拖放上传文件转换格式并推送到浏览器下载

前言 昨天有个小伙伴发了一个老外java编写的小工具给我,功能是转换西班牙邮局快递Coreeos express的单据格式成Amazon格式,他的需求是改一下程序为匹配转换另一个快递公司MRW格式到Amazon格式,然而我堂堂一个Blazor发烧友,怎么可能去反编译人家的java修改呢?必须直接撸一个Bl…

Docker 快速上手学习入门教程

目录 1、docker 的基础概念 2、怎样打包和运行一个应用程序&#xff1f; 3、如何对 docker 中的应用程序进行修改&#xff1f; 4、如何对创建的镜像进行共享&#xff1f; 5、如何使用 volumes 名称对容器中的数据进行存储&#xff1f;// 数据挂载 6、另一种挂载方式&…

Mongodb WT_PANIC: WiredTiger library panic

文章目录故障现象排查过程1.查看Log2.同步恢复数据故障现象 周五突然收到Mongo实例莫名奇妙挂了告警&#xff0c;一般都是RS复制集架构模式&#xff08;5节点&#xff09;&#xff0c;查看此实例角色为SECONDAR&#xff0c;挂了暂时不影响线上业务&#xff0c;但还是需要尽快修…

前端智能化在淘宝的2022实践总结

过去十年是智能化蓬勃发展的十年&#xff0c;但未来十年会是智能化渗入各领域彻底改变我们生活和工作的十年。阿里前端智能化方向小组历经 4 年的实践和演变&#xff0c;在前端融入业务技术团队和终端融合的背景之下&#xff0c;前端智能化小组在2022年更多以优化拓展基础业务工…

【计算机网络】因特网概述

文章目录因特网概述网络、互联网和因特网互联网历史与ISP标准化与RFC因特网的组成三种交换方式电路交换分组交换和报文交换三种交换方式的对比与总结计算机网络的定义和分类计算机网络的定义计算机网络的分类计算机网络的性能指标速率带宽吞吐量时延时延带宽积往返时间利用率丢…

球员分析-前锋

1、球员位置 1.1柱式中锋 球员&#xff1a;吉鲁、奥斯梅恩、米特罗维奇 1.2防守型前锋 球员&#xff1a;劳塔罗、瓦尔迪、维尔纳 1.3抢点前锋 球员&#xff1a;伊卡尔迪、曼联c罗、因扎吉 1.4组织型前锋 球员&#xff1a;凯恩、本泽马、迪巴拉 2、战术职责 2.1柱式中锋&#xf…

设计模式-状态机模式

参考 什么是状态机&#xff1f; 设计模式-状态机模式 什么是状态机(有限状态自动机) 可以把状态机比作方程式, 你输入当前信息, 就能得到下一个信息 举个例子, 按钮门有两个状态, 关闭状态和打开状态, 如果按下开门按钮, 门的状态就从关闭到打开 状态机就是接受门当前状态…

极兔一面:10亿级ES海量搜索狂飙10倍,该怎么办?

背景说明&#xff1a; ES高性能全文索引&#xff0c;如果不会用&#xff0c;或者没有用过&#xff0c;在面试中&#xff0c;会非常吃亏。 所以ES的实操和底层原理&#xff0c;大家要好好准备。 另外&#xff0c;ES调优是一个非常、非常核心的面试知识点&#xff0c;大家要非…

就业大山之下的网络安全:安逸的安服仔

从去年开始&#xff0c;各个互联网大厂就接二连三的放出了裁员消息&#xff0c;整个互联网行业好像都处于寒冬状态。微博、小米、滴滴、知乎、拼多多等在内的一大批互联网知名企业&#xff0c;也相继传出“人员优化”的消息。 除了国内市场的萧条&#xff0c;国外市场也是不容…

kubernetes教程 --Pod调度

Pod调度 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某…

springboot simple (13) springboot Elasticsearch(Elasticsearch8.5.1)

这里首先简单的介绍了Elasticsearch&#xff0c;然后实现了springboot集成Elasticsearch。 版本&#xff1a; Elasticsearch&#xff1a;v8.5.1 Kibana&#xff1a;v8.5.1 springboot集成elasticsearch有两种方式。 1&#xff09;rest客户端RestHingLevelClient&#xff1b; …

2.2 BeautifulSoup 装载HTML文档

HTML文档结点的查找工具很多&#xff0c;其中 BeautifulSoup 是功能强大且十分流行的查找工具之一。1. BeautifulSoup 的安装安装&#xff1a;pip install bs4导包&#xff1a;from bs4 import BeautifulSoup2. BeautifulSoup 装载HTML文档如果 doc 是一个 HTML 文档&#xff0…