【React學習系列】學習React組件,讓你的UI分離,把HTML跟CSS都寫進JS裡

什麼是組件(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,透過這堂課也快速了解到真實專案中,主流的寫法是什麼方式,最讓我印象深刻的是,布魯斯對一套語言跟框架的理解,不單單只是學會使用,更會介紹到底層的運作邏輯,讓你避免將來遇到不知所措的坑,我覺得不管是新手或是老手都值得學習

布魯斯的 TypeScript + React 全攻略|快速上手仿 Instagram UI
布魯斯的 TypeScript + Re

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。