React Redux功能组件多个渲染器



我创建了一个非常简单的React Redux应用程序,并从https://jsonplaceholder.typicode.com/

在我的组件中,我将用户发布数据记录到控制台中。据我所见,在"网络"选项卡中,有一个请求用于用户,有10个请求用于帖子。这是正确的,但在控制台中,我看到每个用户都有10个帖子请求。

这是否意味着ReactJS渲染组件100次?我在这个代码中犯了什么错误?

任何帮助都将不胜感激!

我的代码和代码笔链接在下方

请检查代码笔中的代码

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')
);

这是否意味着ReactJS渲染组件100次?我在这个代码中犯了什么错误

  • 您有一个UserContainer,用于呈现和请求用户
  • 一旦获取了用户,就有了更新状态。UserContainer重新发布,现在您有10个PostContainer
  • 每个PostContainer请求获取帖子,总共10个
  • 它导致10个状态更新。UserContainer重传10次,每个PostContainer重发10次

组件不会渲染100次,每个PostContainer渲染初始装载,然后重新渲染10次。因为有10个PostContainer,每个PostContainer会重新渲染10次,所以你可能会认为它会渲染100次。

你有一些问题。依赖性问题是第一个被指出的问题。getUsersuseEffect应该有一个空的依赖项,而userIduseEffect则应该依赖于userId

要解决UserContainer上由于帖子而导致的10个重发,您需要对每个重发都有不同的mapStateToProps。对于UserContainer,您将只映射用户,否则您将因10个帖子请求而获得10个更新:

const mapUserStateToProps = state => {
return { users: state.users };
};

从而解决了UserContainer 10的重发问题。

现在关于PostContainer,有一些东西需要首先修复,你的reducer。你的reducer用当前调用替换上一个帖子。最终,您将只收到最后到达的帖子,而不是所有帖子。要解决这个问题,你需要分散你的状态。

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次。既然帖子都是一样的,为什么会发生这种情况?发生这种情况是因为过滤器将返回一个新的数组引用,无论其内容是否没有更改。

要解决这个问题,您可以使用React.memo。您需要为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')
);

useEffect()在您提供的依赖项中每次发生更改时都会渲染组件。

理想情况下,您应该更改组件,以便只有在道具发生变化时才能重新渲染。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]);

最新更新