Hooks-useRef

作用:

  1. 访问 DOM 节点/react 元素
    • useRef 替换 class 组件中的 createRef
    • 使用 forwardRef 传递 ref 引用
    • 使用 useImperativeHandle 自定义暴露实例值
  2. 保持变量引用

保持变量的引用

在下面这个例子中,如果我们只是想设置一个定时器,可以使用局部变量id,但是如果我们想要在其他地方清除这个定时器,useRef就够帮助我们保存定时器的引用。

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
import React, { useState, useEffect, useRef } from 'react';
import './index.css';

const cardDataList = [
{
title: '杭州市通用5元券',
subTitle:
'杭味面馆非常好吃,太好吃了,相当不错,味道鲜美,特别划算,快快抢购,聚划算',
},
{
title: '杭州市10元券',
subTitle: '兰州拉面非常好吃',
},
];

const delay = (data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data);
}, 1000);
});
};

/** 这里是react实现方式**/
const CardReact = (props) => {
const { data } = props;
let [btnText, setBtnText] = useState(10);
let timer = useRef(null);

// 抢购
const handleBuy = () => {
delay('已抢购').then((res) => {
setBtnText(res);
});
};

// 开始倒计时
useEffect(() => {
timer.current = setInterval(() => {
setBtnText(--btnText);
}, 1000);
return () => {
timer.current && clearInterval(timer.current);
};
}, []);

// 满足条件后清除定时器
useEffect(() => {
if (btnText < 1) {
// If we just wanted to set an interval, we wouldn’t need the ref (id could be local to the effect),
// but it’s useful if we want to clear the interval from an event handler:
clearInterval(timer.current);
setBtnText('抢购');
}
}, [btnText]);

return (
<div className="card">
<div className="info">
<div className="title">{data.title}</div>
<div className="subTitle">{data.subTitle}</div>
</div>
<button className="btn" onClick={handleBuy}>
{btnText}
</button>
</div>
);
};

const CardList = (props) => {
return (
<>
{props.list.map((data) => (
<CardReact data={data} />
))}
</>
);
};

export default function a() {
return <CardList list={cardDataList} />;
}

ref 回调函数实参不更新问题

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
const CustomizeContext = createContext(defaultV);

export { CustomizeContext };

function Customize() {
const [sceneList, setSceneList] = useState<ILiveScene[]>([]);
const [activeLayerIndex, setActiveLayerIndex] = useState(-1);

const updateSceneList = (sceneList: ILiveScene[]) => {
setSceneList(cloneDeep(sceneList));
};

const updateActiveLayerIndex = (layerId: string | -1) => {
if (layerId === -1) {
setActiveLayerIndex(-1);
} else {
const layerIndex = sceneList[activeSceneIndex].layout.layers.findIndex(
(x) => x.id === layerId,
);

setActiveLayerIndex(layerIndex);
}
};

return (
<Container>
<CustomizeContext.Provider
value={{
liveId,
sceneList,
activeSceneIndex,
activeLayerIndex,
updateSceneList,
updateActiveSceneIndex,
updateActiveLayerIndex,
}}
>
<Scene />
</CustomizeContext.Provider>
</Container>
);
}
export default Customize;
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
import {
useRef,
useState,
useEffect,
useContext,
useLayoutEffect,
} from 'react';
import { CustomizeContext } from '../../../index';

const Scene = ({ zIndex }: SceneProp) => {
const contextData = useContext(CustomizeContext);
const {
sceneList,
activeSceneIndex,
updateSceneList,
updateActiveLayerIndex,
activeLayerIndex,
} = contextData;

/**
* 保存drop节点,监听拖拽事件
* @param node drop节点
* @param layer 该节点对应的数据
*/
const handleDropRefCallback = (
node: any,
layer: ILiveScene['layout']['layers'][number],
) => {
const map = getMap();
if (node) {
map.set(layer.id, node);

// 按下非 active drop
node.addEventListener('mousedown', (e) => {
if (!activeDropStateRef.current.isDragging) {
activeDropStateRef.current.isResize = false;
activeDropStateRef.current.isDragging = true;
activeDropStateRef.current.startX = e.clientX;
activeDropStateRef.current.startY = e.clientY;
activeDropStateRef.current.node = node;
activeDropStateRef.current.id = layer.id;
activeDropStateRef.current.rect = layer.rect;

// node.style.zIndex = (zIndex + 1).toString();
updateActiveLayerIndex(layer.id);
}
});
} else {
map.delete(layer.id);
}
};

return (
<div className={styles.sceneContentWrap} style={sceneStyle}>
{sceneList[activeSceneIndex].layout.layers.map((layer, i) => {
let cp;
const { text, type, digitalHuman, image, video, z } = layer;

return (
<div
className={cx(styles.drop, {
[styles.active]: layer.id === activeDropStateRef.current.id,
})}
// key={layer.id}
key={i}
ref={(node) => handleDropRefCallback(node, layer, i)}
style={{
left: rect.x,
top: rect.y,
width: rect.width,
height: rect.height,
backgroundColor: type === 'text' ? text.backgroundColor : '',
zIndex: z,
}}
>
<img style={s} src={image.url} />;
</div>
);
})}
</div>
);
};
export default Scene;

有一个列表,为了保存列表项节点,我使用 ref 回调来处理。

有这么个场景,点击 layers 中的第一项并做删除操作,layers.splice(0, 1),然后更新 layers 引用

点击第二项时,我期望handleDropRefCallback 中的i为 0,因为 layer 已经更新了,但是结果为 1,也就是还是首次 i 的值

纳闷了好久,后面才发现是key属性key={layer.id}保持了该节点的稳定,该节点没有重新渲染,所以 ref 的回调函数还是旧的回调函数,i 的值还是第一项还未删除时的值。
解决这个问题,只需要将 key 改为key={i},此时列表项重新渲染,handleDropRefCallback 中的i就是期望值 0。

但是我们清楚,用下标作为 key 的值,如果列表发生更改(新增、删除)时,那么其他列表项的 key 值可能会发生变化,那么变化的部分就会造成没必要的重新渲染问题。所以我还是想用layer.id作为 key 值,通过 id,遍历 layers,查找当前项所在的下标位置。
结果却出乎意外,发现 Provied.value 对象中updateLayersFn函数中的 layers 还是旧值,难道 Provider 组件 value 对象中的函数引用了 layers,此时产生了闭包对象?

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×