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

dre*_*val 14 javascript reactjs react-native react-context

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

有没有办法阻止 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>
  );
};
Run Code Online (Sandbox Code Playgroud)

减速器的一个例子:

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;
Run Code Online (Sandbox Code Playgroud)

Root reducer,可以组合尽可能多的reducer:

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

const rootReducer = ({ users, books }, action) => ({
  users: userReducer(users, action),
  books: bookReducer(books, action)
});
Run Code Online (Sandbox Code Playgroud)

一个动作的例子:

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;
Run Code Online (Sandbox Code Playgroud)

这是书籍组件:

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;
Run Code Online (Sandbox Code Playgroud)

当 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;
Run Code Online (Sandbox Code Playgroud)

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

for*_*d04 8

Books并且Users目前在每个周期重新渲染- 不仅是在存储值更改的情况下。

1. prop 和 state 的变化

React重新渲染整个子组件树,以组件为根,其中 props 或 state 发生了变化。您通过getUsers, so更改父状态Books并Users重新渲染。

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"));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

优化技术

使用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
Run Code Online (Sandbox Code Playgroud)

重要提示: 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>
Run Code Online (Sandbox Code Playgroud)

优化技术

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

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

<ApiContext.Provider value={store}>
  {children}
</ApiContext.Provider>
Run Code Online (Sandbox Code Playgroud)

其他

不确定,为什么将所有这些结构放在一起Books,只需使用一个useContext:

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

您还可以同时使用和来查看此代码示例。React.memouseContext


Mat*_*ich 3

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

发生这种情况是因为页面加载,然后useEffect启动并抓取书籍或用户,然后页面需要重新渲染才能将新抓取的书籍或用户放入 DOM 中。

我已经修改了您的 CodePen,以表明情况确实如此。如果您在书籍或用户页面上禁用“自动加载”(我为此添加了一个按钮),则浏览该页面,然后浏览回该页面,你会看到它只渲染一次。

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

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

编辑react-state-manager-hooks-context