我将带有钩子的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>
);
};
减速器的示例:
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;
优化上下文重呈现的最佳方法是什么?预先感谢!
答案 0 :(得分:4)
Books
和Users
当前在每个周期重新渲染 -不仅在商店价值发生变化的情况下。
反应re-renders the whole sub component tree以组件作为根开始,其中道具或状态发生了变化。您通过getUsers
更改了父状态,因此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"));
<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
值更改触发重新渲染)!
所有上下文使用者(useContext
)会自动重新呈现。
// here object reference is always a new object literal = re-render every cycle
<ApiContext.Provider value={{ ...state, dispatch }}>
{children}
</ApiContext.Provider>
确保对上下文值具有稳定的对象引用,例如由useMemo
挂钩。
const [state, dispatch] = useReducer(rootReducer, {});
const store = React.useMemo(() => ({ state, dispatch }), [state])
<ApiContext.Provider value={store}>
{children}
</ApiContext.Provider>
不确定,为什么要将{em> all 这些结构放在Books
中,只使用一个useContext
:
const { dispatch, books } = useContext(ApiContext);
// drop these
const contextValue = useContext(ApiContext);
<ApiContext.Consumer> /* ... */ </ApiContext.Consumer>;
您还可以同时使用React.memo
和useContext
看this code example。
答案 1 :(得分:1)
我相信这里发生的是预期的行为。它呈现两次的原因是,当您分别访问书或用户页面时,您会自动抓住一本新书/用户。
之所以发生这种情况,是因为页面加载了,然后useEffect
启动并抓住了书籍或用户,然后需要重新渲染页面才能将新抓住的书籍或用户放入DOM。
我已修改您的CodePen,以证明是这种情况。.如果您在书或用户页面上禁用“自动加载”(为此添加了一个按钮),则浏览该页面,然后浏览回该页面,您将看到它只呈现一次。
我还添加了一个按钮,该按钮可让您按需获取新书或用户...这是为了显示仅如何重新呈现您所在的页面。
总而言之,据我所知,这是预期的行为。
答案 2 :(得分:0)
我试图用不同的例子来说明希望有所帮助的情况。
因为上下文使用引用标识来确定何时重新渲染,所以当提供者的父母重新渲染时,这可能会触发消费者的无意渲染。
例如:每次提供者重新渲染时,下面的代码将重新渲染所有使用者,因为始终为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>
);
}
}
答案 3 :(得分:0)
此解决方案用于防止组件在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>;
}
}
在这种情况下,如果传入电话号码prop不变,则组件不应更新。您可以尝试再次尝试将控制台日志添加到组件中。透视图更改时,不应重新渲染Square组件。对于您的React应用程序来说,这是极大的性能提升,因为您的所有子组件都不会随着父组件的每次重新渲染而重新渲染。最后,由您决定防止重新呈现组件。
了解此componentShouldUpdate方法肯定会帮助您!