简体   繁体   English

使用钩子响应上下文可防止重新渲染

[英]React context with hooks prevent re render

I use React context with hooks as a state manager for my React app.我使用带有钩子的 React 上下文作为我的 React 应用程序的状态管理器。 Every time the value changes in the store, all the components re-render.每次 store 中的值发生变化时,所有组件都会重新渲染。

Is there any way to prevent React component to re-render?有没有办法阻止 React 组件重新渲染?

Store config:店铺配置:

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

An example of a 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;

Root reducer, that can combine as many reducers, as possible: Root reducer,可以组合尽可能多的reducer:

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

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

An example of an 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;

And here's the book component:这是书籍组件:

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;

When the value changes in Books component, another my component Users re-renders:当 Books 组件中的值发生变化时,另一个 my 组件 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;

What's the best way to optimize context re-renders?优化上下文重新渲染的最佳方法是什么? Thanks in advance!提前致谢!

Books and Users currently re-render on every cycle - not only in case of store value changes. BooksUsers当前在每个周期重新渲染 - 不仅是在商店价值发生变化的情况下。

1. Prop and state changes 1. prop 和 state 的变化

React re-renders the whole sub component tree starting with the component as root, where a change in props or state has happened. React 重新渲染整个子组件树,以组件为根,其中 props 或 state 发生了变化。 You change parent state by getUsers , so Books and Users re-render.您通过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>

Optimization technique优化技术

Use React.memo to prevent a re-render of a comp, if its own props haven't actually changed.使用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

Important: React.memo only checks prop changes ( useContext value changes trigger re-render)!重要提示: React.memo仅检查道具更改( useContext值更改触发重新渲染)!


2. Context changes 2. 上下文变化

All context consumers ( useContext ) are automatically re-rendered, when the context value changes.当上下文值更改时,所有上下文使用者 ( useContext ) 都会自动重新呈现。

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

Optimization technique优化技术

Make sure to have stable object references for the context value, eg by useMemo Hook.确保上下文值具有稳定的对象引用,例如通过useMemo Hook。

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

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

Other其他

Not sure, why you put all these constructs together in Books , just use one useContext :不确定,为什么您将所有这些结构放在Books ,只需使用一个useContext

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

You also can have a look at this code example using both React.memo and useContext .您还可以使用React.memouseContext查看此代码示例

I believe what is happening here is expected behavior.我相信这里发生的事情是预期的行为。 The reason it renders twice is because you are automatically grabbing a new book/user when you visit the book or user page respectively.它呈现两次的原因是因为当您分别访问书籍或用户页面时,您会自动抓取一本新书/用户。

This happens because the page loads, then useEffect kicks off and grabs a book or user, then the page needs to re-render in order to put the newly grabbed book or user into the DOM.发生这种情况是因为页面加载,然后useEffect启动并抓取一本书或用户,然后页面需要重新渲染以将新抓取的书或用户放入 DOM。

I have modified your CodePen in order to show that this is the case.. If you disable 'autoload' on the book or user page (I added a button for this), then browse off that page, then browse back to that page, you will see it only renders once.我已经修改了您的 CodePen 以表明情况确实如此。你会看到它只渲染一次。

I have also added a button which allows you to grab a new book or user on demand... this is to show how only the page which you are on gets re-rendered.我还添加了一个按钮,允许您根据需要获取新书或用户...这是为了显示如何仅重新呈现您所在的页面。

All in all, this is expected behavior, to my knowledge.总而言之,据我所知,这是预期的行为。

编辑 react-state-manager-hooks-context

I tried to explain with different example hope that will help.我试图用不同的例子来解释希望会有所帮助。

Because context uses reference identity to determine when to re-render, that could trigger unintentional renders in consumers when a provider's parent re-renders.因为上下文使用引用标识来确定何时重新渲染,所以当提供者的父级重新渲染时,这可能会在使用者中触发意外渲染。

for example: code below will re-render all consumers every time the Provider re-renders because a new object is always created for value例如:下面的代码将在每次 Provider 重新渲染时重新渲染所有消费者,因为总是为value创建一个新对象

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

To get around this, lift the value into the parent's state为了解决这个问题,将值提升到父状态

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

This solution is used to prevent a component from rendering in React is called shouldComponentUpdate.这个用于防止组件在 React 中渲染的解决方案称为 shouldComponentUpdate。 It is a lifecycle method which is available on React class components.它是一种可在 React 类组件上使用的生命周期方法。 Instead of having Square as a functional stateless component as before:而不是像以前那样将 Square 作为功能性无状态组件:

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

You can use a class component with a componentShouldUpdate method:您可以使用带有 componentShouldUpdate 方法的类组件:

class Square extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    ...
  }

  render() {
    return <Item>{this.props.number * this.props.number}</Item>;
  }
}

As you can see, the shouldComponentUpdate class method has access to the next props and state before running the re-rendering of a component.如您所见, shouldComponentUpdate 类方法在运行组件的重新渲染之前可以访问下一个 props 和 state。 That's where you can decide to prevent the re-render by returning false from this method.这就是您可以决定通过从此方法返回 false 来阻止重新渲染的地方。 If you return true, the component re-renders.如果返回 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>;
  }
}

In this case, if the incoming number prop didn't change, the component should not update.在这种情况下,如果传入的数字道具没有改变,则组件不应更新。 Try it yourself by adding console logs again to your components.通过将控制台日志再次添加到您的组件中来尝试一下。 The Square component shouldn't rerender when the perspective changes.当透视改变时,Square 组件不应重新渲染。 That's a huge performance boost for your React application because all your child components don't rerender with every rerender of their parent component.这对您的 React 应用程序来说是一个巨大的性能提升,因为您的所有子组件都不会随着其父组件的每次重新渲染而重新渲染。 Finally, it's up to you to prevent a rerender of a component.最后,由您来阻止重新渲染组件。

Understanding this componentShouldUpdate method will surely help you out!了解这个 componentShouldUpdate 方法肯定会对你有所帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM