0%

背景描述

实现一个虚拟滚动组件,可以满足列表和瀑布流两种模式

线上预览环境

https://codesandbox.io/s/dong-tai-gao-du-xu-ni-gun-dong-4945px?file=/Masonry.tsx

具体代码实现可以看以上链接

动态高度的计算是在子项渲染之后做的,因此在渲染的时候这里会渲染两次,一次是预渲染,一次是计算过后的渲染

实现高度的组件思路类似,具体可以看代码:

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
import React, { useRef, useEffect } from "react";

const RowRenderer = ({
index,
isScrolling,
key,
style,
state,
render,
setRowHeight
}) => {
const rowRef = useRef<HTMLDivElement | null>(null);
const { showScrollingPlaceholder } = state;
const placeholderContent = (
<div
// className={clsx(styles.row, styles.isScrollingPlaceholder)}
key={key}
style={style}
>
Scrolling...
</div>
);

useEffect(() => {
if (rowRef) {
setRowHeight(index, rowRef.current.clientHeight);
}
return () => {};
}, [rowRef, index]);

return showScrollingPlaceholder && isScrolling ? (
<div>{placeholderContent}</div>
) : (
<div key={key} className="row" style={style}>
<div ref={rowRef}>
<div>{render()}</div>
</div>
</div>
);
};

export default RowRenderer;

如果只是需要实现虚拟列表模式,可以考虑用react-window来实现,因为这个库更小,这里使用了react-virtualized是因为react-window支持的是网格布局,而瀑布流不属于网格布局,所以对于react-window的话不考虑

来看列表的实现:
示例代码,基本可以直接用,有额外需要可以自己做修改

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
import React, { useEffect, useRef, useState } from "react";

import { AutoSizer, List } from "react-virtualized";
import "./list.css";
import RowRenderer from "./Rowrenderer";
interface Props<T extends Record<string, any>> {
children: (p: { index; isScrolling?: boolean; item: T }) => React.ReactNode;
data: T[];
height?: number;
}
export default function VirtualList<T extends Record<string, any>>({
data,
height = 400,
children
}: Props<T>) {
const listRef = useRef<List>();
const itemCount = data?.length;
const [state, setState] = useState({
listHeight: height,
listRowHeight: 50,
overscanRowCount: 10,
rowCount: itemCount,
scrollToIndex: undefined,
showScrollingPlaceholder: false,
useDynamicRowHeight: false
});
const rowHeight = useRef({});

const setRowHeight = (index: number, value: number) => {
rowHeight.current = {
...rowHeight.current,
[index]: value
};

listRef.current.recomputeRowHeights();
};
const _getRowHeight = ({ index }) => {
return rowHeight.current[index] || state.listRowHeight;
};
return (
<AutoSizer disableHeight>
{({ width }) => {
return (
<List
ref={listRef}
className={"List"}
height={state.listHeight}
overscanRowCount={state.overscanRowCount}
// noRowsRenderer={this._noRowsRenderer}
rowCount={state.rowCount}
rowHeight={_getRowHeight}
rowRenderer={({ index, isScrolling, key, style }) => {
return (
<RowRenderer
index={index}
isScrolling={isScrolling}
style={style}
key={key}
setRowHeight={setRowHeight}
render={() => {
return children({ index, isScrolling, item: data[index] });
}}
state={state}
/>
);
}}
scrollToIndex={state.scrollToIndex}
width={width}
/>
);
}}
</AutoSizer>
);
}

这是瀑布流的实现:

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import React, { useEffect, useRef } from "react";
import {
AutoSizer,
CellMeasurer,
CellMeasurerCache,
Masonry
} from "react-virtualized";
import { createCellPositioner } from "react-virtualized/dist/es/Masonry";
interface Props {
itemCount: number;
children: ({
index,
isScrolling
}: {
index: number;
isScrolling: boolean;
}) => React.ReactNode;
height?: number;
}
export default function VirtualMasonry({
itemCount,
children,
height = 300
}: Props) {
const cache = useRef(
new CellMeasurerCache({
defaultHeight: 250,
defaultWidth: 200,
fixedWidth: true
})
);
const state = useRef({
columnWidth: 200,
height,
gutterSize: 10,
overscanByPixels: 0,
windowScrollerEnabled: false
});
const width = useRef(0);
const columnCount = useRef(0);
const scrollTopRefVal = useRef(0);

const cellPositioner = useRef(
createCellPositioner({
cellMeasurerCache: cache.current,
columnCount: columnCount.current,
columnWidth: state.current.columnWidth,
spacer: state.current.gutterSize
})
);
const masonryRef = useRef();
const setMasonryRef = (ref) => {
masonryRef.current = ref;
};

const resetCellPositioner = () => {
const { columnWidth, gutterSize } = state.current;
if (!cellPositioner) return;
cellPositioner.current?.reset?.({
columnCount: columnCount.current,
columnWidth,
spacer: gutterSize
});
};

const calculateColumnCount = () => {
const { columnWidth, gutterSize } = state.current;

columnCount.current = Math.floor(
width.current / (columnWidth + gutterSize)
);
};
const onResize = ({ width: w }) => {
width.current = w;
// 重新计算列的个数
calculateColumnCount();
// 根据列的个数重新计算位置
resetCellPositioner();
// 让组件根据位置重新计算
if (masonryRef) masonryRef?.current?.recomputeCellPositions?.();
};
useEffect(() => {
// 初始化宽度
calculateColumnCount();
}, [width]);
useEffect(() => {
// 初始化cell position

cellPositioner.current = createCellPositioner({
cellMeasurerCache: cache.current,
columnCount: columnCount.current,
columnWidth: state.current.columnWidth,
spacer: state.current.gutterSize
});
// console.info("effect:", cellPositioner.current);
}, [columnCount]);
// console.info("out:", cellPositioner.current);
const rowHeight = useRef({});
const setRowHeight = (index, value) => {
rowHeight.current = {
...rowHeight.current,
[index]: value
};
// onResize({ width: width.current });
};
const CellRenderer = ({ index, key, parent, style, isScrolling }) => {
const { columnWidth } = state.current;

const divRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (divRef) {
setRowHeight(index, divRef.current.clientHeight);
}
}, [divRef]);
return (
<CellMeasurer
cache={cache.current}
index={index}
key={key}
parent={parent}
>
<div
style={{
...style,
width: columnWidth
}}
>
<div
ref={divRef}
style={{
backgroundColor:
"#" + Math.floor(Math.random() * 16777215).toString(16),
borderRadius: "0.5rem",
height: rowHeight.current[index],
marginBottom: "0.5rem",
width: "100%",
fontSize: 20,
color: "white",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{children({ index, isScrolling })}
</div>
</div>
</CellMeasurer>
);
};

const renderMasonry = ({ width: w }) => {
width.current = w;
return (
<Masonry
autoHeight={false}
cellCount={itemCount}
cellMeasurerCache={cache.current}
cellPositioner={cellPositioner.current}
cellRenderer={({ index, key, parent, style, isScrolling }) => {
return (
<CellRenderer
index={index}
key={key}
parent={parent}
style={style}
isScrolling={isScrolling}
/>
);
}}
height={state.current.height}
overscanByPixels={state.current.overscanByPixels}
ref={setMasonryRef}
scrollTop={scrollTopRefVal.current}
width={w}
/>
);
};
const renderAutoSizer = ({ height, scrollTop }) => {
scrollTopRefVal.current = scrollTop;
return (
<AutoSizer
disableHeight
height={height}
onResize={onResize}
overscanByPixels={state.current.overscanByPixels}
scrollTop={scrollTopRefVal.current}
>
{renderMasonry}
</AutoSizer>
);
};

return renderAutoSizer({ height: state.current.height, scrollTop: 0 });
}

选择要转换的格式

计算某个DOM元素距离浏览器顶部的距离

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
import * as React from 'react';

export default function useTopDistance<T extends HTMLDivElement>(defaultInstance = 24) {
const divRef = React.useRef<T>(null);
const [height, setHeight] = React.useState(0);
// 递归获取遍历到的元素的offsetTop
const getElementInstanceToTop = (el: HTMLElement | null): number => {
if (el?.offsetParent) {
return getElementInstanceToTop(el?.offsetParent as HTMLElement) + el.offsetTop + el.scrollTop;
}
return el ? el.offsetTop : 0;
};
// 重新计算高度,因为可能需要在填充数据的时候设置高度
const calculateHeight = React.useCallback(() => {
const { clientHeight } = document.body;
const offsetParentTop = getElementInstanceToTop(divRef.current);
const result = clientHeight - offsetParentTop ?? 0;
setHeight(result - defaultInstance);
}, []);

React.useLayoutEffect(() => {
window.requestAnimationFrame(calculateHeight);
window.addEventListener('resize', calculateHeight);
return () => {
window.removeEventListener('resize', calculateHeight);
};
}, []);

return {
divRef,
height,
calculateHeight,
// rafCalculateHeight: window.requestAnimationFrame(calculateHeight),
};
}

背景:给服务端发送请求的参数中有一个带有+号,并且在url中传递参数
问题:加号以空格的形式发送导致服务端不能正确处理数据
方案:使用window.encodeURIComponent对数据进行处理
以上

在使用antd的select中的search从远端获取数据时option渲染不符合预期行为,

需求背景:下拉框列表数据默认展示二十条数据,支持后端条件过滤,在滚动时加载数据

场景复现:

从视频中可以看到到在搜索数据时列表中有匹配的数据但是在ui上并没有过滤出来
阅读全文 »

loader,它是一个转换器,将A文件进行编译成B文件,比如:将A.less转换为A.css,单纯的文件转换过程。

plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务

问题背景: 递归求两数之间所有数之和

1
2
3
4
5
6
7
8
9
10
11
12
13
function dfs(n,current){  
if(n > 0){
// current 是累加数,只要没有到最后一个就继续累加
return dfs(n-1,current + n)
}else return current
}
function test(n1,n2){
if(n1 > n2) return 0;
// 两数到阶层相减就是两数之间所有数之和
const result = dfs(n2,0) - dfs(n1 - 1,0)
return result
}
console.log(test(1,100))

ummm 我一开始想法是分别求两个数的阶层,然后相减就是两数之间所有数之和,不过这个应该可以不用相减就可以处理的,如下

1
2
3
4
5
6
7
8
9
function dfs2(n1,n2,current){
if(n2 >= n1){
return dfs2(n1,n2 -1,current + n2)
}else return current
}
function test2(n1,n2){
return dfs2(n1,n2,0)
}
console.log(test2(1,100))

直接在计算阶层的时候处理一下累加的判断条件就行了。

冒泡排序

每次排序找到最大的那个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function bubble(arr){
let swapped = false
do{
swapped = false
for(let i =0;i<arr.length-1;i++){
if(arr[i] > arr[i+1]){
[arr[i],arr[i+1]] =[arr[i+1],arr[i]]
swapped = true;
}
}
}while(swapped)
return arr
}
console.log(bubble([9,6,3,6,8,0,6,2,3,5]))
/*
[
0, 2, 3, 3, 5,
6, 6, 6, 8, 9
]
*/

阅读全文 »

npm i -D monaco-editor vite-plugin-monaco-editor 安装依赖

discussions

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
<script setup lang="ts">

import { onMounted, ref } from 'vue'
import * as monaco from 'monaco-editor'
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution'
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
//@ts-ignore
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
//@ts-ignore

import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
//@ts-ignore
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
//@ts-ignore
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
//@ts-ignore
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
// @ts-ignore
self.MonacoEnvironment = {
getWorker(_: string, label: string) {
if (label === 'typescript' || label === 'javascript') return new TsWorker()
if (label === 'json') return new JsonWorker()
if (label === 'css') return new CssWorker()
if (label === 'html') return new HtmlWorker()
return new EditorWorker()
}
}

const monacoRef = ref();
const monacoInstance = ref<ReturnType<typeof monaco.editor.create>>()
onMounted(() => {
monacoInstance.value = monaco.editor.create(monacoRef.value,{
language:"typescript",
value:"console.log('hello world')"
})
})
</script>
<template>
<div id="monaco" ref="monacoRef"></div>
</template>
<style scoped>
#monaco{
height: 500px;
width: 500px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const prefix = `monaco-editor/esm/vs`;
import monacoEditorPlugin from "vite-plugin-monaco-editor"

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),monacoEditorPlugin()],
build:{
rollupOptions: {
output: {
manualChunks: {
jsonWorker: [`${prefix}/language/json/json.worker`],
cssWorker: [`${prefix}/language/css/css.worker`],
htmlWorker: [`${prefix}/language/html/html.worker`],
tsWorker: [`${prefix}/language/typescript/ts.worker`],
editorWorker: [`${prefix}/editor/editor.worker`],
},
},
}
}
})

运行结果:

结果