[React 基礎系列] React 中的 元素 vs 組件
- 元素
- 元素的渲染
- 元素的更新
- Virtual DOM
- 組件
- 組件的渲染
- 組合組件
- 父子組件
- 兄弟組件
- 組件的提取
- 總結
在這次復習官方教程之前,我還沒意識到 元素(element) 和 組件(component) 在 React 中的定義還是有些不太一樣的,通俗理解就是,組件是由元素所構成的,
第一個復習的系列是 JSX 的內容:什么是 JSX,以及如何使用 JSX
元素
官網上是這么描述元素的:
Elements are the smallest building blocks of React apps.
元素是構成 React 應用的最小磚塊,
這個翻譯我真的覺得像是機翻
直白的說法就是,元素是 React 應用中可被創建的最基本的組成部分,沒有比元素更小的組成部分了,依舊是用 Hello World 為例,如果想要在頁面上渲染 Hello World,最簡單的寫法如下:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const element = <h1>Hello World</h1>;
ReactDOM.render(element, document.getElementById('root'));
效果如圖下:
這么看來,元素的寫法其實還頗有一種,所思即所見
的感覺,也就是說,你想在頁面上呈現的內容,就是寫出來的內容,
在這個案例中,如果想要渲染一個 h1 的標簽,其內容就是 Hello World,那么需要定義一個元素,標簽為 <h1>,定義的元素內容為 Hello World,最后將其掛載到頁面上即可,
元素的渲染
在上一篇 什么是 JSX 部分中曾經講過,其實 JSX 最侄訓被編譯成一個 JavaScript 物件,如:
const element = <h1 className="greeting">Hello, world!</h1>;
// 實際上經過編譯之后等同于
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!',
},
};
單純的 JavaScript 物件是沒有辦法直接被渲染到頁面上的,因此它需要借助另一個函式—— ReactDOM.render() ——將 JavaScript 物件渲染成實際的 DOM 元素,ReactDOM.render() 的使用語法為:
ReactDOM.render(element, document.getElementById('root'));
其中,element 是創建的 JavaScript 物件;
root 是 HTML 頁面上的真實存在的元素,
完整的頁面結構為:
|- public
| |- index.html => 作為index.js的入口檔案
|- src
| |- index.js => 作為整個專案的入口檔案
index.html 中一般只會存在一個 root 元素作為整個 DOM 樹的根節點,ReactDOM.render() 會獲取這個根節點,并負責將創建的元素由 JavaScript 物件轉化為頁面上展示的 DOM 節點,
元素的更新
React 的很多核心設計都是基于不可變性,React 中的元素也是如此,這也就意味著一旦一個 React 的元素被創建成功之后,元素的屬性、子元素便無法被更新,而唯一更新 UI 的方式就是創建一個新的元素,并且將新的元素傳入到 ReactDOM.render() 之中,
如果 React 只是在每一次有 UI 變動時,就更新一次完整的 DOM 樹,那么這就和傳統非常耗時的 DOM 操作沒什么區別,
不過,React 內部有自己的更新機制,能夠很好的對操作 DOM 進行優化,減少無意義的重復渲染造成的性能損耗,
Virtual DOM
Virtual DOM 就是 React 實作的 DOM 更新優化機制,它將 DOM 樹進行虛擬化的操作后存入記憶體之中,虛擬的 DOM 樹與真實的 DOM 樹會通過如 ReactDOM 之類的庫進行連接,
使用虛擬 DOM 樹的優點非常的明顯,在對 DOM 進行操作時,直接更新原有的 DOM 樹是非常耗時的操作,但是 ReactDOM 會對兩棵樹——虛擬 DOM 樹和真實 DOM 樹進行對比,只重新渲染更新過的 DOM 節點,這樣就能夠省去大量無意義的重新渲染,從而提升性能,
對于 React 團隊來說,他們的理念是專注于當下的頁面看起來應當是什么模樣,而非如何在未來更新 UI,
組件
組件擁有以下幾個特性:
- 是一部分 UI
- 這一部分 UI 是 獨立(independent) 且 可復用 的片段
- 這一部分 UI 可 孤立(isolate) 于其他的片段單獨存在
- 可以通過
props去獲得從外部傳來的資料
和 元素 對比一下:
// 組件
// 函陣列件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 類組件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 這里的 Welcome 回傳的是自定義元素
// 呼叫的方法都是一樣的
const elementOfComponent = <Welcome />;
能夠很明顯的發現,元素的概念是一個 JavaScript 的物件;而組件的概念類似于一個 JavaScript 的函式,
注:
自定義的組件必須以大寫字母開頭
小寫字母開頭的開頭的組件將會被試做是原生 DOM 的標簽
組件的渲染
以案例中的 Welcome 為例,這個時候的回傳值是空白的:
原因是因為外部沒有傳進 name 這個屬性給 Welcome組件,在獲取不到資料的情況下,props.name 的值就是 undefined,因此頁面上只會顯示 Hello,,
現在,就將 name 傳給 Welcome組件,以此展示一下 props 的呼叫,
// 組件的實作方式不變
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 僅修改呼叫方式
const element = <Welcome name="John" />;
// 具體頁面的渲染還是需要依靠 ReactDOM 去實作
ReactDOM.render(element, document.getElementById('root'));
這時候,頁面顯示的就是更有意義的內容:
組合組件
傳統意義上來說,組合組件有這么會有 父子 和 兄弟 這兩種關系,
父子組件
父子組件也就是在組件之中繼續嵌套一個組件,例如說一個主頁面中可以嵌套一個導航欄和正文內容:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
const Nav = () => {
return (
<nav>
<ol>
<li className="nav-list">
<span>homepage</span>
</li>
<li className="nav-list">
<span>articles</span>
</li>
<li className="nav-list">some article</li>
</ol>
</nav>
);
};
const MainContent = () => {
return (
<div className="main-content">
<h1>Header</h1>
<p>some content</p>
</div>
);
};
const MainPage = (props) => {
return (
<>
<Nav />
<MainContent />
</>
);
};
const component = <MainPage name="John" />;
ReactDOM.render(component, document.getElementById('root'));
效果圖如下:
這個頁面的結構如下:
|- MainPage
| |- Nav
| |- MainContent
這其中,MainPage 與 Nav/MainContent 就是父子關系,又因為 React 的資料是單向傳輸的,所以要傳資料的話,資料只能從 MainPage 傳向 Nav/MainContent,
兄弟組件
同樣是上面的案例,Nav 與 MainContent 是并列存在的,因此他們之間的關系就是兄弟關系,
在 React 之中,資料是單向傳輸的,只能通過 props 從 父 傳到 子,兄弟之間也無法交換資訊,所以 Nav 與 MainContent 之間是沒有辦法進行資料的交換的,
另外,要渲染兄弟組件有以下幾種方式:
-
將他們包裹在一個根元素之中,根元素作為他們的父元素
這種寫法適合兄弟之間有一定的關聯的情況下,這種常見的案例有導航欄下的
li,它們可以隸屬于nav下作為子組件, -
如果兄弟組件之間沒有特別明顯的層級關系,又不想額外嵌套一個
<div> </div>在頁面上去增加復雜度,可以使用<React.Fragment> <Component /> </React.Fragment>,React.Fragment是 React 提供的語法糖,這時候 React 會生成一個空的父節點,語法上是正確的,但是不會增加額外的節點,也就不會增加額外的復雜度, -
上文使用的
<><Component /></>是React.Fragment的語法糖,本質上來說和React.Fragment的效果是一樣的,只是更加省事一些,這個寫法應該是 v17 之后推出的新寫法,v16 無法兼容,
組件的提取
如何合理地對組件進行提取以達到更好的復用性,一直都是一個比較復雜的問題,根據 React 的官方建議來說,當組件滿足以下兩個特性,就代表這個部分可以被提取成單獨的組件:
-
被多次重復使用的 UI 部分
以 蘋果 為例,頁面上的一些 UI 部分都有一定程度的相似性,如:
這幾個產品介紹的設計結構其實都是一樣的:
|- 組件(有不同的背景色) | |- header,商品名 | |- 商品賣點 | |- 讓客戶選擇查看更多,和選擇購買的超鏈接 | |- 商品圖片這種情況下,就可以選擇將這幾個部分組成一個組件,隨后只要通過 props 將對應的屬性傳進去,就可以有效的降低開發時間,提升開發效率,
-
當組件有一定程度的復雜性
以淘寶為例:
如果直接進行開發的話,這個頁面實作起來絕對會有相當程度的復雜性,全都寫在一個組件之中對于頁面的更新(如出現不同的活動)、后期的維護也會形成一個極大的挑戰,
但是如果將頁面根據功能進行簡單的拆分,那么按組件實作的難度就會降低不少:
根據劃分的區域可以大致分為:
|- page,整個頁面 | |- 導航欄,導航欄組件在其他頁面也可以復用,之后直接參考即可 | |- 搜索,搜索組件在其他頁面也可以復用,之后直接參考即可 | |- 首頁的主內容 | | |- 這中間還可以繼續拆分,這里不再細講 | |- 側邊欄,側邊欄在其他頁面也可以復用,之后直接參考即可可以看到,這里粗粗劃分了四個組件,三個組件是可以在其他頁面直接復用,也就是說,在實作其他頁面時,就不需要再去重復實作已經實作過的功能,
總結
本章學習內容主要學習了一下 元素 和 組件,知識點包括:
-
元素
-
元素是 React 應用的最小組成部分
-
元素被編譯完成后是一個 JavaScript 物件
-
元素的渲染是通過
ReactDOM.render()實作的 -
元素具有不可變性,需要更新元素只能重新創建一個元素,并傳入
ReactDOM.render()中重新渲染 -
React 實作了 Virtual DOM,會對比更新前后的虛擬 DOM 樹,并且只重新渲染更新過的 DOM 節點
-
-
組件
-
組件是可獨立復用的 UI 代碼段
-
組件類似于 JavaScript 中的函式
-
組件可以組合使用
-
為了方便實作以及維護,可以提取組件,做更細顆粒的實作
-
下一章的學習將會注重與組件的狀態管理以及生命周期,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/289619.html
標籤:其他
上一篇:jQuery 樣式、屬性、元素操作、影片效果、尺寸位置操作
下一篇:【熬夜猛肝萬字博文】學妹問我怎么入門 Javascript,百般盤問下我終于決定貢獻出自己的 JavaScript入門筆記(四)
