Skip to main content

状态管理-复合组件

v1版本

jsx
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
import {createContext, useContext, useEffect, useState} from 'react'
import { Button } from '@mui/material';
const VoteContext = createContext();
function VoteV6({children,defaultvalue={}}){
const [votes,setVotes] = useState(defaultvalue);
const contextValue = {
votes,
registerVote(option, text){
setVotes(prev=>({...prev,[option]:{vote:0,text}}));
},
addVote:(option,text)=>{
setVotes(prev=>({...prev,[option]:{vote:(prev[option]?.vote||0)+1,text}}));
console.log(votes)
}
};
return (
<div className='vote-section'>
<VoteContext.Provider value={contextValue}>
<div className='vote-title'>v6:复合组件通讯</div>
{children}
</VoteContext.Provider>
</div>
);
}
VoteV6.Result = function VoteResult(){
const {votes} = useContext(VoteContext);
const total = Object.values(votes).reduce((prev,current)=>prev+current.vote,0);
return (
<div className='vote-body'>
<div className='row'><h3>投票结果</h3></div>
<div className='row'>
<ul>
{Object.entries(votes).map(([key,vote])=><li key={key}>{removeEmojis(vote.text)}:{vote.vote}</li>)}
<li>总计:{total}</li>
</ul>
</div>
</div>
);
}
VoteV6.Submit = function VoteSubmit({name,text}){
const {addVote,registerVote} = useContext(VoteContext);
// 关键:组件挂载时自动注册
useEffect(() => {
registerVote(name,text);
}, []);
return (
<Button variant="contained" size='small' onClick={()=>addVote(name,text)}>{text}</Button>
);
}
function removeEmojis(str) {
// 这个正则匹配大部分 emoji
return str.replace(/[\u{1F300}-\u{1F9FF}]/gu, '');
}
export {VoteV6}

在开发功能过程中,只要一保存代码,就会出现useContext(...) is undefined错误.

v2版本

jsx
1

// Router caught the following error during render TypeError: useContext(...) is undefined

所以请确保 VoteV6.ResultVoteV6.Submit 都是 VoteV6 的直接子元素。

关于effect

方案1只注册一次(推荐)

jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
VoteOption = function VoteOption({ value, children }) {
const { registerOption } = useContext(VoteContext);
const hasRegistered = useRef(false);
useEffect(() => {
// ✅ 只注册一次,即使依赖变化也不重新注册
if (!hasRegistered.current) {
registerOption(value, children);
hasRegistered.current = true;
}
}, []); // 空依赖,只执行一次
// ✅ 如果 value 或 children 变化,可以更新注册
useEffect(() => {
if (hasRegistered.current) {
registerOption(value, children);
}
}, [value, children, registerOption]); // 只更新,不重新注册
return <div>{children}</div>;
};

方案2,稳定所有依赖(最佳)

jsx
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
// 1. 父组件稳定 registerOption
function VoteProvider() {
const [options, setOptions] = useState({});
// ✅ 使用 useCallback 稳定函数
const registerOption = useCallback((value, children) => {
setOptions(prev => {
if (prev[value] === children) return prev; // 避免不必要的更新
return { ...prev, [value]: children };
});
}, []); // 空依赖
// ✅ 使用 useMemo 稳定 context
const contextValue = useMemo(() => ({
registerOption,
// ... 其他值
}), [registerOption]);
}
// 2. 子组件稳定 children
VoteOption = React.memo(function VoteOption({ value, children }) {
const { registerOption } = useContext(VoteContext);
// ✅ 使用稳定的 children
const stableChildren = useMemo(() => children, [children]);
useEffect(() => {
registerOption(value, stableChildren);
}, [value, stableChildren, registerOption]);
return <div>{children}</div>;
});