不要复制你的数据——从代码审查中学习
获取重构的代码和上述组件的解释
正确处理数据可能很困难。我们必须从 API 获取数据,必须将其与其他来源的数据聚合,并且必须高效地转换数据以便在 UI 中使用。
在过去的几个月里,我在这个新课程中为初级开发人员进行了许多代码审查。令我惊讶的是,同一个错误反复出现。这个错误可能会导致难以调试的严重错误。
这篇文章是关于数据重复及其解决方法:单一事实来源。
在我解释这意味着什么之前,让我们先看一个代码示例。
重复数据
以下组件呈现从其父级接收的博客文章列表。
用户可以选择过滤器,仅显示特定日期创建的帖子。组件会相应地过滤并渲染提供的帖子。
const PostList = ({ posts }) => {
const [selectedDay, setSelectedDay] = useState(null);
const [filteredPosts, setFilteredPosts] = useState(posts);
const onChangeDay = (day) => {
setSelectedDay(day);
const postsForDay = posts.filter(
(post) => isSameDay(post.createdAt, day)
);
setFilteredPosts(postsForDay);
};
return (
<Wrapper>
<Filter
selectedDay={selectedDay}
onChangeDay={onChangeDay}
/>
{
filteredPosts.map((post) => (
<Post key={post.id} {...post} />
))
}
</Wrapper>
);
};
为了实现过滤,选定的日期存储在状态变量中。在选定的日期旁边,我们找到另一个状态变量来保存已过滤的帖子。
该filteredPosts
数组如下所示。每当onChangeDay
回调函数中所选日期发生变化时,该数组都会更新。
你可能已经意识到这种方法的问题了:filteredPosts
state 只是posts
prop 的一个子集。我们复制了数组的一部分posts
,因此将数据存储在两个不同的地方。
好的,确实如此。
但这里的问题是什么?
我们必须使副本与原件保持同步。
想象一下以下情况:父组件允许用户编辑帖子。用户决定将帖子的标题从“数据重复太棒了!”更改为“数据重复糟透了!”。
现在会发生什么?
- 父组件使用更新后的数组重新渲染
posts
。 - 该
PostList
组件使用更新后的 prop 重新渲染posts
。
到目前为止一切顺利。但请记住组件的样子:
const PostList = ({ posts }) => {
const [selectedDay, setSelectedDay] = useState(null);
const [filteredPosts, setFilteredPosts] = useState(posts);
const onChangeDay = (day) => { ... };
return (
<Wrapper>
<Filter ... />
{
filteredPosts.map((post) => (
<Post key={post.id} {...post} />
))
}
</Wrapper>
);
};
实际上显示的是数组PostList
中的数据filteredPosts
。这是旧版prop的子集posts
。
这意味着用户界面仍会显示旧帖子及其过时的标题“数据复制很棒!”
问题是我们只更新了帖子的一个版本。我们的filteredPosts
数组不同步。
单一事实来源
我们的组件的更好版本是什么样的?
我们不会将数据复制到另一个状态变量中。我们会尝试只使用一个来源:posts
prop。单一事实来源。
function PostList({ posts }) {
const [selectedDay, setSelectedDay] = useState(null);
const filteredPosts = posts.filter(
(post) => isSameDay(post.createdAt, selectedDay)
);
return (
<Wrapper>
<Filter
selectedDay={selectedDay}
onChangeDay={setSelectedDay}
/>
{
filteredPosts.map((post) => (
<Post key={post.id} {...post} />
))
}
</Wrapper>
);
}
看看我们如何摆脱状态filteredPosts
并用正常变量代替它?
此版本更简单,不太可能引入错误。
如果你担心性能问题,那你可能是对的。如果帖子数组很长或者筛选很复杂,应用可能会很慢。
但在这种情况下,我们可以简单地使用useMemo钩子。
const filteredPosts = useMemo(() => posts.filter(
(post) => isSameDay(post.createdAt, selectedDay)
), [posts, selectedDay]);
该useMemo
钩子返回一个记忆值。提供的函数仅在依赖项发生变化时运行。
这意味着上面示例中的过滤操作仅在posts
数组发生变化时才会执行。如果组件重新渲染但posts
数组保持不变,useMemo
则只需返回已记忆的值,无需再次执行昂贵的过滤逻辑。
运动时间
这是另一个可以通过简化而受益的例子。
function Books() {
const [data, setData] = useState(null);
const [books, setBooks] = useState([]);
useEffect(() => {
fetchData().then((data) => setData(data));
}, []);
useEffect(() => {
if (!data) {
return;
}
const mappedBooks = mapBooks(data);
setBooks(mappedBooks);
}, [data]);
return (
<div>
{
books.map((post) => (
<div key={post.id}>{post.title}</div>
))
}
</div>
);
}
我把它留给你作为练习,让你找到问题并重构这个组件以使用单一事实来源。
获取重构的代码和上述组件的解释
点击上面的链接并留下你的邮箱,即可获取我的练习答案。我还会带你浏览原始代码,并详细解释它的作用。
鏂囩珷鏉ユ簮锛�https://dev.to/jkettmann/don-t-duplicate-your-data-learnings-from-code-reviews-caj