状态管理-复合组件
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) {// 这个正则匹配大部分 emojireturn 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.Result和VoteV6.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. 父组件稳定 registerOptionfunction VoteProvider() {const [options, setOptions] = useState({});// ✅ 使用 useCallback 稳定函数const registerOption = useCallback((value, children) => {setOptions(prev => {if (prev[value] === children) return prev; // 避免不必要的更新return { ...prev, [value]: children };});}, []); // 空依赖// ✅ 使用 useMemo 稳定 contextconst contextValue = useMemo(() => ({registerOption,// ... 其他值}), [registerOption]);}// 2. 子组件稳定 childrenVoteOption = React.memo(function VoteOption({ value, children }) {const { registerOption } = useContext(VoteContext);// ✅ 使用稳定的 childrenconst stableChildren = useMemo(() => children, [children]);useEffect(() => {registerOption(value, stableChildren);}, [value, stableChildren, registerOption]);return <div>{children}</div>;});