React中每次渲染都會傳入一個新的props.children到子組件?

傳入props.children后, 為什么會導致組件的重新渲染?

問題描述

在 react 中, 我想要對組件的渲染進行優化, 遇到了一個非常意思的問題, 當我向一個組件中傳入了 props.children 之后, 每次父組件重新渲染都會導致這個組件的重新渲染; 它看起來的表現就像是被memo包裹的組件, props和自身狀態未發生變化, 組件卻重新渲染了; 下面我寫了一個demo, 一起來看看這個問題吧:

父組件App中引入了一個Home組件:

import Home from "./pages/Home";
import { useState } from "react";function App() {const [count, setCount] = useState(0);console.log("App is render");return (<div className="App">{count}<button onClick={() => setCount(count + 1)}>Increment</button><Home></Home></div>);
}

使用 memo 包裹 Home 子組件, 同時 Home 組件可以接收一個 props.children 展示傳入到 Home 中的組件, 如下:

import React, { memo } from "react";const Home = memo((props) => {console.log("Home is render");return (<div>Home{props.children}</div>);
});export default Home;

目前在 App 組件中, 沒有向 Home 組件中傳入 props.children, 此時第一次加載時 App 組件和 Home 組件都會重新渲染, 當我們點擊 Increment 按鈕讓 count 的值變化時, App 組件重新渲染, 由于 Home 組件被 memo 包裹, 當 Home 組件的 props 和自身狀態未發生變化時, 組件不進行重新渲染, 目前也正是我們所期望的這樣, 沒有問題。

但是, 當我們在 App 組件中向 Home 組件傳入 props.children 時, 就會出現問題(此問題不僅限于我下面例子中傳入了一個 About 組件, 傳入任何元素都會出現這個問題, 即使我們傳入一個簡單的 div 元素):

import { useState } from "react";
import Home from "./pages/Home";
import About from "./pages/About";function App() {const [count, setCount] = useState(0);console.log("App is render");return (<div className="App">{count}<button onClick={() => setCount(count + 1)}>Increment</button><Home><About /></Home></div>);
}

About 組件同樣使用 memo 包裹, 代碼如下:

import React, { memo } from "react";const About = memo(() => {console.log("About is render");return <div>About</div>;
});export default About;

此時如果我們修改 count 的值, 會導致 App 組件重新渲染, 但是也會導致 Home 組件重新渲染。這就有些令人疑惑, 我們來分析一下:

首先我們知道, 在未經過任何優化的情況下, 父組件重新渲染一定會導致子組件的重新渲染, 那么也就會創建一個新的組件實例; 而如果使用 memo 對組件進行包裹, 那么在組件的 props 和自身狀態沒有發生變化的情況下, 父組件重新渲染子組件不會重新渲染, 是不是意味著不會創建一個新的組件實例呢? (這里進入了思維誤區)

上面代碼中, 我們向 Home 組件中傳遞了一個 About 組件, 目前 Home 組件中的表現就相當于 props.children = <About/>, 由于 Home 組件被 memo 包裹還重新渲染了, 那大幾率是 props 發生了變化。糾結之處就在于, 此時 props 中又只有 children 一個屬性, 值為 About 組件, About 組件同樣被 memo 包裹, 且沒有依賴任何 props 和狀態, 如果 About 組件返回的結果應該是相同的, 就不應該導致 Home 組件的 props 發生變化才對。

這就是我所遇到的問題, 為什么 props.children 會影響組件的渲染呢?

問題分析

我依然懷疑是由 Home 組件的 props 發生了變化, 唯一可能變化的就是 About 組件, 為了驗證我的想法, 于是我在Home 組件中定義了一個 aboutRef 變量, 使用 useRef 包裹 About 組件, 如下所示:

import Home from "./pages/Home";
import { useState } from "react";function App() {const [count, setCount] = useState(0);// 使用useRef包裹const aboutRef = useRef(<About/>);console.log("App is render");return (<div className="App">{count}<button onClick={() => setCount(count + 1)}>Increment</button><Home>{aboutRef.current}</Home></div>);
}

此時我發現, 首次渲染時 App、Home、About 都會渲染, 而當 count 發生變化時, 只有 App 組件重新渲染了, 這也就達到了我最初期望的效果。但是為什么包裹了 useRef 才可以做到這個效果呢? 到這里已經可以確定的是 Home 組件的 props.children 一定是發生了變化的, 那么我們來探討一下 About 組件為什么會變化。

變化的原因是因為組件每次重新渲染時都會創建 React 元素, 例如<About /> = jsx(About), 并且在調用時會返回一個新對象, 當然不只是 About 會這樣創建, 其他組件和元素也是這樣創建的。其中jsx()只不過是React.createElement 的語法糖而已, 元素或組件都會通過 React.createElement 創建返回一個 ReactElement 對象, 這是因為 React 利用 ReactElement 對象組成了一個 Javascript 對象樹(也就是虛擬 DOM )。前面我進入了一個思維誤區, 認為 memo 包裹的組件不會再被重新創建了, 其實不管是否有memo包裹, 都是會通過 React.createElement 來創建, 只不過被memo包裹的組件創建出來的 React 元素會有所不同, 具體的可以深入的學習 memo, 這里給大家推薦一篇文章《從源碼學 API 系列之 React.memo》。

因此對于 props.children 而言, 每次得到的都是 React.createElement(About) 返回的一個新對象, 這也是 Home 組件的 props 改變了的原因; 而我們使用 useRef, 創建了一個不會改變的對象賦值給 Home 組件的 props, 所以 Home 組件的 props 沒有發生變化, 就不會重新渲染。

解決方案

解決這個問題, 除了使用 useRef 之外, 我們還可以定義一個變量, 提到 App 組件外, 也可以做到這個效果, 如下所示:

import { useState } from "react";
import Home from "./pages/Home";
import About from "./pages/About";// 在組件外定義變量
const about = <About />;function App() {const [count, setCount] = useState(0);console.log("App is render");return (<div className="App">{count}<button onClick={() => setCount(count + 1)}>Increment</button><Home>{about}</Home></div>);
}

當 About 組件沒有依賴于 App 組件中其他狀態時, 我們可以采用上面的做法, 但是如果 About 組件還依賴 App 內的其他狀態, 可以發現無論是提變量還是 useRef 的做法都無法實現, 例如 About 組件中接收一個 name 參數, 由 App 組件傳入:

import React, { memo } from "react";// 接收一個props.name
const About = memo(({ name }) => {console.log("About is render");return <div>About: {name}</div>;
});export default About;

這個時候我們就需要借助于 useMemo 進行優化(不用 useCallback 的原因是 useCallback 作用于函數, useMemo 作用于返回值, 在這里很明顯我們想要作用于函數返回的組件), 就做到了實現當 count 發生變化時, 只有 App 組件重新渲染, 而 name 屬性變化時 App、Home、About 都會重新渲染:

function App() {const [count, setCount] = useState(0);// 傳入About組件的狀態const [name, setName] = useState("Hello");// 使用useMemo優化const about = useMemo(() => <About name={name} />, [name]);console.log("App is render");return (<div className="App">{count}<button onClick={() => setCount(count + 1)}>Increment</button><button onClick={() => setName("abc")}>Change Name</button><Home>{about}</Home></div>);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211986.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MTU與MSS

MTU&#xff1a;一個網絡包的最大長度&#xff0c;以太網中一般為1500各字節。 MSS&#xff1a;除去頭部之后&#xff0c;一個網絡包所能容納的TCP數據的最大長度。 應用程序調用write后&#xff0c;將要發送的數據被交給TCP/IP協議棧進行。 協議棧不關心應用的數據內容&…

四:爬蟲-Cookie與Session實戰

四&#xff1a;Cookie與Session實戰 ? 在瀏覽網站的過程中&#xff0c;我們經常會遇到需要登錄的情況&#xff0c;有些頁面只有登錄之后才可以訪問。在登錄之后可以連續訪問很多次網站&#xff0c;但是有時候過一段時間就需要重新登錄。還有一些網站&#xff0c;在打開瀏覽器…

c語言歸并排序(詳解)

歸并排序是一種分治算法&#xff0c;它將列表分割成較小的子列表&#xff0c;然后遞歸地對子列表進行排序&#xff0c;最后將這些子列表合并以產生已排序的列表。基本概念包括&#xff1a; 分割&#xff1a;將列表分割成較小的子列表&#xff0c;直到子列表的長度為1或0。排序…

Leetcode—219.存在重復元素II【簡單】

2023每日刷題&#xff08;五十三&#xff09; Leetcode—219.存在重復元素II 實現代碼 class Solution { public:bool containsNearbyDuplicate(vector<int>& nums, int k) {unordered_map<int, int> m;int n nums.size();for(int i 0; i < n; i) {if(m…

vs的生成事件error MSB3073

生成事件設置位于&#xff1a;項目-》屬性-》生成事件&#xff1b; 生成事件有&#xff1a;生成前事件、鏈接前事件、生成后事件 以生成前事件為例&#xff1a;可以用于一些庫文件的配置 COPY ..\dll\*.* .\bin\ MKDIR .\bin\libx COPY ..\dll\libx\*.* .\bin\libx這里是在開…

[Decipher@mailfence.com].faust勒索病毒數據怎么處理|數據解密恢復

導言&#xff1a; 在數字世界的邊緣&#xff0c;[support2022cock.li].faust、[tsai.shenmailfence.com].faust、[Encrypteddmailfence.com].faust、[backupsairmail.cc].faust、[Deciphermailfence.com].faust勒索病毒如同黑暗的幽靈&#xff0c;威脅著我們珍貴的數字財產。本…

漏洞復現-大華dss struts2-045表達式注入漏洞(附漏洞檢測腳本)

免責聲明 文章中涉及的漏洞均已修復&#xff0c;敏感信息均已做打碼處理&#xff0c;文章僅做經驗分享用途&#xff0c;切勿當真&#xff0c;未授權的攻擊屬于非法行為&#xff01;文章中敏感信息均已做多層打馬處理。傳播、利用本文章所提供的信息而造成的任何直接或者間接的…

【webpack】初始化

webpack 舊項目的問題下一代構建工具 Vite 主角 &#xff1a;webpack安裝webpack1&#xff0c;mode的選項2&#xff0c;使用source map 精準定位錯誤行數3&#xff0c;使用watch mode(觀察模式)&#xff0c;自動運行4&#xff0c;使用webpack-dev-server工具&#xff0c;自動刷…

Linux_CentOS_7.9配置oracle sqlplus、rman實現上下按鍵切換歷史命令等便捷效率功能之簡易記錄

配置oracle sqlplus以及rman可以上下按鍵切換歷史命令等便捷效率功能 設置前提是已經yum安裝了rlwrap軟件具體軟件下載及配置參考文章http://t.csdnimg.cn/iXuVK su - oracleVim .bash_profile ## 文件中增加如下的別名設置 ---------------- alias sqlplusrlwrap sqlplus…

c++的算術生成算法

#include<numeric>//算術生成算法頭文件 要加的頭文件#include<numeric> accumulate 是 C 標準庫中的一個算法函數&#xff0c;用于計算給定范圍內的數值之和&#xff0c;它位于 <numeric> 頭文件中。它的函數原型如下&#xff1a; template <class In…

Matlab之帶時區的日期時間數據和不帶時區的日期時間數據相互轉換方法

使用datetime和datetimezone函數 通過使用datetime和datetimezone函數&#xff0c;可以將帶時區的日期時間數據轉換為不帶時區的數據&#xff0c;或者將不帶時區的日期時間數據轉換為帶時區的數據。這樣可以滿足坐標區的配置要求。 1、將帶時區的日期時間數據轉換為不帶時區的…

理解IoC容器初始化

問題&#xff1a;當自己面試或者背誦八股文時&#xff0c;會背到各種各樣的spring底層的東西&#xff0c;自己越看越迷糊。 OS&#xff1a;不知道兄弟們是不是也會這樣&#xff1f;如果大家沒有說明我太菜了。 原因&#xff1a;就是自己學的框架越來越多&#xff0c;很多框架…

?types --- 動態類型創建和內置類型名稱?

目錄 動態類型創建 標準解釋器類型 附加工具類和函數 協程工具函數 源代碼: Lib/types.py 此模塊定義了一些工具函數&#xff0c;用于協助動態創建新的類型。 它還為某些對象類型定義了名稱&#xff0c;這些名稱由標準 Python 解釋器所使用&#xff0c;但并不像內置的 int …

代碼規范及開發工具

代碼規范及開發工具&#xff1a; 前端&#xff08;vscode、idea&#xff09;: JavaScript規范&#xff1a; 1. 谷歌開源項目風格指南&#xff1a;JavaScript 、TypeScript篇 https://zh-google-styleguide.readthedocs.io/en/latest/google-typescript-…

P8625.生命之樹

求最大的子樹之和 維護包含當前節點的最大子樹之和就好了 #include<bits/stdc.h> using namespace std; using ll long long; const int N 1e610; ll w[N]; vector<int>g[N]; ll f[N]; ll res;ll dfs(int u,int father){f[u] w[u];for(auto &t:g[u]){if(tf…

2023.12.10 homework

五年級一元一次方程

C語言作業6

1.聯合體也會完全浪費空間 2.在結構體中 注意好偏移量和實際是第幾個的區別 那個對齊數是和偏移量有關的 (就用我之前的那個就行了) 3. 字節序 才有大小端

參數占位符#{}和${}

#是預處理而$是直接替換 Mybatis在處理#{}時&#xff0c;會將SQL中的#{}替換成占位符&#xff1f;&#xff0c;再使用preparedStatement的set方法來賦值。而Mybatis在處理 時&#xff0c;是將 {}時&#xff0c;是將 時&#xff0c;是將{}直接替換成變量的值 我們分別使用#{}和…

Redis AOF源碼解析

本文取3.0版本分析&#xff08;各個版本差異很大&#xff0c;4.0以上才有aof和rdb混合模式&#xff09; 觸發時機 1、bgrewriteaofCommand函數觸發&#xff0c;即在Redis server服務上運行bgrewriteaof命令。 1-1、當前已經有 AOF 重寫的子進程正在執行&#xff0c;重復執行bg…

JavaScript-Window對象

Window對象 BOM&#xff1a;瀏覽器對象模型 定時器-延時函數 JavaScript內置的一個用來讓代碼延遲執行的函數&#xff0c;setTimeout setTimeout(回調函數&#xff0c;等待的毫秒數);setTimeout僅僅只執行依次&#xff0c;所以可以理解為就是把一段代碼延遲執行&#xff0c…