內容目錄
什麼是組件(Component )
依照官網的說明是指可以把UI元件獨立拆分出來,成為可一個可重複使用的程式碼,那分為用function跟class寫法不同的組件
組件的規則是
- 使用大寫開頭命名
- 回傳JSX
function Component
寫起來很像是在寫一般的function只是這邊回傳的內容是JSX
※JSX是類似HTML的語法堂
function App() {
return <h1>hi function</h1>;
}
那如果把return拿掉,就只是單純的function,所以我們用TypeScript下去加入型態,就不會有這樣的問題
const App2: React.FunctionComponent = () => {
return <h1>hi function</h1>;
};
那這樣看起來很長,還可以簡寫為FC,這是一樣的東西
const App2: React.FC = () => {
return <h1>hi function</h1>;
};

※這邊是用TypeScript練習,所以副檔名是tsx
class Component
原本是為了讓組件上會有一些狀態的變化,所以之前剛開始的組件都是class寫法,直到React推出hooks的功能之後,function也能使用狀態,但是在看一些以前寫的程式碼,還是必須了解class的組件
原始的寫法是
class App extends React.Component {
render(): React.ReactNode {
return <h1>hi class</h1>;
}
}
那同樣裡面可以簡寫,不用放React.ReactNode
class App extends React.Component {
render() {
return <h1>hi class</h1>;
}
}

那現在大多都是以function Component ,只有以前寫的程式跟一些套件需要用到class,所以我們還是必須了解他的內容及他還有一些生命週期的問題
什麼是JSX
從組件中return的東西,很像是HTML不是HTML的東西就是JSX,是為了讓你在開發的時候,直覺的編寫,而不是用JS在寫這樣
JSX的寫法
const App: React.FC = () => {
return <h2>hi</h2>;
};
這是JSX原本被編輯後的樣子
const App: React.FC = () => {
return React.createElement('h2',{},'hi')
};
所以一個熟悉寫前端的人來說,肯定是看上面的更直觀更好寫

新增css的class是用className來新增,因為跟JS裡面的保留字class衝突到了,所以這邊的定義為className
App.tsx
import "./App.css";
const App: React.FC = () => {
return <h2 className="color-red">hi ,red</h2>;
};
App.css
.color-red {
color: red;
}

再來是路徑,我們的根目錄路徑開頭在public這個資料夾裡面,所以靜態資源都是丟在這邊
import "./App.css";
const App: React.FC = () => {
return (
<div>
<h1>標題</h1>
<p className="color-red">內容</p>
<img src="/logo512.png" alt="" />
</div>
);
};

再來一個特殊規則是,只能回傳一組標籤,標籤內有多少層都可以,但只能回傳一個標籤,如果傳很多個就會報錯
const App: React.FC = () => {
return (
//報錯
<h1>標題</h1>
<p className="color-red">內容</p>
<img src="/logo512.png" alt="" />
);
};

要解決這個問題可以使用Fragment
import "./App.css";
const App: React.FC = () => {
return (
<React.Fragment>
<h1>標題</h1>
<p className="color-red">內容</p>
<img src="/logo512.png" alt="" />
</React.Fragment>
);
};
這也可以簡寫使用<> </>,這樣空的代替
import "./App.css";
const App: React.FC = () => {
return (
<>
<h1>標題</h1>
<p className="color-red">內容</p>
<img src="/logo512.png" alt="" />
</>
);
};
如何匯入組件
像我們這些寫好之後export default出去之後,就可以被呼叫來使用,像剛剛寫的APP組件export出去之後,在入口頁面import進來就可以呼叫組件使用他
那個這<APP />這個寫法,反斜線在後面,只要寫一個就好,就不用像HTML標籤一樣要寫<> </>這樣成對的
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

組件匯入到組件內使用
,設置一個新的組件,直接把新的組件裝在要放入的位置,就可以使用了,這樣就形成了父子組件的關係
先設置檔案
-app.tsx
– components
– logo.tsx
logo.tsx
import React from "react";
const Logo = () => {
return (
<>
<h1>這是logo.tsx</h1>
<img src="/logo192.png" alt="" />;
</>
);
};
export default Logo;
app.tsx
import React from "react";
import Logo from "./components/logo";
const App: React.FC = () => {
return (
<>
<h1>這是App.tsx</h1>
<Logo />
</>
);
};
export default App;

而且組件可以重複使用,統一變更內容,相當方便

ReactDOM.render
這是一個React的渲染機制,要理解這本身背後的運作原理,可以在布魯斯的 TypeScript + React 全攻略|快速上手仿 Instagram UI這堂課中學到超級詳細的概念,包含他整個從使用者透過http到sever端,獲得回傳後整個詳細的運作都可以在這堂課中獲得這個知識,光ReactDOM.render這個解說,就值回整個課程的費用了,那這邊我們先理解為,將我們所寫的JSX解析之後渲染到空白的HTML畫面上即可
使用props,state
props就是當父組件,有需要跟子組件同步內容的時候,就可以使用props的功能,像是傳一個參數的效果一樣,傳送到子組件
state是一種賦予組件的方法,當組件內設定的變數更新之後,整個內容會再被重新渲染一次,讓畫面有更新的作用
那分為function跟class的寫法,兩種寫法達成的效果差不多,但寫法注意的過程差很多
function的寫法
首先props的傳送,建立一個app組件當父組件,跟一個Btn組件為子組件,我們先將一個數字變數從父組件傳送到子組件
- 透過在父組件程式裡的<Btn />將num傳送Btn組件內的參數
- 因為TS要寫型態的關係,所以在Btn的組件加入泛型,設定為AppNum為number型態
- 要在JSX裡面新增變數,就需要{},將變數包覆住,否則就會變成 ”現在數字是:AppNum”,而非出現設定數字
透過這樣的設定,只要更新畫面,就可以讓數字隨著設定改變,兩個組件內的數字都會一起改變
import React from "react";
type BtnType = {
AppNum: number;
};
const Btn: React.FC<BtnType> = ({ AppNum }) => {
return (
<>
<button>現在數字是: {AppNum}</button>
</>
);
};
const App: React.FC = () => {
let Num:number = 0;
return (
<>
<h1>現在數字是:{Num} </h1>
<Btn AppNum={Num} />
</>
);
};
export default App;

而且不只能夠傳變數,還可以傳入function
- 設定ChangeNum在父組件,透過props傳入子組件
- 子組件的型態要新增clickNum 的function的型態
- 並且加入到JSX裡面
- onclick為寫小駝峰式,也就是開頭小寫,後面單字為大寫開頭onClick
但是這樣的傳入,會發現不會更新畫面,只會更新裡面的數字,是因為單純更新變數,不足以觸發畫面重新渲染,這時候就要使用state
import React from "react";
type BtnType = {
AppNum: number;
clickNum: () => void;
};
const Btn: React.FC<BtnType> = ({ AppNum, clickNum }) => {
return (
<>
<button onClick={clickNum}>現在數字是: {AppNum}</button>
</>
);
};
const App: React.FC = () => {
let Num: number = 0;
function ChangeNum() {
Num++;
console.log(Num);
}
return (
<>
<h1>現在數字是:{Num} </h1>
<Btn AppNum={Num} clickNum={ChangeNum} />
</>
);
};
export default App;
再來設定state,如果我們只是單純變動程式碼的Num,是不會變動畫面上數字,必須存檔重新整理才可能變動,但加入state就可以綁定變數,變更後就會觸發渲染
- 先將useState從react裡面import進來
- 接下來改變num的宣告
- function的部分改為setNum(Num + 1);
這樣點擊的時候,就會因為變數改變,而觸發渲染。
宣告的時候[ 使用的變數,變更變數的函式],這邊就會直接設定好兩個東西,一般來說,命名會是第一個變數前加上set,useState()就是設定為State初始為0。
當要改變變數的時候,就setNum()來填入新的值
import React, { useState } from "react";
type BtnType = {
AppNum: number;
clickNum: () => void;
};
const Btn: React.FC<BtnType> = ({ AppNum, clickNum }) => {
return (
<>
<button onClick={clickNum}>現在數字是: {AppNum}</button>
</>
);
};
const App: React.FC = () => {
const [Num, setNum] = useState(0);
function ChangeNum() {
setNum(Num + 1);
}
return (
<>
<h1>現在數字是:{Num} </h1>
<Btn AppNum={Num} clickNum={ChangeNum} />
</>
);
};
export default App;
class的寫法
相對function來說,class寫法的學習成本非常高,但還是有許多早期設定好的套件,程式,都是用class寫法,而且還有生命週期的部分要注意到
class組件props
那已經有props跟state的概念之後,我們直接帶入一個內容porps跟state在class寫法的起手步驟
- 先設定兩個基本的class組件,繼承React.Component跟render()寫好
- 設定兩個組件的props跟state的型態,並加入到泛型中
- constructor建構props,並帶入剛寫的型態,而且有繼承,所以要super()
- 傳送props的時候,要設定this.因為function寫在class裡面
- 接收props的時候,要先this. props來接收
import React from "react";
type BtnState = {};
type BtnProps = {
clickNum: () => void;
};
class Btn extends React.Component<BtnProps, BtnState> {
constructor(props: BtnProps) {
super(props);
}
render() {
return (
<>
<button onClick={this.props.clickNum}>現在數字:</button>
</>
);
}
}
type AppState = {};
type AppProps = {};
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
}
ChangeNum() {}
render() {
return (
<>
<h1>現在數字:</h1>
<Btn clickNum={this.ChangeNum} />
</>
);
}
}
export default App;
class組件生命週期
class寫法有賦予執行的當下可以命令的方法,幾個重要的,創建時,更新時,被刪除前,這些地方都可以賦予方法,執行一些動作
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
}
ChangeNum() {}
//建立之時
componentDidMount() {}
//被更新時
componentDidUpdate() {}
//將要被刪除時
componentWillUnmount() {}
render() {
return (
<>
<h1>現在數字:</h1>
<Btn clickNum={this.ChangeNum} />
</>
);
}
}
class組件state
接下來,是將state加入到class組件中,因為已經繼承React.Component就可以直接this.state去設定,
- 我要設定一個num的state
- 這邊我們給他的型態是 {num:number}
- 使用的部分就是this.state.num來呼叫變數
- 修改的部分,使用修改this.setState( ( )=>{}),參數就會拿到state的上一個狀態,回傳為改變的新內容
但即使設定好之後,還是會報錯,因為我們把function透過props傳到Btn組件上,那麼裡面的this就會指向window,丟給他之後,自然找不到組件的state
import React from "react";
type BtnState = {};
type BtnProps = {
clickNum: () => void;
};
class Btn extends React.Component<BtnProps, BtnState> {
constructor(props: BtnProps) {
super(props);
}
render() {
return (
<>
<button onClick={this.props.clickNum}>現在數字:</button>
</>
);
}
}
type AppState = {
num: number;
};
type AppProps = {};
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {
num: 0,
};
}
ChangeNum() {
this.setState((prevState) => {
return {
num: prevState.num + 1,
};
});
}
//建立之時
componentDidMount() {}
//被更新時
componentDidUpdate() {}
//將要被刪除時
componentWillUnmount() {}
render() {
return (
<>
<h1>現在數字:{this.state.num}</h1>
<Btn clickNum={this.ChangeNum} />
</>
);
}
}
export default App;

接下來就要綁定this的位置,在constructor裡將this.ChangeNum永遠指向app
class App extends React.Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props);
this.state = {
num: 0,
};
this.ChangeNum = this.ChangeNum.bind(this);
}
ChangeNum() {
this.setState((prevState) => {
return {
num: prevState.num + 1,
};
});
}
//建立之時
componentDidMount() {}
//被更新時
componentDidUpdate() {}
//將要被刪除時
componentWillUnmount() {}
render() {
return (
<>
<h1>現在數字:{this.state.num}</h1>
<Btn clickNum={this.ChangeNum} />
</>
);
}
}
CSS加入JS的寫法
那還可以把CSS加入JS進去寫,直接設定一個物件放CSS的屬性進去,然後放入JSX的style裡面,可以呈現樣式了
import React from "react";
const blue = {
color: "blue",
};
const App = () => {
return (
<>
<h1 style={blue}>CSS加入JS</h1>
</>
);
};
export default App;

好處就是可以在裡面直接寫程式碼,這邊再新增一個紅的,當設定為true就是藍的,false就是紅的
import React from "react";
const blue = {
color: "blue",
};
const red = {
color: "red",
};
const App = () => {
const set: boolean = false;
return (
<>
<h1 style={set ? blue : red}>CSS加入JS</h1>
</>
);
};
export default App;
styled-components
這是一個非常實用的套件,可以直接形成一個帶有樣式的組件,先到官網,裡面有安裝流程
輸入指令,因為是typeScript所以也要安裝type
npm install --save styled-components @types/styled-components

並且在VScode安裝他的套件,可以幫助看一些樣式跟智能選字

具體用法是
- 將styled-components給import進來
- 設定一個變數直接等於styled.你要的標籤,後面“就可以裝你要的CSS樣式,這邊設置好,就是一個組件
- 直接匯入組件就可以使用
import React from "react";
import styled from "styled-components";
const Button = styled.button`
background: transparent;
border-radius: 3px;
border: 2px solid palevioletred;
color: palevioletred;
margin: 0 1em;
padding: 0.25em 1em;
`;
const App = () => {
const set: boolean = false;
return (
<>
<Button>styled-components</Button>
</>
);
};
export default App;

因為是組件,所以傳入props來使用
- 傳入一個設定顏色的變數
- 在styled.button後面加入型態
- 並在要使用的地方用function去設定自己要的邏輯
import React from "react";
import styled from "styled-components";
const Button = styled.button<{ colorSet: boolean }>`
background: transparent;
border-radius: 3px;
border: 2px solid palevioletred;
color: ${(props) => (props.colorSet === true ? "blue" : "palevioletred")};
margin: 0 1em;
padding: 0.25em 1em;
`;
const App = () => {
const set: boolean = true;
return (
<>
<Button colorSet={set}>styled-components</Button>
</>
);
};
export default App;

結論
今天學習的是布魯斯的 TypeScript + React 全攻略|快速上手仿 Instagram UI,這次主要是將之前學過的React組件的觀念,重新學習一次,而且之前並沒有學到class的寫法,藉由這堂課布魯斯還比對了兩者的差異,加深學習的印象,由於布魯斯實務上也有使用過React,透過這堂課也快速了解到真實專案中,主流的寫法是什麼方式,最讓我印象深刻的是,布魯斯對一套語言跟框架的理解,不單單只是學會使用,更會介紹到底層的運作邏輯,讓你避免將來遇到不知所措的坑,我覺得不管是新手或是老手都值得學習
