我创建了一个非常简单的 React-Redux 应用程序并从 https://jsonplaceholder.typicode.com/ 获取用户和帖子
在我的组件中,我将用户和帖子数据记录到控制台中。据我所知,在网络选项卡中,有一个针对用户的请求和 10 个针对帖子的请求。这是正确的,但在控制台中,我看到每个 用户 有 10 个 帖子 请求。
这是否意味着 ReactJS 渲染组件 100 次?我在这段代码中的错误是什么?
任何帮助将不胜感激!
我的代码和codepen链接如下
Please check the code in codepen
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapStateToProps = state => {
return { users: state.users, posts: state.posts };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, [getUsers]);
const renderUsers = () =>
props.users.map(user => {
return (
<div>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts' , props.posts);
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [getPosts, userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const PostsContainer = connect(mapStateToProps, mapDispatchToProps)(Posts);
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
答案 0 :(得分:4)
这是否意味着 ReactJS 渲染组件 100 次?我在这段代码中的错误是什么?
该组件不会渲染 100 次,每个 PostContainer 渲染初始装载然后重新渲染 10 次。因为有 10 个 PostContainers 并且每个重新渲染 10 次,这就是为什么您可能认为渲染 100 次。
你有一些问题。被指出的依赖性问题是第一个。 getUsers
useEffect 应该有一个空的依赖项,而 userId
useEffect 应该依赖于 userId
。
要解决由于帖子导致的 UserContainer 上的 10 次重新渲染,您需要为每个重新渲染提供不同的 mapStateToProps。对于 UserContainer,您将仅映射用户,否则由于 10 个帖子请求,您将获得 10 个更新:
const mapUserStateToProps = state => {
return { users: state.users };
};
它解决了 UserContainer 10 次重新渲染。
现在关于 PostContainer 有一些需要首先修复的东西,你的减速器。您的减速器用当前调用替换了最后一个帖子。最后,您将只看到最后到达的帖子,而不是所有帖子。解决您需要传播状态的问题。
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
最终,如果在您的项目中,您可能对同一个 userId 重复请求,则需要进行一些验证以防止再次添加相同的帖子
现在它引导我们将 props 映射到 PostContainer。您需要根据 userId 对帖子进行过滤。 mapStateToProps 将 props 作为第二个参数,这使我们能够做到这一点:
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
这看起来解决了问题,但每个 PostContainer 仍然重新渲染 10 次。为什么会发生这种情况,因为帖子是相同的?发生这种情况是因为 filter 将返回一个新的数组引用,无论其内容是否更改。
要解决此问题,您可以使用 React.memo。您需要为备忘录提供组件和相等函数。要比较一组对象,有一些解决方案,也有一些库提供了一些 deepEqual
函数。这里我使用 JSON.stringify 进行比较,但您可以随意使用其他的:
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
你也会验证其他可能改变的道具,但事实并非如此
现在将 React.memo 应用于帖子:
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
在所有这些应用之后,UserContainer 将重新渲染一次,并且每个 PostContainer 也将仅重新渲染一次。
这里是工作解决方案的链接: https://codepen.io/rbuzatto/pen/BaLYmNK?editors=0010
最终代码:
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapUserStateToProps = state => {
return { users: state.users };
};
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, []);
const renderUsers = () =>
props.users.map(user => {
return (
<div key={user.id}>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapUserStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts');
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
答案 1 :(得分:0)
useEffect()
每次在您提供的依赖项中发生更改时都会呈现组件。
理想情况下,您应该将组件更改为仅在 props 发生变化时重新渲染。每次渲染时 getUser 和 getPost 都会发生变化。因此,最好将其更改为从状态监视用户和帖子。
在用户中:
const { users, getUsers } = props;
useEffect(() => {
getUsers();
}, []); -- Leaving this empty makes it load only on mount.
在帖子中:
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);