react

react是啥

渲染使用者介面

React 是用來實作使用者介面的 JavaScript 函式庫(by 官網)

使用 JSX 語法來渲染前端網頁

安裝

注意版本要是 LTS

建立專案

新增資料夾

開一個 terminal

輸入指令

npm create vite@latest 專案名稱 -- --template react

專案名稱記得改成你要的名稱

Ctrl + 左鍵點擊那個藍色連結,就可以開網站了

關閉與開啟網站

關閉:終端機打 ctrl + c

開啟:終端機輸入 npm run dev

注意,輸入前要先確定終端機顯示的路徑正確

路徑的名稱要是你的專案名稱

不然會炸

JSX

html

JSX 讓你能用類似 HTML 的語法來寫網頁

編譯器最後會把這一坨 HTML 轉成 JS

阿為甚麼不直接用 JS 就好

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}
function App() {
  const [count, setCount] = useState(0);

  return React.createElement(
    React.Fragment,
    null,
    React.createElement(
      "div",
      null,
      React.createElement(
        "a",
        { href: "https://vite.dev", target: "_blank" },
        React.createElement("img", {
          src: viteLogo,
          className: "logo",
          alt: "Vite logo"
        })
      ),
      React.createElement(
        "a",
        { href: "https://react.dev", target: "_blank" },
        React.createElement("img", {
          src: reactLogo,
          className: "logo react",
          alt: "React logo"
        })
      )
    ),
    React.createElement(
      "h1",
      null,
      "Vite + React"
    ),
    React.createElement(
      "div",
      { className: "card" },
      React.createElement(
        "button",
        { onClick: () => setCount((count) => count + 1) },
        "count is ",
        count
      ),
      React.createElement(
        "p",
        null,
        "Edit ",
        React.createElement("code", null, "src/App.jsx"),
        " and save to test HMR"
      )
    ),
    React.createElement(
      "p",
      { className: "read-the-docs" },
      "Click on the Vite and React logos to learn more"
    )
  );
}

很明顯 JSX 的易讀性較高,碼量也小

注意

HTML裡面有些標籤是自關閉的,像 <br>

 JSX 裡面這些自關閉的標籤要加上斜線

<br />

檔案架構

寫程式

回到剛剛建好的資料夾

找到 src 這個資料夾

找到 main.jsx 這個檔案

他不是主要寫程式的地方但先看一下裡面的結構

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

main.jsx

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

其中的 <App /> 就是 main.jsx 呼叫 App.jsx 來渲染網頁

好所以來看看 App.jsx 裡面有甚麼

app.jsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
        <a href="https://vite.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.jsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  )
}

export default App

這個就是你所看到的網頁

然後你也可以看到他引入了 App.css

也可以看一下

他就是個正常的 css 檔

strict mode

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

在你之後做成發專案的時候

如果你發現你的一些程式(像 console.log())跑了兩次

那是因為 strictmode 會導致後面提到的 useEffect 執行兩次

阿 strict mode 其實可以拔掉

component

積木

重複利用

你可以把 component 想像成一塊積木

你先在其他地方把一個 component 寫好

然後回到主程式把他 import 進來

現在你就可以把 component 放在好幾個地方

範例

在 src 裡面新增一個資料夾 component

裡面開一個 .jsx 檔

輸入以下程式

function Todo(props) {
  return (
    <div className="card">
      <h2>
        {props.title}
      </h2>
      <div className="actions">
        <button className="btn">Delete</button>
      </div>
    </div>
  );
}
export default Todo;

範例

回到 App.jsx

import 剛剛新增的 .jsx 檔(用相對路徑)

把 component 放到 function 裡面

import Todo from './component/Todo';
import "./App.css"
function App() {
  return (
    <div>
      <h1>My Todos</h1>
      <Todo title={"No1"} />
      <Todo title={"No2"} />
      <Todo title={"No3"} />
    </div>
  );
}
export default App;

範例

你就得到了這個介面

講解

function Todo(props) {
  return (
    <div className="card">
      <h2>
        {props.title}
      </h2>
      <div className="actions">
        <button className="btn">Delete</button>
      </div>
    </div>
  );
}
export default Todo;

你會發現component 的h2 裡面包了一個 {props.title}

而 App.jsx 裡面在使用 Todo 時給了 title 一個值

這個 props.title 其實就是讓你可以更改的值

而最後這個 "No1" 就會跑到 h2 裡面變成小標題

切記 component 最後要 export,不然網頁會爆炸

import Todo from './component/Todo';
import "./App.css"
function App() {
  return (
    <div>
      <h1>My Todos</h1>
      <Todo title={"No1"} />
      <Todo title={"No2"} />
      <Todo title={"No3"} />
    </div>
  );
}
export default App;

props

剛剛的 props 傳遞的是 string

他其實可以傳遞各種東西

甚至可以傳遞函式

展開

如果不用 component 的話,App.jsx 會長這樣

會有很多重複的地方

import "./App.css"
function App() {
  return (
    <div>
      <h1>My Todos</h1>
      <div className="card">
        <h2>
          No1
        </h2>
        <div className="actions">
          <button className="btn">Delete</button>
        </div>
      </div>
      <div className="card">
        <h2>
          No2
        </h2>
        <div className="actions">
          <button className="btn">Delete</button>
        </div>
      </div>
      <div className="card">
        <h2>
          No3
        </h2>
        <div className="actions">
          <button className="btn">Delete</button>
        </div>
      </div>
    </div>
  );
}
export default App;

實作

現在只有一個 title 太少了

給他加多一點東西

例如說截止日期、科目等等

可以嘗試用 css 美化

如果發現網頁甚麼東西都沒有,通常代表有報錯

開 F12 打開 console 就能看到問題

use state

做個計數器

我不知道阿

let count = 0;
function add() {
  count = count + 1;
  console.log(count);
}

執行此程式後, console.log 出來的值當然是 1

但是如果你要用網頁顯示這個變數的話,他還是0

所以 use state 的功用就是叫 react 重新渲染一下畫面

這樣才能看到最新的值,也就是 1

語法

import { useState } from 'react';
const [變數名稱, 修改變數的函式] = useState(初始值);
const [count, setCount] = useState(0);

初始化

更改數值

setCount(1);

請注意這樣網頁不會重新渲染

count = 1;

onclick

這其實是 HTML 自己就有的語法

但 JSX 長得不太ㄧ樣

<button onClick = {changeColor}>Change to Blue</button>

當這個按鈕被點擊的時候, changeColor 這個函式就會啟動

注意

這兩行程式差在哪裡呢?

<button onClick = {changeColor()}>Change to Blue</button>
<button onClick = {() => changeColor()}>Change to Blue</button>

第一行的函式,是在渲染時就會先叫一次

而第二行的函式,是一個待執行的函式,渲染時不會呼叫

實作

做一個計數器,包含三個按鈕

+1, -1, 歸零

把上個實作的 delete 加個 Onclick 事件

map

不是 C++ 的那個 map

更長的代辦清單

map 可以方便地將一個 list 裡面的值一個個渲染出來

import Todo from './component/Todo';
import "./App.css"
function App() {
  return (
    <div>
      <h1>My Todos</h1>
      <Todo title={"No1"} />
      <Todo title={"No2"} />
      <Todo title={"No3"} />
    </div>
  );
}
export default App;

例如說我想要 10 組 todo

你當然可以一個一個打,但這樣有點笨

創一個 list

這邊的語法跟 JS 基本上是一樣的

let arrLists = []
for (let i = 0 ; i < 10 ; i++) {
    arrLists.push("No." + i.toString())
}

map語法

{list.map((item, index) => {
  return 另一種格式;
})}

以下是轉成 Todo 格式的範例

{arrLists.map((item, index) => {
        return <Todo key = {index} title={item} />;
})}

key 的存在不只是為了正確渲染元件

他還能搭配 diffing演算法極大福減少渲染時確認資料有無變動並替換時所需的時間

不過如果你不加的話也會報錯就是了

完整 code

import Todo from './component/Todo';
import "./App.css"

function App() {
  let arrLists = []
  for (let i = 0 ; i < 10 ; i++) {
    arrLists.push("No." + i.toString())
  }
  return (
    <div>
      <h1>My Todos</h1>
      {arrLists.map((item, index) => {
        return <Todo key = {index} title={item} />;
      })}
    </div>
  );
}
export default App;

一次 10 個代辦事項

嗚嗚嗚事情好多要似了

use effect

infinite loop

每次渲染時就啟動

在 use effect 中的程式,會在每次元件渲染時啟動

import { useState, useEffect } from 'react';
useEffect (() => {
    //程式碼
}, [])

範例

例如說我可以在每次重新開啟網頁時,覆蓋 list 的內容

阿當然畫面不會有東西,因為我沒用 use state

import { useState, useEffect } from 'react';
import Todo from './component/Todo';
import "./App.css";

let todo = []
function App() {
  useEffect (() => {
    todo = []
    for (let i = 1 ; i <= 10 ; i++) {
        todo.push({id : i, title : "No" + i.toString()})
    }
  })
  return (
    <div>
      <h1>My Todos</h1>
      {todo.map((todo) => (
        <Todo 
          key={todo.id} 
          title={todo.title} 
        />
      ))}
    </div>
  );
}

export default App;

無限迴圈

我現在把它變成 use state 的樣子

import { useState, useEffect } from 'react';
import Todo from './component/Todo';
import "./App.css";


function App() {
  const [todos, setTodos] = useState([
  ]);

  useEffect (() => {
      let list = []
      for (let i = 1 ; i <= 10 ; i++) {
          list.push({id : i, title : "No" + i.toString()})
      }
      setTodos(list)
  })
  return (
    <div>
      <h1>My Todos</h1>
      {todos.map((todo) => (
        <Todo 
          key={todo.id} 
          title={todo.title} 
        />
      ))}
    </div>
  );
}

export default App;

好像沒什麼問題?

打開 console 看看

無限迴圈

怎麼爆炸了

因為你在 useEffect更新陣列後

畫面就會渲染一次

然後又會觸發 useEffect

形成無限迴圈

怎麼辦?

依賴陣列

import { useState, useEffect } from 'react';
useEffect (() => {
    //程式碼
}, [])

你有看到一個中括號包起來的空陣列嗎

那個就是依賴陣列

當這個依賴陣列沒有產生變動的時候 useEffect 就不會啟動

問題不大

import { useState, useEffect } from 'react';
import Todo from './component/Todo';
import "./App.css";


function App() {
  const [todos, setTodos] = useState([
  ]);

  useEffect (() => {
      let list = []
      for (let i = 1 ; i <= 10 ; i++) {
          list.push({id : i, title : "No" + i.toString()})
      }
      setTodos(list)
  }, [])
  return (
    <div>
      <h1>My Todos</h1>
      {todos.map((todo) => (
        <Todo 
          key={todo.id} 
          title={todo.title} 
        />
      ))}
    </div>
  );
}

export default App;

所以就加個 [] 就好了

通靈實作

現在把 delete 按鈕加上刪除功能

這樣能根據 id 刪除指定的值

setTodos(todos.filter((todo) => todo.id !== id));

tip1: 這次實作會動到 component 喔

tip2: Todos 在更新後整個 App 函式會重跑一遍

同時 useEffect 不會動(因為依賴陣列沒動)

通靈實作

// component/Todo.js
function Todo(props) {
  return (
    <div className="card">
      <h2>{props.title}</h2>
      <div className="actions">
        <button className="btn" onClick={props.onDelete}>
          Delete
        </button>
      </div>
    </div>
  );
}

export default Todo;
//App.jsx
import { useState, useEffect } from 'react';
import Todo from './component/Todo';
import "./App.css";


function App() {
  const [todos, setTodos] = useState([
  ]);

  function deleteHandler(id) {
    setTodos(todos.filter((todo) => todo.id !== id));
  }
  useEffect (() => {
      let list = []
      for (let i = 1 ; i <= 10 ; i++) {
          list.push({id : i, title : "No" + i.toString()})
      }
      setTodos(list)
  }, [])
  return (
    <div>
      <h1>My Todos</h1>
      {todos.map((todo) => (
        <Todo 
          key={todo.id} 
          title={todo.title} 
          onDelete={() => deleteHandler(todo.id)}
        />
      ))}
    </div>
  );
}

export default App;

要在 onClick 上加一個 props

props 裡面也可以塞函式

提醒

return (
    <div>
      <h1>My Todos</h1>
      {todos.map((todo) => (
        <Todo 
          key={todo.id} 
          title={todo.title} 
          onDelete={() => deleteHandler(todo.id)}
        />
      ))}
    </div>
  );

注意到 onDelete 裡面的函式,前面還放了一個 () =>

如果不放的話會發生甚麼事呢?

這個函式會在網頁開啟時就啟動一次

然後把所有的東西刪除

github

pushpushpushpushpush

前置作業

創建 github 帳號並 create repository

不用勾選 Add a README file 或 .gitignore,因為你已經有了

複製 repository 的網址

前置作業

終端機輸入以下指令

npm install gh-pages --save-dev

更改 vite.config.js

export default defineConfig({
  plugins: [react()],
})
export default defineConfig({
  plugins: [react()],
  base: "/儲存庫名稱/"
})

更改 package.json

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
"scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  	"predeploy": "npm run build",
 	"deploy": "gh-pages -d dist"
},

並在最上面添加這串

"homepage": "https://你的GitHub帳號.github.io/你的專案名稱",

git

初始化

git init
git add .
git commit -m "Initial commit"

加入檔案並提交版本(你會看到專案裡已經有 .gitignore 了所以不用擔心)

連線到 repository

git remote add origin https://github.com/你的帳號/儲存庫名稱.git

更改分支名稱,然後把程式碼丟上去

git branch -M main
git push -u origin main

git

初始化

git init
git add .
git commit -m "Initial commit"

加入檔案並提交版本(你會看到專案裡已經有 .gitignore 了所以不用擔心)

連線到 repository

git remote add origin https://github.com/你的帳號/儲存庫名稱.git

更改分支名稱,然後把程式碼丟上去

git branch -M main
git push -u origin main

啟動

在 terminal 執行

npm run deploy

輸入以下網址,應該就可以看到你的網頁了

https://你的GitHub用戶名.github.io/儲存庫名

成發小提醒

舊版本問題

今天講得東西......其實不多,所以你在做成發時理論上需要上網找資料

但請注意,react 有新舊版本之分,有些舊版本的東西拿到現在用會爆炸

講師上次寒訓被這個東西搞

所以如果你發現你上網找的程式是爛的

不一定是你的問題

也不一定是他的問題

可能是版本的問題

記得問問題

然後有問題記得問講師

或著問 AI

丸啦 AI 要取代講師了

Minimal

By ck11300768鄭博軒