JavaScript

回调地狱

回调函数嵌套回调函数,为了能按顺序执行异步任务 (可读性差

Promise

js对象,异步编程的解决方案,是一个构造函数,自身有resolve、reject等方法,原型上有then、catch方法

特点

  1. 状态只受异步操作的结果影响,不受外界影响。pendingrejectedfulfilled
  2. 一旦状态改变,不会再改变。
  3. 链式调用

async/await

async 函数返回一个 promise 对象,可以用 then 方法添加回调函数。当执行的时候,一旦遇到 await 就先返回,等到触发的异步操作完成,再执行函数体后面的语句

特点

  1. 同步代码编写方式。顺序执行就像写同步代码,符合编码习惯
  2. 多个参数传递。
  3. 同步代码和异步代码可以一起编写。
  4. promise 的优化

BOM

  • navigator: 获取浏览器特性,识别客户端

    1
    2
    const ua = navigator.userAgent
    const isChrome = ua.indexOf('Chrome')
  • screen: 获取屏幕相关信息 screen.height , screen.width

  • location: 获取网址、协议、path、参数、hash等

  • history: 调用浏览器前进、后退功能等

let const var 作用域

在全局作用域中, letconst 声明的全局变量,存储在了一个块级作用域 script 中。 var 声明的变量, function 声明的函数直接挂载到了全局对象 (global) 的属性上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let b = 'let b'
var a = 'var a'
const c = 'const c'
var funA = function () {
console.log('hello')
}
let funB = function () {
console.log('world')
}
function fun(){
console.log('-')
}
// var a, undefined, undefined, -, hello, Error ... not a function
console.log(this.a, this.b, this.c, this.fun(), this.funA(), this.funB())

// 正常输出,先执行函数 再输出变量
console.log(a, b, c, fun(), funA(), funB())

let const var 使用场景

let、const、var 用于声明变量的关键字

  1. var:
  • 可以声明全局变量和局部变量。
  • 变量声明提升,可以在声明之前使用,但是值为 undefined。
  • 可以重复声明同一个变量。
  1. let:
  • 块级作用域的变量声明方式,只在语句块内有效。
  • 不允许重复声明同一个变量。
  • 不会像 var 一样存在变量声明提升的问题。
  1. const:
  • 声明一个只读常量,不允许修改。
  • 也是块级作用域的方式声明变量,只在语句块内有效。
  • 不允许重复声明同一个变量。

总的来说,推荐使用 constlet 声明变量,因为它们的作用域更加明确,可以有效避免变量的问题。

  • const 适合声明不需要修改的常量,

  • let 则适合一般的变量声明。

  • 而在一些特殊场景下,var 也可以用来声明变量。

    在特定场景下,使用 var 声明变量也是可以的,虽然这种情况很少:

    1. 全局作用域下声明变量,这时候使用 var 会更为方便。

    2. 函数中声明变量时,如果希望变量的作用域能够扩展到整个函数,那么使用 var 声明变量就是比较合适的。

    3. 如果需要把同名变量声明在某个嵌套的作用域内,同时又不希望它被外部的作用域访问到,那么使用 var 就是比较好的选择。

      需要注意的是,在使用 var 的时候,要特别小心变量提升(Hoisting),也就是在变量还没有被声明时就可以使用的问题。
      因此,建议在 JavaScript 中尽量使用 let 或 const 来声明变量,尤其是在块级作用域中声明变量时。

数据类型判断

基础数据类型:string、number、boolean、null、undefined、symbol
引用数据类型:object (function、object、array)

  • 原始数据类型在内存中是存储,引用类型存储
  • 栈(stack)为自动分配的内存空间,它由系统自动释放
  • 堆(heap)则是动态分配的内存,大小不定也不会自动释放
  • 在内存中存储方式的不同导致了原始数据类型不可变
  • 原始数据类型和引用数据类型做赋值操作一个是传值一个是传址

typeof

用于判断基础数据类型

  • 对于基础类型,除 null 以外,均可以正确返回
  • 对于引用类型,除 function 以外,均返回 object
1
2
3
4
5
6
7
8
9
10
11
typeof '' // string
typeof 1 // number
typeof Symbol() // symbol
typeof true // boolean
typeof undefined // undefined

typeof null // object ×

typeof [] // object
typeof {} // object
typeof new Function() // function

instanceof

用于判断引用数据类型 (判断两个对象是否属于实例关系,不能判断一个对象实例具体属于哪种类型
xx.proto => XX.prototype // 最终都指向 null

eg: [].__proto__ -> Array.prototype -> Object.prototype -> null

1
2
3
4
5
6
7
8
9
10
11
[] instanceof Array // true
[] instanceof Object // true

{} instanceof Object // true
new Date() instanceof Date // true
new Function() instanceof Function // true
new Function() instanceof Object // true

1 instanceof Number // false
new Number(1) instanceof Number // true
1 == new Number(1)

constructor

用于判断引用数据类型,原型对象内包含一个 constructor 构造器,指向的是构造函数 (对 null、undefined 无效)

1
2
3
4
5
6
7
let num = 1,str = '',obj = {}

num.__proto__.constructor === Number // true
num.__proto__.constructor === Object // false

str.__proto__.constructor === String // true
obj.__proto__.constructor === Object // true

Object.prototype.toString (最严谨的方法)

内置转换方法判断类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var num = 1
var str = 'abc'
var arr = []
var obj = {}
var nul = null
var unde
var bool = true
var foo = function(){}

Object.prototype.toString() // [object Object]
Object.prototype.toString.call(str) // [object String]
Object.prototype.toString.call(num) // [object Number]
Object.prototype.toString.call(arr) // [object Array]
Object.prototype.toString.call(obj) // [object Object]
Object.prototype.toString.call(nul) // [object Null]
Object.prototype.toString.call(unde) // [object Undefined]
Object.prototype.toString.call(bool) // [object Boolean]
Object.prototype.toString.call(foo) // [object Function]

Flvjs

flv.js

中文文档

实用的文档

解决跳帧、断流问题文档

一个可以在HTML5视频中播放.flv视频格式的JavaScript库

API

使用

  1. 安装依赖

npm i –save flv.js

  1. 在文件中引入依赖

import flvjs from ‘flv.js’

  1. 初始化播放器实例,并播放
1
<video id='video' controls></video>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

const videoDom = document.getElementById('video')

if (flvjs.isSupported()) { // 判断浏览器是否支持flvjs
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: '',
isLive: true,
hasAudio: false,
hasVideo: true,
duration: 0,
},
{
enableWorker: false,
autoCleanupSourceBuffer: true, // 对SourceBuffer进行自动清理
autoCleanupMaxBackwardDuration: 12, // 当向后缓冲区持续时间超过此值(以秒为单位)时,请对SourceBuffer进行自动清理
autoCleanupMinBackwardDuration: 8, // 指示进行自动清除时为反向缓冲区保留的持续时间(以秒为单位)。
enableStashBuffer: false, // 关闭IO隐藏缓冲区
isLive: true,
lazyLoad: false,
reuseRedirectedURL: true
})
flvPlayer.attachMediaElement(videoDom)
flvPlayer.load()
flvPlayer.play()

// 监听数据源是否加载完成
flvPlayer.on(flvjs.Events.LOADING_COMPLETE, (res) => {
if (!flvPlayer._receivedCanPlay) {
// 不能播放 销毁
flvPlayer.pause()
flvPlayer.unload()
flvPlayer.detachMediaElement();
flvPlayer.destroy();
// message.error('视频资源获取失败')
}
})

// 所有异常捕获(播放途中断流、网络错误等)
flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) => {
flvPlayer.pause()
flvPlayer.unload()
flvPlayer.detachMediaElement();
flvPlayer.destroy();
})

// 关闭异常
flvPlayer.off(flvjs.Events.ERROR, err => {
console.log('--off异常', err)
})

// 监听数据流请求 只要流在请求就一直监听 (追针应该就是通过定时器写在这里实现的)
flvPlayer.on(flvjs.Events.STATISTICS_INFO, (res) => {
console.log('请求数据信息')
})
}

  1. 跳帧处理

通过改变视频的当前时间来实现
buffered 属性返回 TimeRanges 对象,表示音频/视频的已缓冲部分

TimeRanges 对象的属性:

  • length - 获得音频/视频中已缓冲范围的数量
  • start(index) - 获得某个已缓冲范围的开始位置
  • end(index) - 获得某个已缓冲范围的结束位置
  • 注释:第一个缓冲范围的下标是 0
1
2
3
4
5
6
7
8
9
10
const clearBufferToPlay = () => {
if (flvPlayer && flvPlayer.buffered && flvPlayer.buffered.length) {
const end = flvPlayer.buffered.end(0)
const diff = end - flvPlayer.currentTime;
if (diff > 0) {
flvPlayer.currentTime = end - 0.00001 // 直接等于end会加载不出来
}
}
}

  1. 追针处理

改变视频播放的速率来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const changeSpeadToPlay = () => {
if (flvPlayer && flvPlayer.buffered && flvPlayer.buffered.length) {
const end = flvPlayer.buffered.end(0)
const diff = end - flvPlayer.currentTime;
if (diff > 1) {
// 大于一秒开始倍速播放
videoElement.playbackRate = 1.1
}

if (diff <= 0.5) {
videoElement.playbackRate = 1
}
}
}
  1. 异常处理

后端转流后会出现第一帧卡帧的情况、追针和跳帧在流不稳定的情况下容易出问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
try {
flvPlayerList[index].on(flvjs.Events.STATISTICS_INFO, (res) => {
if (res.decodedFrames === 0) {
// TODO 画面卡顿重连
stuckCount.value[index] = stuckCount.value[index] ? stuckCount.value[index] + 1 : 1
if (stuckCount.value[index] >= 30) {
setAllPlayFlag(false)
// TODO 是否需要重连
setTimeout(() => {
stuckCount.value[index] = 0
handleVideoStatusChange(index, true)
playerList[index].addEventListener('loadedmetadata', () => {
setAllPlayFlag(isAllPlayStatus(currentPage, defaultSize))
})
console.log('---画面卡顿重连')
}, 1000);
}
console.log(`%c STATISTICS_INFO loading ${index} , count:${stuckCount.value[index]} ${playerList[index]?.paused}`, 'color:red')
} else {
stuckCount.value[index] = 0
}
})
} catch (err) {
console.log('拉流异常捕获', err)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const handleProgress = (index: number) => {
// 数据在请求但是画面不动
if (playSt.value[index]) {
if (currentTimeCount.value[index] && currentTimeCount.value[index].key == playerList[index].currentTime) {
currentTimeCount.value[index].count++
if (currentTimeCount.value[index].count >= 10) {

currentTimeCount.value[index] = null
handleVideoStatusChange(index, true)

playerList[index].addEventListener('loadedmetadata', () => {
setAllPlayFlag(isAllPlayStatus(currentPage, defaultSize))
})

console.log('%c 画面卡顿重连', 'color:red')
}
} else {
currentTimeCount.value[index] = { key: playerList[index].currentTime ? playerList[index].currentTime : 0 + '', count: 1 }
}
}
}

内存泄漏

文档提供的配置项设置了之后还是会内存泄漏,目前解决方法是 reload() 可以清除缓存、但是切换标签的时候会清空所有状态,只有在路由跳转的时候用一下

【react】Fiber & Diff & HOC

原文 | 视频

fiber架构

jsx -> babel (转换成虚拟dom) -> render (渲染到页面上,将vDom挂载到真实的dom)

为什么需要fiber

vdom -> dom (renderer 渲染器

reconciler diff vdom进行对比更新 (vdom->dom 是同步的过程

在 16 之前,React 是直接递归渲染 vdom 的,setState 会触发重新渲染,对比渲染出的新旧 vdom,对差异部分进行 dom 操作。

在 16 之后,为了优化性能,会先把 vdom 转换成 fiber,也就是从树转换成链表,然后再渲染。整体渲染流程分成了两个阶段:

  • render 阶段:从 vdom 转换成 fiber,并且对需要 dom 操作的节点打上 effectTag 的标记

  • commit 阶段:对有 effectTag 标记的 fiber 节点进行 dom 操作,并执行所有的 effect 副作用函数。

vdom 转成 fiber 的过程叫做 reconcile(调和),这个过程是可以打断的,由 scheduler 调度执行

fiber就是解决同步更新耗时长的问题的

同步更新存在的问题 -> 怎么该进

  1. 同步不可中断 -> 异步可中断
  2. scheduler 中存在任务优先级

fiber -> window.requestIdleCallback() 解决该方法兼容性问题

window.requestIdleCallback(callack,option) 方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

解决的问题

  1. 新的任务调度,有高优先级任务的时候将浏览器让出来,等浏览器空了在执行
  2. 新的数据结构,可以随时中断,下次进来可以接着执行

节点 -> 子节点 -> 子节点 -> 兄弟节点 -> return父节点

diff算法

diff 算法作用在 reconcile 阶段:

  • 第一次渲染不需要 diff,直接 vdomfiber
  • 再次渲染的时候,会产生新的 vdom,这时候要和之前的 fiber 做下对比,决定怎么产生新的 fiber对可复用的节点打上修改的标记,剩余的旧节点打上删除标记新节点打上新增标记

那为什么浏览器里要做 diff

因为 dom 创建的性能成本很高,如果不做 dom 的复用,那前端框架的性能就太差了。

diff 算法的目的就是对比两次渲染结果,找到可复用的部分,然后剩下的该删除删除,该新增新增。

具体实现

经过 reconcile 之后,会变成这样的 fiber 结构:

再次渲染出 vdom 的时候,也要进行 vdom 转 fiber 的 reconcile 阶段,但是要尽量能复用之前的节点。

那怎么复用呢?

一一对比下不就行了?

先把之前的 fiber 节点放到一个 map 里,key 就是节点的 key:

然后每个新的 vdom 都去这个 map 里查找下有没有可以复用的,找到了的话就移动过来,打上更新的 effectTag:
**( Q: 为什么标记的是更新 A: 不重新创建 dom,但是属性什么的还是要更新 )**

这样遍历完 vdom 节点之后,map 里剩下一些,这些是不可复用的,那就删掉,打上删除的 effectTag;如果 vdom 中还有一些没找到复用节点的,就直接创建,打上新增的 effectTag

核心就是找到可复用的节点,删掉剩下的旧节点,新节点新增。

技术总结

浏览器下使用 react-dom 的渲染器,会先把 vdom 转成 fiber,找到需要更新 dom 的部分,打上增删改的 effectTag 标记,这个过程叫做 reconcile,可以打断,由 scheducler 调度执行。reconcile 结束之后一次性根据 effectTag 更新 dom,叫做 commit

这就是 react 的基于 fiber 的渲染流程,分成 render(reconcile + schedule)commit 两个阶段。

当渲染完一次,产生了 fiber 之后,再次渲染的 vdom 要和之前的 fiber 对比下,再决定如何产生新的 fiber,目标是尽可能复用已有的 fiber 节点,这叫做 diff 算法

所以 React 的 diff 算法是分成两次遍历的:

  • 第一轮遍历,一一对比 vdom 和老的 fiber,如果可以复用就处理下一个节点,否则就结束遍历。

    • 如果所有的新的 vdom 处理完了,那就把剩下的老 fiber 节点删掉就行。

    • 如果还有 vdom 没处理,那就进行第二次遍历:

  • 第二轮遍历,把剩下的老 fiber 放到 map 里,遍历剩下的 vdom,从 map 里查找,如果找到了,就移动过来。

    • 第二轮遍历完了之后,把剩余的老 fiber 删掉,剩余的 vdom 新增。

    • 这样就完成了新的 fiber 结构的创建,也就是 reconcile 的过程。

HOC

以组件做为入参并且返回一个新的组件

使用原因:

  1. 抽离重复代码,实现组件复用
  2. 条件渲染渲染拦截
  3. 拦截组件的生命周期

使用场景:

  1. 属性代理 (模拟修改/操作传入的值)
    props
    state
  1. 条件渲染,外部逻辑的封装

  2. 反向继承(组件复制类组件)

  3. 生命周期的拦截(通过原型链获取生命周期 并绑定this指向)

Q&A

答案源于chatgpt

React 的特点和优势是什么?

  1. 声明式编程
    React 使用声明式编程,可以让开发者更加专注于组件的数据和状态,而不用关心底层的 DOM 操作。

  2. 组件化开发
    React 采用组件化开发的方式,可以将 UI 拆分成多个小的、独立的组件,从而提高代码的可复用性、可测试性和可维护性。

  3. 虚拟 DOM
    React 使用虚拟 DOM,可以避免不必要的 DOM 操作,从而提高性能。虚拟 DOM 是一个轻量级的 JavaScript 对象,它代表了真实 DOM 的结构和状态,React 可以通过比较两个虚拟 DOM 的差异,最小化 DOM 操作的次数。

  4. 单向数据流
    React 使用单向数据流,数据从父组件传递到子组件,子组件通过 props 属性接收数据,并且不能直接修改父组件的数据。这种数据流的方式可以使组件更加可预测、可控,从而提高代码的可读性和可维护性。

  5. 高效的渲染
    React 使用高效的算法来确定组件的更新,可以最小化 DOM 操作的次数,从而提高性能。React 还提供了一些性能优化的技术,如 shouldComponentUpdate、PureComponent、memo 等。

  6. 社区和生态系统
    React 拥有庞大的社区和生态系统,有大量的第三方库和插件可供使用,可以帮助开发者更快地完成项目开发。

什么是 Virtual DOM?它的作用是什么?

把DOM结构转化成JavaScript对象来进行对比、减少DOM的更新

Virtual DOM(虚拟DOM)是 React 的核心概念之一,它是一个轻量级的 JavaScript 对象,代表了真实 DOM 的结构和状态。Virtual DOM 的作用是减少 DOM 操作的次数,从而提高性能。
在 React 中,当组件的状态发生变化时,React 会重新构建 Virtual DOM 树,并且通过对比前后两个 Virtual DOM 树的差异,最小化 DOM 操作的次数。这种方式可以避免不必要的 DOM 操作,从而提高性能。
具体地,React 会将 Virtual DOM 树映射到真实的 DOM 树上,通过 DOM Diff 算法找出需要更新的节点,并且只更新这些节点的内容。由于 Virtual DOM 是一个轻量级的 JavaScript 对象,它的更新速度非常快,因此可以大大提高性能。
另外,Virtual DOM 还可以让开发者更加专注于组件的数据和状态,而不用关心底层的 DOM 操作。这种方式可以使组件更加易于维护和测试,从而提高代码的质量和可读性。
总之,Virtual DOM 是 React 的核心技术之一,它的作用是减少 DOM 操作的次数,提高性能,并且提高代码的可维护性和可读性

什么是 JSX?它有什么优点?

JSX 是一种 JavaScript语法扩展,它可以在 JavaScript 中编写类似于 HTML 的代码。在 React 中,开发者使用 JSX 来描述 UI 组件的结构和样式。

JSX 的优点如下:

  1. 提高代码的可读性
    使用 JSX 可以将组件的结构和样式直接写在 JavaScript 中,从而提高代码的可读性和可维护性。

  2. 提供了静态类型检查
    JSX 可以与 TypeScript 或 Flow 等静态类型检查工具一起使用,可以在编译时检查代码的类型错误,从而提高代码的可靠性

  3. 更加灵活
    JSX 可以使用 JavaScript 的所有特性,包括变量、函数、循环等,从而使组件更加灵活。

  4. 提供了高效的渲染方式
    React 使用 Virtual DOM 来更新组件的 UI,而 JSX 可以直接转换成 JavaScript 对象,从而提高渲染的效率。

  5. 容易上手
    对于熟悉 HTML 和 JavaScript 的开发者来说,使用 JSX 可以使学习 React 更加容易上手。
    总之,JSX 是 React 的核心概念之一,它可以提高代码的可读性、可维护性和可靠性,同时也提供了高效的渲染方式和更加灵活的组件编写方式。

React 中的状态和属性有什么区别?它们如何传递?

React 中状态和属性的区别:

  • 状态 (state) 是组件内部自己管理的数据可以在组件内部进行修改和更新。状态通常是通过 this.state 来访问和修改。
  • 属性 (props) 是组件外部传入的数据,一旦传入就不可以被组件自己修改和更新。属性通常是通过组件的参数来传入。

如何传递状态和属性:

传递属性

通过组件属性传递数据,父组件可以将数据通过属性传递给子组件,子组件通过 this.props 获取传递过来的数据。

传递状态

在组件中定义状态,可以通过 this.state 来访问和修改状态。当状态发生变化时,React 会重新渲染组件。
父组件也可以通过属性的方式向子组件传递回调函数,子组件在合适的时候调用这个回调函数,将状态更新的请求传递给父组件,父组件再通过属性将新的状态传递给子组件,从而达到数据传递的目的。
需要注意的是,状态的修改必须通过 setState 方法进行,而不能直接修改状态,否则可能会出现一些不可预测的问题。

React 中如何实现组件的复用?

  1. 组合
    React 组件可以通过组合方式实现复用。组件可以包含其他组件,从而实现更复杂的 UI。通过组件的 props 属性,可以将数据和函数传递到组件中,从而实现更灵活的组合方式。

  2. 继承
    React 组件也可以通过继承方式实现复用。通过继承 React.Component 类,可以实现代码的复用。子类继承父类的功能,可以通过覆盖父类的方法来实现特定的功能。

  3. 高阶组件
    高阶组件是一种模式,通过将一个组件作为参数传递到一个函数中,返回一个新的组件,从而实现组件的复用。高阶组件可以封装一些通用的逻辑,从而让组件更加简洁。

  4. Render Props
    Render Props 是一种模式,通过将一个组件的 render 方法作为参数传递到一个函数中,返回一个新的组件,从而实现组件的复用。Render Props 可以让组件更加灵活,从而适应不同的场景。

  5. Hook
    Hook 是 React 16.8 引入的新特性,可以让函数组件具有类组件的一些功能,从而实现组件的复用。Hook 可以让组件更加简洁,从而提高代码的可读性和可维护性。

React 中的生命周期有哪些?它们的作用是什么?

React 中的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。每个阶段都有对应的生命周期方法,它们的作用如下:

  1. 挂载阶段
    在挂载阶段,组件将被插入到 DOM 树中。React 中的挂载阶段有如下生命周期方法:
  • constructor: 组件的构造函数,在组件被创建时调用,用于初始化状态和绑定方法。

  • getDerivedStateFromProps: 在组件被挂载之前调用,用于根据新的属性值和状态值计算出新的状态值。

  • componentWillMount: DOM挂在之前执行

  • render: 返回组件的 UI 结构,必须被实现。

  • componentDidMount: 在组件被挂载后调用,用于执行一些副作用操作,如数据请求、DOM 操作等。

  1. 更新阶段
    在更新阶段,组件的状态或属性发生了变化,需要更新组件的 UI。React 中的更新阶段有如下生命周期方法:
  • getDerivedStateFromProps: 在更新阶段也会被调用,用于根据新的属性值和状态值计算出新的状态值。

  • shouldComponentUpdate: 在组件更新之前调用,用于判断是否需要更新组件,可以优化组件的性能。

  • render: 返回组件的 UI 结构,必须被实现。

  • getSnapshotBeforeUpdate: 在组件更新之前调用,用于返回一个快照,在 componentDidUpdate 中使用。

  • componentDidUpdate: 在组件更新之后调用,用于执行一些副作用操作,如数据请求、DOM 操作等。

  1. 卸载阶段
    在卸载阶段,组件将被从 DOM 树中移除。React 中的卸载阶段有如下生命周期方法:
  • componentWillUnmount: 在组件被卸载之前调用,用于清理一些副作用操作,如定时器、事件监听器等。
    总之,React 的生命周期方法可以让开发者在组件的不同阶段执行一些操作,如初始化状态、执行副作用操作、清理资源等。开发者可以根据需要重写这些生命周期方法,以实现自己的业务逻辑。

什么是 React Hooks?它们的作用是什么?如何使用?

它们是一些特殊的函数,可以让函数组件具有类组件的一些功能。React Hooks 的作用是为了让开发者更方便地在函数组件中使用 React 的特性,从而提高代码的可读性和可维护性。
React Hooks 目前提供了以下几个常用的 Hook:

  1. useState: 用于在函数组件中使用状态

  2. useEffect: 用于在函数组件中使用副作用(如数据获取、DOM 操作等)。

  3. useContext: 用于在函数组件中使用上下文

  4. useReducer: 用于在函数组件中使用 Reducer。

  5. useCallback: 用于在函数组件中缓存回调函数

  6. useMemo: 用于在函数组件中缓存计算结果

使用 React Hooks 的步骤如下:

  1. 在函数组件中导入所需的 Hook。

  2. 在函数组件中声明 Hook。

  3. 在 Hook 中使用 React 的特性。

  4. 将 Hook 返回的值用于组件的渲染。

Redux 是什么?它的作用是什么?如何使用?

Redux 是一个用于管理应用程序状态的 JavaScript 库。它可以在应用程序中创建一个全局的状态存储器,使得不同的组件可以轻松地共享和访问应用程序状态。

Redux 的作用包括以下几个方面:

  1. 状态集中管理: Redux 将应用程序状态集中管理,使得开发者可以更加方便地管理应用程序状态,避免了状态分散的问题。

  2. 数据流的清晰可控: Redux 采用了单向数据流的模式,使得数据的流动清晰可控,便于调试和维护。

  3. 方便进行状态管理: Redux 提供了一些方便进行状态管理的工具,如中间件、调试工具等。

使用 Redux 可以分为以下几个步骤:

  1. 安装 Redux 库: 在项目中安装 Redux 库,可以使用 npm 或 yarn 进行安装。

  2. 创建 Store: 在应用程序中创建一个全局的 Store,Store 中保存了应用程序的状态。

  3. 创建 Reducer: Reducer 是一个纯函数,用于描述应用程序状态的变化。Reducer 接收当前的状态和一个 action 作为参数,并返回一个新的状态。

  4. 创建 Action: Action 是一个 JavaScript 对象,用于描述状态的变化。Action 中必须包含一个 type 属性,用于标识这个 Action 的类型。

  5. 创建 Dispatch: Dispatch 是一个函数,用于将 Action 发送到 Reducer 中,从而更新应用程序的状态。

  6. 创建 Subscriber: Subscriber 是一个函数,用于监听 Store 中状态的变化。当状态发生变化时,Subscriber 会被自动调用。

总之,Redux 提供了一种方便、清晰、可控的状态管理方式,可以帮助开发者更加方便地管理应用程序状态,从而提高应用程序的可维护性和可扩展性。

React Router 是什么?它的作用是什么?如何使用?

React Router 是一个用于 React 应用程序中进行路由管理的库。它可以帮助开发者更加方便地管理应用程序的路由,实现页面的跳转和传递参数等功能。

React Router 的作用包括以下几个方面:

  1. 实现路由功能: React Router 可以帮助开发者将应用程序的不同页面进行路由管理,实现页面跳转和参数传递等功能。

  2. 支持嵌套路由: React Router 支持嵌套路由,可以在不同的路由之间进行跳转。

  3. 方便进行路由拦截和重定向: React Router 提供了方便进行路由拦截和重定向的功能。

使用 React Router 可以分为以下几个步骤:

  1. 安装 React Router 库:在项目中安装 React Router 库,可以使用 npm 或 yarn 进行安装。

  2. 创建路由组件:在应用程序中创建路由组件,可以使用 BrowserRouter 或 HashRouter 等组件包裹应用程序的根组件,实现路由的管理。

  3. 配置路由规则:在路由组件中配置路由规则,可以使用 Route 组件来匹配不同的路由规则,并指定对应的组件。

  4. 实现页面跳转:在应用程序中使用 Link 或 NavLink 组件实现页面的跳转。

  5. 传递参数:在路由规则中可以使用参数来匹配不同的路由,也可以使用 location 对象来传递参数。

总之,React Router 提供了一种方便、灵活、可扩展的路由管理方式,可以帮助开发者更加方便地管理应用程序的路由,从而提高应用程序的可维护性和可扩展性。

React 中如何进行性能优化?

  1. 使用 shouldComponentUpdate 避免不必要的渲染。shouldComponentUpdate 是 React 生命周期中的一个方法,在组件更新前被调用,可以通过返回 false 避免组件的不必要渲染。

  2. 使用 PureComponentmemo 减少不必要的渲染。PureComponent 和 memo 都是 React 提供的函数,可以用来优化组件的渲染,避免不必要的更新。

  3. 使用 key 属性优化列表渲染。在渲染列表时,为每个列表项添加一个唯一的 key 属性,可以帮助 React 更好地识别哪些列表项需要更新,从而提高性能。

  4. 懒加载和代码分割。通过按需加载组件和模块,可以减少初始加载时需要下载的代码量,提高页面加载速度。

  5. 使用 React.memo 包装组件,缓存组件。当组件的输入参数没有发生变化时,memo 可以帮助 React 缓存组件的输出结果,避免不必要的重新渲染。

  6. 使用 React DevTools 分析性能。React DevTools 是一个浏览器插件,可以帮助开发者分析组件的渲染性能,找到渲染性能瓶颈并进行优化。

  7. 以上是 React 中常用的性能优化技术,当然还有其他的一些技术,如使用 CDN 加速静态资源加载、使用 Webpack 进行代码分割和懒加载等。开发者可以根据具体的场景选择适合的优化技术。

vue和react的区别,各自的优劣

  1. 数据绑定方式不同:
    Vue 的数据绑定采用双向绑定,可以自动将数据的变化同步到视图中,也可以将视图事件的变化同步到数据中。
    React 的数据绑定采用单向绑定,只能将数据的变化同步到视图中,无法将视图事件的变化直接同步到数据中。

  2. 组件通信方式不同:
    Vue 的组件通信方式包括 props事件,父组件通过 props 向子组件传递数据,子组件通过 $emit 触发事件向父组件传递数据。
    React 的组件通信方式采用 props回调函数,父组件通过 props 向子组件传递数据和回调函数,子组件通过回调函数向父组件传递数据。

  3. 模板语法不同:
    Vue 的模板语法(<template>)更加简洁易懂,可以直接在模板中使用变量和表达式。而 React 的模板语法采用 JSX 语法,需要在 JavaScript 代码中嵌入 HTML 标签和属性

  4. 学习成本不同:
    Vue 的学习成本相对较低,因为它的 API 设计更加简单易用,不需要太多的编程经验就可以快速上手
    React 的学习成本相对较高,因为它的 API 设计更加灵活,需要更多的编程经验才能熟练使用。

  5. 性能表现不同:
    Vue 的性能表现相对较好,因为它采用了虚拟 DOM 技术,可以降低 DOM 操作的频率和复杂度。而 React 的性能表现也相对较好,因为它采用了状态管理和组件化等技术,可以提高代码的可维护性和可复用性。

总之,Vue 和 React 都具有各自的优点和缺点,开发者可以根据自己的需求选择适合自己的框架。
如果需要快速开发简单应用,建议使用 Vue;如果需要开发大型应用或需要更高的灵活性和可扩展性,建议使用 React

ECharts

ECharts

常用配置

tooltip 提示框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
tooltip:{
show: true | false,
trigger: 'item' | 'axis' | 'none', // 触发类型
formatter: '' | ({marker,name,percent,})=>'', // 浮层自定义内容 marker图标 percent占比 name名称
}
}

params = {
componentType: 'series',
// 系列类型
seriesType: string,
// 系列在传入的 option.series 中的 index
seriesIndex: number,
// 系列名称
seriesName: string,
// 数据名,类目名
name: string,
// 数据在传入的 data 数组中的 index
dataIndex: number,
// 传入的原始数据项
data: Object,
// 传入的数据值。在多数系列下它和 data 相同。在一些系列下是 data 中的分量(如 map、radar 中)
value: number|Array|Object,
// 坐标轴 encode 映射信息,
// key 为坐标轴(如 'x' 'y' 'radius' 'angle' 等)
// value 必然为数组,不会为 null/undefied,表示 dimension index 。
// 其内容如:
// {
// x: [2] // dimension index 为 2 的数据映射到 x 轴
// y: [0] // dimension index 为 0 的数据映射到 y 轴
// }
encode: Object,
// 维度名列表
dimensionNames: Array<String>,
// 数据的维度 index,如 0 或 1 或 2 ...
// 仅在雷达图中使用。
dimensionIndex: number,
// 数据图形的颜色
color: string,
// 饼图,漏斗图的百分比
percent: number
}

legend 图例组件

  • series内的数据对象必须要有 name 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
legend:{
...
itemStyle:{}, // 图例‘图形’的样式
lineStyle:{}, // 图例‘线’的样式
formatter: '{name}' | (name)=>'', // 图例自定义内容
icon: 'circle' | 'rect' | 'roundRect' | 'triangle' | 'diamond' | 'pin' | 'arrow' | 'none' // 图例项的icon

// 针对多个series进行个性化设置
data: [
{
name:'a',
...
},
{
name:'b',
...
}
]
},
series:[
{
name:'a',
...
}
]
}

grid 坐标系网格

文字过长 通过left/bottom的距离设置 eg:16 + fontSize * maxYLabelLength * 1.2,

1
2
3
4
5
6
7
8
9
{
grid:{
// 距离容器 上下左右的距离
left(right/top/bottom): 20 (具体像素) | '20%'(百分比) | 'left'|'center'|'middle'|'right'|'bottom'|'top',

// grid组件的宽高
width(height): string | number | 'auto'
}
}

yAxis y轴

  • 多个y轴的情况用数组,name对应seriesname
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
yAxis:[
{
name:'a',
nameLocation:'center' | 'start' | 'end', // 坐标轴名称显示位置
nameGap:number, // 坐标轴名称与轴线的距离
nameRotate:number, // 名称的旋转角度

type:'value' | 'time' | 'category' | 'log', // 数值轴、时间轴、类目轴、对数轴
min: number | string | (value)=>number, // 坐标刻度最小值
max: number | string | (value)=>number, // 坐标刻度最大值
minInterval: number, // 最小间隔
splitNumber: number, // 坐标分割段数 配合min、max使用

// 坐标轴轴线相关
axisLine:{

}
},
]
}

xAxis x轴

  • 和y轴类似

dataZoom 区域缩放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

dataZoom: [
{
type: 'inside', // 内置
yAxisIndex: 0,
startValue: data.length,
endValue: data.length - 12,
filterMode: 'filter', // 主轴 'filter',辅轴 'empty'
zoomOnMouseWheel: false,
moveOnMouseWheel: true,
moveOnMouseMove: false
},
{
type: 'slider', // 滚动条
...
}
],

ProComponents

ProTable

Columns配置项

  • valueEnum
  • valueType
  • ProFields
  • From.Item
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77


    const columns = [
    {
    title:'', // 标签名称
    dataIndex:'', // 绑定字段
    ellipsis: true | false,
    valueEnum: [{label:'',value:''}],
    valueType:'text' | 'date' // 值类型 ...
    order: number // 表单中的权重 越大越靠前
    search: true | false, // 搜索启用/隐藏
    colSize: number, // 表单占用格子数量 colSpan*span
    hideInSearch: true | false,
    hideInTable:true | false,
    hideInForm:true | false,
    initialValue:any, // 初始值

    // table的render
    renderText: (text,record,index,action)=>string,
    render: (text,record,index,action)=>ReactNode|ReactNode[],

    // 表单的输入组件 proFields / antd / 自定义组件
    renderFormItem: (item,form)=>RecatNode,
    // 表单配置
    fieldProps: {
    precision:1 // 精度 ... antd 表单组件相关配置
    },
    formItemProps: { // Form.Item 相关配置
    rules:[ { required:true|false } ],
    {
    message:'',
    validator: (record,value){
    if(value)return Promise.resolve()
    return Promise.reject()
    }
    }
    }

    },
    ],

    /*
    valueEnum :
    password 密码输入框
    money 金额输入框
    textarea 文本域
    date 日期
    dateTime 日期时间
    dateWeek 周
    dateMonth 月
    dateQuarter 季度输入
    dateYear 年份输入
    dateRange 日期区间
    dateTimeRange 日期时间区间
    time 时间
    timeRange 时间区间
    text 文本框
    select 下拉框
    treeSelect 树形下拉框
    checkbox 多选框
    rate 星级组件
    radio 单选框
    radioButton 按钮单选框
    progress 进度条
    percent 百分比组件
    digit 数字输入框
    second 秒格式化
    avatar 头像
    code 代码框
    switch 开关
    fromNow 相对于当前时间
    image 图片
    jsonCode 代码框,但是带了 json 格式化
    color 颜色选择器
    cascader 级联选择器
    */

websocket

websocket

在单个TCP连接上进行全双工通信的协议。浏览器服务器需要完成一次握手,两者之间就能创建持久性的连接,并进行双向数据传输。

websocket连接基本步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// 创建webscoket实例
const ws = new WebSocket('ws://8.140.175.191:5008/v1/ws')

// 连接监听
ws.onopen = function (e) {
console.log('start', e)

// 向服务器推送消息
ws.send('...')
}

// 断连监听
ws.onclose = function (e) {
console.log('close', e)
}

// 接收消息
ws.onmessage = function (e) {
console.log('message', e)
// 对获取到的信息进行处理 ... (全局弹框之类的)
}

// 连接发生错误
ws.onerror = function () {
// ...
}


hooks&ahooks

简介

在不使用class的情况下使用state以及其他react特性
=> 不论是有状态组件还是无状态组件都可以用函数来声明

useState

useState通过在函数组件里调用它来给组件添加一些内部 state
useState会返回一对值:当前状态一个让你更新它的函数
你可以在事件处理函数中或其他一些地方调用这个函数。
(类似 this.setState,但是它不会把新的 state 和旧的 state 进行合并)
useState的参数:state的初始值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 引入 useState
import React,{useState} from 'react';

export default function Test(){
// 声明state变量count和更新count的方法setCount (解构赋值)
const [count,setCount] = useState(18) // 赋给count的初始值为18
return(
<div>
<h3>
count: {count} {/* count的使用 */}
</h3>
{/* setCount的使用 */}
<button onClick={()=>{setCount(count+1)}}>add</button>
</div>
)
}

// Class 方法
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
render() {
return (
<div>
<h3>
count: {this.state.count}
</h3>
<button onClick={()=>this.setState({ count: this.state.count + 1 })}>add</button>
</div>
);
}
}

声明多个state变量

1
2
3
4
5
6
7
function Test() {
// state的值是按顺序存储调用的所以不能对他的声明进行条件判断
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}

有个问题,我按照官方文档给的示例代码写的时候
useState(..)会标红
报错:

React Hook “useState” is called in function “test” which is neither a React function component or a custom React Hook function

后面发现是函数命名的问题
解决方法:

  1. 函数名/组件名 首字母大写
  2. 驼峰命名法在前面+use也可 (useTest)

useEffect

effect hook在函数组件中执行副作用操作 (异步的)
副作用: 用来代替生周期、是componentDidMount,componentDidUpdate,componentWillUnmount的组合(替代品)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React,{useState,useEffect} from 'react';

export default function Test(){
const [count,setCount] = useState(18)
const [age,setAge] = useState(0)
useEffect(()=>{
document.title = count
},[count])
// 可接收第二个参数 只有在 count`改变时`触发更新 类似于computed
return(
<div>
<h3>
count: {count}
</h3>
<h3>
Age: {age}
</h3>
<button onClick={()=>{setCount(count+1)}}>add</button>
<button onClick={()=>{setAge(age+10)}}>add</button>
</div>
)
}

useContext 状态管理类似于eventBus

  1. 公共状态存储文件 createContent.tsx
1
2
3
4
5
import { createContext } from "react";

const myContext = createContext(null)

export default myContext;
  1. 父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Button } from 'antd';
import React, { createContext, useState } from 'react';
import myContext from './createContext';
import ChildPage from './components/ChildPage'

export default () => {
const [count, setCount] = useState<number>(0)

return (
<div>
<h1>父组件</h1>
<Button onClick={() => setCount(count + 1)}>点击 {count}</Button>

<hr />

// 提供器
<myContext.Provider value={count}>
<ChildPage />
</myContext.Provider>
</div>
);
};

  1. 子组件
1
2
3
4
5
6
7
8
9
10
11
import React, { useContext } from "react"
import myContext from "../createContext"

const ChildPage: React.FC<any> = () => {

const count = useContext(myContext) // 通过useContext直接获取到context数据

return (<h1>子组件:{count}</h1>)
}

export default ChildPage

useReducer 需要逻辑处理state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 初始值
const initialState = { count: 0 }
// 数据操作
const reducer = (state: { count: number; }, action: { type: string; }) => {
switch (action.type) {
case "add": return { count: state.count + 1 };
case "del": return { count: state.count - 1 };
default: throw new Error()
}
}

// 数据声明
const [state, dispatch] = useReducer(reducer, initialState)


// 使用

<h1>Count:{state.count}</h1>
<Button onClick={() => dispatch({ type: 'add' })}>reducer add</Button>
<Button onClick={() => dispatch({ type: 'del' })}>reducer del</Button>

useEffect & useMemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const dom = useRef()
const state = useReactive({ num: 1, childInfo: 'hello' })

useEffect(() => {
// mounted 的时候执行 模板已经挂在到页面上,dom可以获取到
console.log('render effect', dom, state.num)
// dom -> { current:HTMLElement }
}, [state.num])

useMemo(() => {
// 相当于create 的时候执行 模板没有挂在到页面上,dom获取不到
console.log('render memo', dom, state.num)
// dom -> { current:undefined }
}, [state.num])

useReactive 响应式数据,直接修改属性可以刷新视图

1
const state = useReactive(initialState: Record<string,any>) // 参数为一个对象

React.memo && useCallback()

在父组件中引用子组件,只要父组件有数据改变都会触发子组件刷新
React.memo()用来控制是否触发子组件刷新

React.memo()

接收两个参数,第一个是纯函数的组件,第二个参数用于对比props控制是否刷新
如果不传第二个参数的话则是对props进行浅对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { useEffect } from "react"
interface ChildProps {
count: number
}
const Child: React.FC<ChildProps> = ({count}) => {
console.log('child render')

return <>
child
count: {count}
</>
}

function isRefresh(prevProps: ChildProps, nextProps: ChildProps) {
if (prevProps.count === nextProps.count) {
return true
}
return false
}

export default React.memo(Child, isRefresh)

useCallback

缓存的是函数、优化子组件防止组件的重复渲染
返回一个函数,只有在以来变化的时候才会更新

  • 通过父组件传递给子组件的方法被调用时,会触发所有子组件更新,不管关联的值是否改变
  • 如果不用useCallback一个输入框改变会触发两个输入框组件重新渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// child.tsx
interface ChildProps {
count: number
onChange: any
}
const Child: React.FC<ChildProps> = ({ count, onChange }) => {

useEffect(() => {
console.log('%c child refresh', 'color:red')
})

return <>
child
<input value={count} onChange={onChange} />
</>
}
export default React.memo(Child)


// parent.tsx
export default () => {
let [childCount, setChildCount] = useState<number>(0)
let [childCount2, setChildCount2] = useState<number>(0)

useEffect(() => {
console.log('%c parent refresh', 'color:blue')
}, [])


// const handleChange = (e) => {
// console.log('%c change', 'color:yellow')
// setChildCount(e.target.value)
// }

// const changeChild = (e) => {
// console.log('child change')
// setChildCount2(e.target.value)
// }


const handleChange = useCallback((e) => {
console.log('%c change', 'color:yellow')
setChildCount(e.target.value)
}, [])

const changeChild = useCallback((e) => {
console.log('child change')
setChildCount2(e.target.value)

}, [])

return (
<div style={{ padding: '50px' }}>
<Child count={childCount} onChange={handleChange} />
<br />
<Child count={childCount2} onChange={changeChild} />
</div>
)
}

Video标签

video标签相关

大部分控件都能隐藏 但是更多控件那个控件隐藏不了 采取了自欺欺人的方法… 设置高度父元素溢出隐藏挡住

1
2
3
4
5
6
7
8
9
10
<!--controlsList  -->
<video
controlsList='nofullscreen nodownload noremoteplayback'
disablePictureInPicture
className="videoItems"
muted
controls
width="100%"
height="100%"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&::-webkit-media-controls-time-remaining-display,
&::-webkit-media-controls-current-time-display,
&::-webkit-media-controls-play-button,
&::-webkit-media-controls-fullscreen-button,
&::-webkit-media-controls-timeline,
&::-webkit-media-controls-volume-slider,
&::-webkit-media-controls-mute-button,
&::-webkit-media-controls-overflow-menu-list {
display: none;
}

&::-webkit-media-controls-enclosure {
overflow: hidden;
}

&::-webkit-media-controls-panel {
height: calc(100% + 50px);
}

阻止默认事件

video默认事件 单击播放/暂停 双击全屏

现需求是不能通过单击操作视频的播放/暂停 但是又不想去掉controls因为去掉了没有loading样式

单击默认事件阻止之后 双击的全屏事件也不能实现了QAQ , 所以双击也得自己实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const videoExitFullScreen = () => {
const dom = document

// 浏览器兼容性处理
if (dom.exitFullscreen) {
dom.exitFullscreen()
} else if (dom.mozCancelFullScreen) {
dom.mozCancelFullScreen()
} else if (dom.webkitCancelFullScreen) {
dom.webkitCancelFullScreen()
}
}

// 阻止video单击播放停止的默认事件
const onceClick = (event) => {
event.preventDefault();
}

// 还原video双击全屏事件
const singleFullScreen = (index: number) => {
const isFull = document.fullscreenElement
if (isFull) {
videoExitFullScreen()
} else {
videoFullScreen(index)
}
}

video播放按钮

因为没有加载数据源的时候video的播放按钮是禁用状态,而且监听不到他的点击事件

解决思路

  • 通过css样式写死透明度为 1 (假装成能点的样子)
  • 将一个透明的div覆盖在播放按钮的位置上
  • 给div加点击事件、加载数据源,video有数据源之后就直接销毁div,原生的播放暂停按钮可以点击啦
1
2
3
4
&::-webkit-media-controls-play-button {
// TODO 视频播放按钮
opacity: 1;
}
1
2
3
4
5
6
7
8
9
10
11
<div className={styles["video-container"]}>
<video id='video'
controlsList='nodownload noremoteplayback'
disablePictureInPicture
className={styles['hide-video-control']}
onClick={handleClick}
controls />
</div>

{!flv && <div className={styles["play-cover"]} onClick={handleCoverClick}></div>}

video画中画

正常切换标签页画中画状态是可以切换回去的
但是路由切换的话,组件被卸载了,就回不去了
就需要对画中画进行禁用 给video加上disablePictureInPicture属性

2022年度总结

关键词:疫情

这一年上半年在前公司饱受低代码折磨后把“蓄谋已久”的离职计划付诸行动
6月初离职、6月下旬入职新公司,7月又收到别的offer,考虑后还是呆在了这家公司(虽然过后又有些后悔...
这年又是限电又是频繁封控的确实人的精神状态不是很好、戾气也很大
年底彻底开之后,没多久就阳了,这是两年多来第一次阳
有点猝不及防、物资紧缺根本买不到需要的东西
好在前同事施舍了各种防疫物资、加上症状相对较轻,算是度过了难关

经历这次疫情之后,好像想开了一点点
不知道明天会发生什么离谱的事情、不要把时间浪费在等待当中
生活不在别处、当下即是全部

23年的目标是当周末特种兵、把四川以及周边地区都走一遍!
  • Copyrights © 2019-2023 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信