【问题标题】:React context with hooks prevent re render用钩子反应上下文防止重新渲染
【发布时间】:2019-11-23 13:58:20
【问题描述】:

我使用带有钩子的 React 上下文作为我的 React 应用程序的状态管理器。每次商店中的值发生变化时,所有组件都会重新渲染。

有没有办法防止 React 组件重新渲染?

存储配置:

import React, { useReducer } from "react";
import rootReducer from "./reducers/rootReducer";

export const ApiContext = React.createContext();

export const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, {});

  return (
    <ApiContext.Provider value={{ ...state, dispatch }}>
      {children}
    </ApiContext.Provider>
  );
};

reducer 示例:

import * as types from "./../actionTypes";

const initialState = {
  fetchedBooks: null
};

const bookReducer = (state = initialState, action) => {
  switch (action.type) {
    case types.GET_BOOKS:
      return { ...state, fetchedBooks: action.payload };

    default:
      return state;
  }
};

export default bookReducer;

根减速器,可以组合尽可能多的减速器:

import userReducer from "./userReducer";
import bookReducer from "./bookReducer";

const rootReducer = ({ users, books }, action) => ({
  users: userReducer(users, action),
  books: bookReducer(books, action)
});

动作示例:

import * as types from "../actionTypes";

export const getBooks = async dispatch => {
  const response = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    method: "GET"
  });

  const payload = await response.json();

  dispatch({
    type: types.GET_BOOKS,
    payload
  });
};
export default rootReducer;

这里是书籍组件:

import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getBooks } from "../../store/actions/bookActions";

const Books = () => {
  const { dispatch, books } = useContext(ApiContext);
  const contextValue = useContext(ApiContext);

  useEffect(() => {
    setTimeout(() => {
      getBooks(dispatch);
    }, 1000);
  }, [dispatch]);

  console.log(contextValue);

  return (
    <ApiContext.Consumer>
      {value =>
        value.books ? (
          <div>
            {value.books &&
              value.books.fetchedBooks &&
              value.books.fetchedBooks.title}
          </div>
        ) : (
          <div>Loading...</div>
        )
      }
    </ApiContext.Consumer>
  );
};

export default Books;

当 Books 组件中的值发生变化时,另一个我的组件 Users 会重新渲染:

import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getUsers } from "../../store/actions/userActions";

const Users = () => {
  const { dispatch, users } = useContext(ApiContext);
  const contextValue = useContext(ApiContext);

  useEffect(() => {
    getUsers(true, dispatch);
  }, [dispatch]);

  console.log(contextValue, "Value from store");

  return <div>Users</div>;
};

export default Users;

优化上下文重新渲染的最佳方法是什么?提前致谢!

【问题讨论】:

  • 你有演示这个的 CodeSandbox 吗?
  • 看来你用钩子+上下文创建了自己的redux :)
  • 是什么让你说其他组件正在重新渲染?你怎么知道?看起来发生的事情很正常 - 每次您更改路线时,导航链接都会重新渲染..您指的是什么?

标签: javascript reactjs react-native react-context


【解决方案1】:

BooksUsers 目前在每个循环中重新渲染 - 不仅在存储值更改的情况下。

1。道具和状态变化

React re-renders the whole sub component tree 从组件作为 root 开始,其中 props 或 state 发生了变化。您通过getUsers 更改父状态,因此BooksUsers 重新渲染。

const App = () => {
  const [state, dispatch] = React.useReducer(
    state => ({
      count: state.count + 1
    }),
    { count: 0 }
  );

  return (
    <div>
      <Child />
      <button onClick={dispatch}>Increment</button>
      <p>
        Click the button! Child will be re-rendered on every state change, while
        not receiving any props (see console.log).
      </p>
    </div>
  );
}

const Child = () => {
  console.log("render Child");
  return "Hello Child ";
};


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

优化技术

使用React.memo 防止重新渲染合成,如果它自己的道具实际上没有改变。

// prevents Child re-render, when the button in above snippet is clicked
const Child = React.memo(() => {
  return "Hello Child ";
});
// equivalent to `PureComponent` or custom `shouldComponentUpdate` of class comps

重要提示: React.memo 仅检查道具更改(useContext 值更改触发重新渲染)!


2。上下文变化

当上下文值更改时,

所有上下文消费者 (useContext) 会自动重新呈现。

// here object reference is always a new object literal = re-render every cycle
<ApiContext.Provider value={{ ...state, dispatch }}>
  {children}
</ApiContext.Provider>

优化技术

确保上下文值具有稳定的对象引用,例如useMemoHook.

const [state, dispatch] = useReducer(rootReducer, {});
const store = React.useMemo(() => ({ state, dispatch }), [state])

<ApiContext.Provider value={store}>
  {children}
</ApiContext.Provider>

其他

不确定,为什么你把这些所有结构放在Books中,只用一个useContext

const { dispatch, books } = useContext(ApiContext);
// drop these
const contextValue = useContext(ApiContext); 
<ApiContext.Consumer> /* ... */ </ApiContext.Consumer>; 

您也可以同时使用React.memouseContext 查看this code example

【讨论】:

    【解决方案2】:

    我相信这里发生的事情是预期的行为。它呈现两次的原因是因为当您分别访问图书或用户页面时,您会自动抓取新图书/用户。

    这是因为页面加载,然后useEffect 启动并抓取一本书或用户,然后页面需要重新渲染才能将新抓取的图书或用户放入 DOM。

    我已经修改了您的 CodePen 以表明是这种情况。如果您在图书或用户页面上禁用“自动加载”(我为此添加了一个按钮),然后浏览该页面,然后浏览回该页面,您将看到它只呈现一次。

    我还添加了一个按钮,可让您按需获取新书或用户...这是为了显示如何仅重新呈现您所在的页面。

    总而言之,据我所知,这是预期的行为。

    【讨论】:

      【解决方案3】:

      我试图用不同的例子来解释,希望能有所帮助。

      因为上下文使用引用标识来确定何时重新渲染,当提供者的父级重新渲染时,这可能会触发消费者的无意渲染。

      例如:下面的代码将在每次 Provider 重新渲染时重新渲染所有消费者,因为总是为 value 创建一个新对象

      class App extends React.Component {
        render() {
         return (
            <Provider value={{something: 'something'}}>
              <Toolbar />
            </Provider>
          );
       }
      }
      

      要解决这个问题,请将值提升到父状态

      class App extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            value: {something: 'something'},
          };
        }
      
        render() {
          return (
            <Provider value={this.state.value}>
              <Toolbar />
            </Provider>
          );
        }
      }
      

      【讨论】:

      • 所以,例如,我需要创建一个子组件来显示书籍。我从 Books 组件的后端获取数据并将其传递给 BooksItem,它会阻止重新渲染,对吧?
      • 但是由于父组件的状态发生了变化,子组件不会重新渲染吗?
      【解决方案4】:

      这个用于防止组件在 React 中渲染的解决方案称为 shouldComponentUpdate。它是一种生命周期方法,可用于 React 类组件。而不是像以前那样将 Square 作为功能性无状态组件:

      const Square = ({ number }) => <Item>{number * number}</Item>;
      

      您可以使用带有 componentShouldUpdate 方法的类组件:

      class Square extends Component {
        shouldComponentUpdate(nextProps, nextState) {
          ...
        }
      
        render() {
          return <Item>{this.props.number * this.props.number}</Item>;
        }
      }
      

      如您所见, shouldComponentUpdate 类方法在运行组件的重新渲染之前可以访问下一个道具和状态。在这里,您可以决定通过从此方法返回 false 来阻止重新渲染。如果返回 true,则组件重新渲染。

      class Square extends Component {
        shouldComponentUpdate(nextProps, nextState) {
          if (this.props.number === nextProps.number) {
            return false;
          } else {
            return true;
          }
        }
      
        render() {
          return <Item>{this.props.number * this.props.number}</Item>;
        }
      }
      

      在这种情况下,如果传入的 number 属性没有改变,则组件不应该更新。通过将控制台日志再次添加到您的组件来自己尝试。 Square 组件不应该在视角改变时重新渲染。这对于您的 React 应用程序来说是一个巨大的性能提升,因为您的所有子组件不会随着父组件的每次重新渲染而重新渲染。最后,由你来阻止组件的重新渲染。

      理解这个 componentShouldUpdate 方法一定会对你有所帮助!

      【讨论】:

      • 谢谢!但是我用钩子,所以我会试试备忘录
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-08
      • 1970-01-01
      • 2022-01-23
      • 2019-07-27
      • 1970-01-01
      • 2020-12-17
      • 1970-01-01
      相关资源
      最近更新 更多