useEffect来渲染这个组件



我试图使用useEffect来渲染postList(使其在没有删除的帖子的情况下渲染)当postsCount改变时,但我无法得到正确的结果。我试着把所有的东西都包装在useEffect里面,但我不能执行addEventListener("click", handlePost),因为我使用useEffect来等待这个组件先挂载,然后再附加evenListener。

父组件:

function Tabs() {
const [posts, setPosts] = useState([]);
const dispatch = useDispatch();
const postsCount = useSelector((state) => state.posts.count);
useEffect(() => {
document.getElementById("postsTab").addEventListener("click", handlePost);
}, [handlePost]);
const handlePost = async (e) => {
const { data: { getPosts: postData }} = await refetchPosts();
setPosts(postData);
dispatch(postActions.getPostsReducer(postData));
};
const { data: FetchedPostsData, refetch: refetchPosts } = useQuery( FETCH_POSTS_QUERY, { manual: true });
const [postList, setPostsList] = useState({});
useEffect(() => {
setPostsList(
<Tab.Pane>
<Grid>
<Grid.Column>Title</Grid.Column>
{posts.map((post) => (
<AdminPostsList key={post.id} postId={post.id} />
))}
</Grid>
</Tab.Pane>
);
console.log("changed"); //it prints "changed" everytime postCount changes (or everytime I click delete), but the component doesn't remount
}, [postsCount]);
const panes = [
{ menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
];
return (<Tab panes={panes} />);
}

子/AdminPostsList组件:

function AdminPostsList(props) {
const { postId } = props;
const [deletePost] = useMutation(DELETE_POST_MUTATION, {variables: { postId } });
const dispatch = useDispatch();
const deletePostHandler = async () => {
dispatch(postActions.deletePost(postId));
await deletePost();
};
return (
<>
<Button icon="delete" onClick={deletePostHandler}></Button>
</>
);
}

还原剂

const PostSlice = createSlice({
name: "storePosts",
initialState: {
content: [],
count: 0,
},
reducers: {
getPostsReducer: (state, action) => {
state.content = action.payload;
state.count = action.payload.length
},
deletePost: (state, action) => {
const id = action.payload
state.content = current(state).content.filter((post) => (post.id !== id))
state.count--
}
},
});

好的,让我们单独讨论这个问题。关键是将posts逻辑与包装器组件(Tabs)解耦。您应该创建专门用于帖子的组件,并将其呈现在包装器中。像这样,您可以很容易地在与帖子相关的组件中隔离所有与帖子相关的逻辑,例如避免从包装器中附加一些侦听器(因为您正在做什么以及谁侦听什么并不直观,因为按钮不在同一个组件中)。在分离的组件中,您将只有一个useEffect,用于获取帖子,您将有一个选择器(用于从redux中选择帖子),然后仅使用该选择从组件输出内容。那部分<Tab panes={...} />是你的大多数问题的根源,因为像这样,你被迫解决<Tab../>以上的一切,然后只是通过它,这不是你的情况下的最佳实践,因为它可能太复杂(特别是在你可以有多个标签的情况下)。这就是为什么你需要解耦并创建特定于选项卡的组件。

这是你应该如何重构它的一个想法:

function PostsTab() {
const posts = useSelector((state) => state.posts?.content ?? []);
useEffect(() => {
// Here dispatch action to load your posts
// With this approach, when you have separated component for PostsTab no need to attach some weird event listeners, you can do everything here in effect
// This should be triggered only once
// You can maybe introduce 'loading' flag in your reducer so you can display some loaders for better UX
}, []);
return (
<div>
{/* Here use Tab components in order to create desired tab */}
<Tab.Pane>
<Grid>
<Grid.Column>Title</Grid.Column>
{posts.map((post) => (
<AdminPostsList key={post.id} postId={post.id} />
))}
</Grid>
</Tab.Pane>
</div>
);
}
function Tabs() {
return (
<div>
<PostsTab/>
{/** HERE you can add more tabs when you need to
* Point is to create separate component per tab so you can isolate and maintain tab state in dedicated component
and to avoid writing all logic here in wrapper component
* As you can see there is no need to attach any weird listener, everything related to posts is moved to PostsTab component
*/}
</div>
);
}

好吧,让我们为未来的读者讨论一下我做错了什么:

  1. 没必要用这个奇怪的意大利面
useEffect(() => {
document.getElementById("postsTab").addEventListener("click", handlePost);
}, [handlePost]);
const panes = [
{ menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
];

,因为我可以使用<Menu.Item onClick={handleClick}>Posts</Menu.Item>直接连接onClick

  1. 我不得不使用useEffect来监控posts的依赖关系,但是如果我映射的数组有任何变化,.map()将自动更新其内容,所以没有必要在这种情况下使用useEffect

  2. 我认为我可以从子组件中使用提升状态到setPosts,并且更改将触发.map()重新映射并弹出已删除的元素,但我找不到这样做的方法,所以我使用redux(存储帖子)和useEffect的组合将posts分派到商店,而不是我在存储的redux元素上映射,idk如果这是最好的方法,但这就是我所能做的。

  3. 最重要的是我没有注意到,当我几乎尝试了一切,我必须更新apollo-cache时添加/删除一个帖子,通过使用proxy.readQuery我是这样做的

const [posts, setPosts] = useState([]);
const handlePosts = async () => {
const { data: { getPosts: postData } } = await refetchPosts();
setPosts(postData);
};
const handlePosts = async () => {
const { data } = await refetchPosts();
setPosts(data.getPosts);
};
// Using useEffect temporarily to make it work.
// Will replace it with an lifting state when refactoring later.
useEffect(() => {
posts && dispatch(postsActions.PostsReducer(posts))
}, [posts]);
const [deletePost] = useMutation(DELETE_POST_MUTATION, {
update(proxy) {
let data = proxy.readQuery({
query: FETCH_POSTS_QUERY,
});
// Reconstructing data, filtering the deleted post 
data = { getPosts: data.getPosts.filter((post) => post.id !== postId) };
// Rewriting apollo-cache
proxy.writeQuery({ query: FETCH_POSTS_QUERY, data });
},
onError(err) {
console.log(err);
},
variables: { postId },
});
const deletePostHandler = async () => {
deletePost();
dispatch(postsActions.deletePost(postId))
};

感谢@Anuj Panwar @Milos Pavlovic的帮助,感谢@Cptkrush让我注意到商店的想法