【发布时间】:2021-05-19 04:46:06
【问题描述】:
我有一个带有需要特定操作的状态变量的 React 组件。例如,考虑一个显示用户配置文件列表的组件,用户可以切换到另一个配置文件或创建一个新配置文件。状态变量是用户配置文件列表,第二个变量是当前选择的配置文件;该组件可以添加一个新的配置文件(这比仅仅“设置”一个新的配置文件列表更具体),或者它可以更改当前选择的配置文件。
我的第一个想法是有两个useState 挂钩,一个用于列表,一个用于当前配置文件。但是,其中一个问题是我想存储当前配置文件的 id,它引用列表中的配置文件之一,这意味着两个状态变量是相互依赖的。另一个问题是,拥有一个通用的setProfiles 状态更改函数对我来说有点过于“开放”:添加逻辑可能非常具体,我想封装它。
所以我想出了这个解决方案:一个管理两个状态变量及其设置器的自定义挂钩,它将公开两个值(列表和当前 id)及其适当的操作(添加新配置文件和选择配置文件)。
这是钩子的代码:
export const useProfileData = () => {
const [ profiles, setProfiles ] = useState([]);
const [ currentProfileID, setCurrentProfileID ] = useState(null);
const [ currentProfile, setCurrentProfile ] = useState(null);
useEffect(() => {
// This is actually a lazy deferred data fetch, but I'm simplifying for the sake of brevity
setProfiles(DataManager.getProfiles() || [])
}, [])
useEffect(() => {
if (!profiles) {
setCurrentProfile(null);
return;
}
const cp = profiles.find(p => p.ID === currentProfileID);
setCurrentProfile(cp);
}, [ currentProfileID, profiles ])
return {
currentProfile: currentProfile,
profiles: profiles,
setCurrentProfileID: i_id => setCurrentProfileID(i_id),
addNewProfile: i_profile => {
profiles.push(i_profile);
setProfiles(profiles);
DataManager.addNewProfile(i_profile); // this could be fire-and-forget
},
};
};
使用三种状态:列表、当前配置文件 id 和当前配置文件(作为对象)。该列表是在挂载时检索的(当前 id 也应该是,但为简洁起见,我省略了它)。当前配置文件从不直接从外部设置:更改它的唯一方法是更改由第二个useEffect 管理的 id 或列表。而更改 id 的唯一方法是通过暴露的 setCurrentProfileID 函数。
添加新配置文件由公开的addNewProfile 函数管理,该函数应将新配置文件添加到状态列表中,更新状态列表,并将新配置文件添加到持久数据管理器中。
我的第一个问题是:设计这样的钩子可以吗? 从一般软件设计的角度来看,这段代码提供了封装、关注点分离和正确的状态管理。我不确定这在像 React 这样的函数式世界中是否合适。
我的第二个问题是:为什么我的组件(使用useProfileData)在调用addNewProfile 时没有更新? 例如:
const ProfileSelector = (props) => {
const [ newProfileName, setNewProfileName ] = useState('');
const { profiles, currentProfile, setCurrentProfileID, addNewProfile } = useProfileData();
function createNewProfile() {
addNewProfile({
name: newProfileName,
});
}
return (
<div>
<ProfilesList profiles={profiles} onProfileClick={pid => setCurrentProfileID(pid)} />
<div>
<input type="text" value={newProfileName} onChange={e => setNewProfileName(e.target.value)} />
<Button label="New profile" onPress={() => createNewProfile()} />
</div>
</div>
);
};
ProfilesList 和 Button 是在别处定义的组件。
当我点击按钮时,一个新的配置文件被添加到持久数据管理器中,但profiles 没有更新,ProfilesList 也没有更新(当然)。
我要么实现了一些错误,要么这不是一个可以在 React 中工作的范例。我能做什么?
编辑
正如@thedude 所建议的,我尝试使用减速器。这是我的减速器的(存根):
const ProfilesReducer = (state, action) => {
const newState = state;
switch (action.type) {
case 'addNewProfile':
{
const newProfile = action.newProfile;
newState.profiles.push(newProfile);
DataManager.addNewProfile(newProfile);
}
break;
default:
throw new Error('Unexpected action type: ' + action.type);
}
return newState;
}
在我调用它 (profilesDispatch({ type: 'addNewProfile', newProfile: { name: 'Test' } });) 之后,没有检测到 profilesState.profiles 的变化 - 或者至少,渲染从未被触发,也没有任何效果。但是,对 DataManager 的调用已完成,新的配置文件已被保留。
【问题讨论】:
-
你考虑过
useReducer吗? -
@thedude
useReducer会给我一个状态变量;我需要两个。虽然我可以使用带有我的两个变量的单个对象作为其属性,但当减速器状态的属性发生变化而不是整个减速器的状态时,是否会通知状态更新? -
useReducer的重点是为所有状态突变提供一个中心位置,这在处理相互依赖的数据时很方便。您的状态可以有多个值,并且由于所有状态更改都通过 reducer,您可以更轻松地管理它 -
@thedude 那我试试reducer,谢谢!
-
@thedude 我尝试了减速器,但仍然没有成功。我在上面贴了reducer:我做错了吗?
标签: reactjs