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/208730.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/208730.shtml
英文地址,請注明出處:http://en.pswp.cn/news/208730.shtml

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

相關文章

【1day】?萬戶協同辦公平臺 convertFile 任意文件讀取漏洞學習

注:該文章來自作者日常學習筆記,請勿利用文章內的相關技術從事非法測試,如因此產生的一切不良后果與作者無關。 目錄 一、漏洞描述 二、影響版本 三、資產測繪 四、漏洞復現

圖的鄰接鏈表儲存

噴了一節課 。。。。。。。、。 #include<stdio.h> #include<stdlib.h> #define MAXNUM 20 //每一個頂點的節點結構&#xff08;單鏈表&#xff09; typedef struct ANode{ int adjvex;//頂點指向的位置 struct ArcNode *next;//指向下一個頂點 …

C++ 內存分區模型

目錄 程序運行前 代碼區 全局區 程序運行后 new 在堆區開辟數據 delete釋放堆區數據 堆區開辟數組 內存分區模型 棧&#xff08;Stack&#xff09; 堆&#xff08;Heap&#xff09; 全局/靜態存儲區&#xff08;Global/Static Storage&#xff09; 常量存儲區&am…

力扣230. 二叉搜索樹中第K小的元素

深度優先搜索 思路&#xff1a; 二叉搜索樹的特性&#xff0c;通過中序遍歷得到有序序列&#xff0c;則遍歷到第K個節點的時候即為結果&#xff1b;使用棧通過深度優先遍歷進行中序遍歷&#xff1a; 先將節點和左子節點壓棧&#xff1b;然后棧頂上就是“最左”葉子節點&#x…

Linux DAC權限的簡單應用

Linux的DAC&#xff08;Discretionary Access Control&#xff09;權限模型是一種常見的訪問控制機制&#xff0c;它用于管理文件和目錄的訪問權限。作為一名經驗豐富的Linux系統安全工程師&#xff0c;我會盡可能以簡單明了的方式向計算機小白介紹Linux DAC權限模型。 在Linu…

jenkins中“Jenkins Plot Plugin”的使用方法,比較兩個excel的數據差異

Jenkins Plot Plugin是Jenkins的一個插件&#xff0c;它可以用于生成圖表和報表&#xff0c;以便更好地理解和分析構建和測試數據。下面是使用Jenkins Plot Plugin比較兩個Excel數據差異的步驟&#xff1a; 1.安裝Jenkins Plot Plugin&#xff1a;在Jenkins的插件管理頁面搜索…

使用 Axios 進行網絡請求的全面指南

使用 Axios 進行網絡請求的全面指南 本文將向您介紹如何使用 Axios 進行網絡請求。通過分步指南和示例代碼&#xff0c;您將學習如何使用 Axios 庫在前端應用程序中發送 GET、POST、PUT 和 DELETE 請求&#xff0c;并處理響應數據和錯誤。 準備工作 在開始之前&#xff0c;請…

電子學會C/C++編程等級考試2021年09月(五級)真題解析

C/C++等級考試(1~8級)全部真題?點這里 第1題:抓牛 農夫知道一頭牛的位置,想要抓住它。農夫和牛都位于數軸上,農夫起始位于點N(0<=N<=100000),牛位于點K(0<=K<=100000)。農夫有兩種移動方式: 1、從X移動到X-1或X+1,每次移動花費一分鐘 2、從X移動到2*X,每…

ubuntu18.04安裝opencv-4.5.5+opencv_contrib-4.5.5

一、安裝opencv依賴 sudo apt-get install build-essential sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-d…

Navicat 技術指引 | 適用于 GaussDB 分布式的自動運行功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式數據庫。GaussDB 分布式模式更適合對系統可用性和數據處理能力要求較高的場景。Navicat 工具不僅提供可視化數據查看和編輯功能&#xff0c;還提供強大的高階功能&#xff08;如模型、結…

「Python編程基礎」第7章:字符串操作

文章目錄 一、回顧二、新手容易踩坑的引號三、轉義字符四、多行字符串寫法五、注釋六、字符串索引和切片七、字符串的in 和 not in八、字符串拼接九、轉換大小寫十、合并字符串join()十一、分割字符串split()十二、字符串替換 replace()十三、字符串內容判斷方法十四、字符串內…

讀文章摘錄

20%的時間可以做點業余項目。有個叫克萊舍基的人&#xff0c;寫了一本書&#xff0c;書名叫《認知盈余-網絡時代的創造與繁榮》&#xff0c;他有個觀點&#xff0c;閑暇時間給人機會創造有價值的東西。 很重要的一點是選合適的人&#xff0c;把他們引入團隊。何謂合適的人&…

uniapp 開發app項目步驟

Uniapp 是一個基于 Vue.js 的跨平臺開發框架&#xff0c;可以將同一個項目同時編譯到多個平臺&#xff0c;包括 H5、iOS、Android 等。以下是開發 Uniapp 項目的步驟&#xff1a; 安裝 Uniapp 可以通過 npm 安裝 Uniapp&#xff0c;具體操作如下&#xff1a; npm install -g…

Qt使用Cryptopp生成HMAC-MD5

近期項目中HTTPS通訊中&#xff0c;token需要使用HMAC-MD5算法生成&#xff0c;往上找了一些資料后&#xff0c;仍不能滿足自身需求&#xff0c;故次一記。 前期準備&#xff1a; ①下載Cryptopp庫&#xff08;我下載的是8.8.0 Release版本&#xff09;&#xff1a;Crypto Li…

Linux: glibc: net/if.h vs linux/if.h

最近看到一段代碼改動,用net/if.h替換了linux/if.h。仔細看了看這兩個的區別: https://stackoverflow.com/questions/20082433/what-is-the-difference-between-linux-if-h-and-net-if-h 從網上搜了一下看到如下的一個編譯錯誤,如果同時使用這兩個if.h文件,需要將net/if.h…

注意力機制添加方法

要將注意力機制模塊添加到YoloV5工程項目中的yolo.py中&#xff0c;可參考以下四種情況。 以下4個elif代碼來自https://yolov5.blog.csdn.net/article/details/129108082 elif m in [SimAM, ECA, SpatialGroupEnhance,TripletAttention]:args [*args[:]]elif m in [CoordAtt…

【1day】致遠系統A6版本operaFileActionController.jsp接口任意文件讀取漏洞學習

注:該文章來自作者日常學習筆記,請勿利用文章內的相關技術從事非法測試,如因此產生的一切不良后果與作者無關。 目錄 一、漏洞描述 二、影響版本 三、資產測繪 四、漏洞復現

基于ResNet模型的908種超大規模中草藥圖像識別系統

中草藥藥材圖像識別相關的實踐在前文中已有對應的實踐了&#xff0c;感興趣的話可以自行移步閱讀即可&#xff1a; 《python基于輕量級GhostNet模型開發構建23種常見中草藥圖像識別系統》 《基于輕量級MnasNet模型開發構建40種常見中草藥圖像識別系統》 在上一篇文章中&…

RocketMQ-RocketMQ高性能核心原理(流程圖)

1.NamesrvStartup 2.BrokerStartup 3. DefualtMQProducer 4.DefaultMQPushConsumer

maven工程的pom.xml文件中增加了依賴,但偶爾沒有下載到本地倉庫

maven工程pom.xml文件中的個別依賴沒有下載到本地maven倉庫。以前沒有遇到這種情況&#xff0c;今天就遇到了這個問題&#xff0c;把解決過程記錄下來。 我在eclipse中編輯maven工程的pom.xml文件&#xff0c;增加對mybatis的依賴&#xff0c;但保存文件后&#xff0c;依賴的j…