滚动加载

一个简单的滚动加载实现:通过Web API IntersectionObserver观察最后一个列表元素,当它出现在可视区域时,解除观察,同时异步地加载数据,并展示一个loading,在加载数据的回调中取消loading,并更新数据,这时ScrollLoad中state.data改变,重新渲染,原尾元素被卸载,断开IntersectionObserver的连接,新的尾元素挂载为ObservableScrollItem,其他元素渲染为BaseScrollItem,如此循环;

每次加载数据会导致整个列表重新渲染,可以优化;

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
const fetchMockData = () => {
return new Promise((resolve) => {
setTImeout(() => {
resolve(new Array(1).fill("sssssssssssss"));
}, 2000);
})
}

class BaseScrollItem extends React.Component {
render() {
return <div className="scrollitem" ref={this.props.internalRef}>{this.props.children}</div>
}
}

class ObservableScrollItem extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef(null);
}
componentDidMount() {
const node = this.ref.current;
this.observer = new IntersectionObserver(([entry], observer) => {
if (entry.isIntersecting) {
observer.unobserve(node);
this.props.loadItems();
}
});
this.observer.observe(node);
}
componentWillUnmount() {
this.observer.disconnect();
}
render() {
return <BaseScrollItem internalRef={this.ref}>{this.props.children}</BaseScrollItem>
}
}

export class ScrollLoad extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: new Array(5).fill("sdasadsadsads")
};
}
loadItems = async () => {
this.setState({ loading: true });
const value = await fetchMockData();
this.setState({ loading: false, data: [...this.state.data, ...value] });
}
render() {
return <div>
{
this.state.data.map((item, index, arr) => {
return index === arr.length - 1
? <ObservableScrollItem loadItems={this.loadItems}>{index}-----{item}</ObservableScrollItem>
// 其实React默认会为Array Children挨个添加值为index的key,这里写不写无所谓
: <BaseScrollItem key={index}>{index}-----{item}</BaseScrollItem>
})
}
{
this.state.loading && <BaseScrollItem>Loading...</BaseScrollItem>
}
</div>
}
}

列表虚拟化

列表虚拟化就是只渲染部分列表项,具体思路:两层容器,外层控制可见区域大小,内层计算一个高度(列表项数量 * 列表项高度,或列表项高度累加),内层超高会使外层出现滚动条,监听外层容器滚动事件,通过event.currentTarget.scrollTop / 列表项高度得到可视区域的列表项的起始下标start,从start开始遍历列表数据,取若干条数据渲染,注意列表项的key设为列表数据下标,列表项为绝对定位,top通过下标 * 列表项高度动态计算而来;

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
import React from "react";
import "./index.css";

function ListItem({ top, children }) {
return <div className="listItem" style={{ top }}>{children}</div>
}


const ITEM_HEIGHT = 35;

function VirtualList({ sourceData, itemCount = 8 }) {
const [start, setStart] = React.useState(0);

const scrollHandler = React.useCallback((e) => {
const newStart = Math.ceil(e.target.scrollTop / ITEM_HEIGHT);
if (newStart !== start) {
setStart(newStart);
}
}, [start, setStart]);

const renderedItems = React.useMemo(() => {
const end = start + itemCount;
const items = [];
for (let i = start; i < end && i < sourceData.length; i++) {
items.push(<ListItem key={i} top={i * ITEM_HEIGHT}>{sourceData[i]}</ListItem>);
}
return items;
}, [start, itemCount, sourceData]);

return <div className="container">
<div className="listContainer" style={{ height: itemCount * ITEM_HEIGHT }} onScroll={scrollHandler}>
<div className="list" style={{ height: sourceData.length * ITEM_HEIGHT }}>
{renderedItems}
</div>
</div>
</div>
}

export function MockVirtualList() {
const LEN = 100000;
const data = new Array(LEN);
for (let i = 0; i < LEN; i++) {
data[i] = `${i}-------${Math.random() * i}`;
}

return <VirtualList sourceData={data} />
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.container {
padding: 50px;
}

.listContainer {
position: relative;
overflow: auto;
}

.list {
position: relative;
width: 100%;
}

.listItem {
position: absolute;
border: 1px solid blue;
height: 35px;
box-sizing: border-box;
padding: 5px;
width: 100%;
}

常见业务之单点登录(SSO)

简介

SSO 是 single sign-on 的缩写,国内统称单点登录,其实翻译为单一登录更为准确,因为其内涵就是对于多系统的场景下,只进行一次登录即可访问所有子系统资源。

[注]:阅读前需掌握前置知识 JWT,Session 和 Cookie。

流程

SSO 的标准实现包含以下几类角色:

  1. 认证中心(Central Authentication Server)
阅读全文

LeetCode Hard 32. 最长有效括号

32. 最长有效括号

解题思路

注释比较长,耐心看,重点一是看懂dp[i]表示的是什么,那就是从j到i的合法子串长度,0 <= j <= i,重点二是j可由等式dp[i] = i - j + 1变形求得,重点三是对贪心法的理解,只取局部最优,按这个思路考虑,每次迭代尾字符要么是(要么是),则:

  1. ...(一定不合法
  2. ...)要分情况讨论:

阅读全文