React-Redux 功能组件多重渲染

时间:2020-12-28 11:54:35

标签: reactjs redux react-hooks

我创建了一个非常简单的 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')
);

2 个答案:

答案 0 :(得分:4)

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

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

该组件不会渲染 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]);