來學 React 吧之一_以 todo list 為例學會 React 基礎與 useState 介紹


Posted by Christy on 2021-11-01

本文為 Lidemy [FE302] 直到「中場休息時間」課程影片的讀書筆記,分為 React 最重要概念、現成套件建置 React 環境、實作一個 todo list 學習 React 基礎

下面五點是這裡要學的內容及練習題,主要從基礎看起,知道 React 是什麼,裡面有什麼東西,怎麼用它來實作 todo list;目前最好的練習紀錄是 15 分鐘,從引入做好的 <TodoItem /> component 開始從頭做起,實作新增、刪除、完成/未完成

一、React 最重要概念:component & state

二、現成套件建置 react 環境:在電腦端建立環境、怎麼看一個新專案

三、React 基礎

  1. 環境設置:認識檔案內容、建立一個方便練習的環境

  2. 練習 component & style 基礎

  3. 練習 component & style 進階

  4. state 介紹:利用 counter 來理解 state

  5. useState 實作新增、刪除、完成/未完成按鈕的 todo list,實作內容紀錄了遇到的問題及犯的錯誤

  6. 中場總結:React 重要的概念是

    a. Component: 自定義的 component,刻畫面好方便

    b. Props: 自定義的屬性,傳進 component,更好操作

    c. style: styled-component 類似 sass 寫法,加速開發

    d. Event handler: React 裡面怎麼處理事件,直覺式的寫法,快又方便

    e. JSX: 把 JavaScript 套用在 html 元素上面,怎麼用

    f. State: 產生一個新的 state,state is immutable

四、重要概念補充資料

閱讀這一系列文章的筆記 從 Hooks 開始,讓你的網頁 React 起來

五、React 練習題整理,影片都在 [FE302]「React 基礎」裡

  1. 環境建置:create-react-app

    a. 07'40" 練習 component

    b. 10'43" style 的三種寫法

  2. 初探 React 中的 style

    a. 09'03" 切 todo 的版面

  3. 初探 state

    a. 00'46" - 03'30" 利用 counter 理解 state,當點擊 counter 按鈕時,counter: 0 數字自動加一

    b. 05'51": 當點擊按鈕時,新增一個 todo(在 React 裡面,要 render 一系列相關的資料時會用 map()

  4. 再探 state 之新增 todo

    把輸入框的內容拿出來

    a. 05'29" 用 DOM 元素

    b. 05'32" 用 useRef

    c. 07'41" 用 useState 當點擊按鈕時,拿到輸入框的內容,並新增一個 todo

    d. 8'52" 新增 todo 時也新增 id

  5. 加上編輯 todo 功能:todo 已完成 / 未完成功能


一、React 最重要的概念

1. component,像是把畫面分成一個個元素,如同零組件一樣。Web Components

2. 畫面永遠由 state 產生 UI = f(state),簡單來說就是先有資料再產生畫面。

二、怎麼用現成的套件建置 react 環境

前言:實際開發不會用現成的環境,不過在「重現一個環境」,可以用 codesandbox 快速建一個,方便提問 debug。

1. 在本地端練習,用套件建一個環境

facebook/create-react-app 

在想要建環境的資料夾下,輸入三個指令:

$ npx create-react-app my-app
$ cd my-app
$ npm start

看到以下訊息就是成功了:

Compiled successfully!

You can now view my-app in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.1.2:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

先 ctrl c 跳出來,看一下版本號

$ npx create-react-app -V 確認版本號:4.0.3

2. 要怎麼跑 create-react-app

$ cd 到指定資料夾,然後 $ npm run start,瀏覽器就會自動連到 http://localhost:3000/,這個就是 React 幫你 render 的畫面

3. 怎麼看一個新的專案

小記:現在才發現,把一整個資料夾丟進 vs code 會自動幫你分層分好,找資料好方便喔

a. 看 README

裡面有說,可以跑下面幾個指令:

yarn start = npm run start
yarn test
yarn build
yarn eject 用了無法回頭重來喔

b. 看 package.json

稍微看一下 dependencies、scripts


三、開始寫 React

1. 基礎環境設置

a. 建議用 vs code 開檔案比較方便

b. 修改一下 app.js、index.js

c. 到 src 資料夾底下,打開上述兩個檔案

第一個看到的是 Strict Mode

建議先把嚴格模式拿掉,有時為了偵測,console.log 的結果會跟你想的不一樣

// index.js
ReactDOM.render(
  <React.StrictMode> // 這個標籤拿掉
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

拿掉就變這樣了:

// index.js
ReactDOM.render(
    <App />,
  document.getElementById('root')
);

另外在 app.js,也會把標籤裡面的東西先刪掉,只剩下面那樣:

// app.js
function App() {
  return (
    <div className="App">

    </div>
  );
}

2. 練習 component & style

影片在React 基礎 > 環境建置:create-react-app

a. 07'40 練習 component

a.1 component 把它想成是一個函式,函式名稱都是大寫開頭

a.2 component 裡面的 return 後面一定要跟著東西,或用小括號 () 包起來

function Title() {
  return (
    <h1>hello</h1>
  )
}

a.3 在 JSX 語法相關的,都要用大括號 {} 包起來

function Description (props) {
  return (
    <p>
      {props.children}
    </p>
  )
}

a.4 style 怎麼寫,常見有三種寫法 10’43”

a.5 可以像寫 inline style 那樣:

class MyHeader extends React.Component {
  render() {
    return (
      <div>
      <h1 style={{color: "red"}}>Hello Style!</h1>
      <p>Add a little style!</p>
      </div>
    );
  }
}

a.6 或者把 style 當參數(mystyle)傳進去

值得特別注意的是,這裡會用 Camel Case,像是"textAlign"、"fontFamily" 這種寫法

class MyHeader extends React.Component {
  render() {
    const mystyle = {
      color: "white",
      backgroundColor: "DodgerBlue",
      padding: "10px",
      fontFamily: "Arial"
    };
    return (
      <div>
      <h1 style={mystyle}>Hello Style!</h1>
      <p>Add a little style!</p>
      </div>
    );
  }
}

不過這種方法不能寫「 hover or 偽元素」

範例來自 Styling React Using CSS

b. 用 css + class name

b.1 class 是保留字,所以要用的時候,可以用 className

b.2 可在 App.css 裡寫 style,就跟以前一樣,記得匯入檔案

import './App.css';

function App() {
  return (
    <div className="App">
      <Title size="M"/>
      <Description>
        哈囉你好
      </Description>  
    </div>
  );
}

css 檔案也可以換成 Sass 那種,只是要去改 webpack 設定

b.3 最推薦寫 style 的方法是用這個套件 styled-components用法

b.3.1 安裝 Installation

b.3.2 import styled from "styled-components";

b.3.3 最後用 Backticks ( tagged template literals)包起來,裡面就可以寫一般的 css

// 要記得 import
import styled from "styled-components"

const Title = styled.h1`
  font-size: 100px;
  color: blue;
`
function App() {
  return (
    <div className="App">
      <Title>
        哈囉你好
      </Title>
    </div>
  );
}

3. style 進階用法

a. 初探 React 中的 style: 09'03" 切 todo 的版面

b. styled component 實戰: 常用用法

c. style、fn component 的重用

d. RWD: 通常會在 src 資料夾底下,開一個 constants 的資料夾,裡面可以放 breakpoint.js,把 media query 放裡面

e. theme 06'55":類似主題的感覺,方便套用

f. JSX 其實就是 React 背後幫你轉換語言;會自動防止 XSS 攻擊;但是要特別注意  <a href={window.encodeURIComponent(輸入的東西)}></a>

4. React state 介紹

a. 實作一個 counter 來理解 useState 的運用:

前言:React 有提供 hook 給大家使用,在這裡用的是 useState 這個 hook(Using the State Hook

// 一定要先引入才能用 useState
import React from 'react'
// [data, action] = React.useState(初始值)
const [counter, setCounter] = React.useState(0)

一個 state 就是一個畫面,每次按按鈕的時候,就會重新渲染畫面一次

a.1 實作:影片「初探 state」00'46" - 03'30" 當點擊按鈕時,counter: 0 後面的數字自動加一

a.1.1 引入 react `import React from "react";`

a.1.2 先刻畫面

a.1.3 再做功能:數字加一、監聽按鈕

a.1.4 測試

a.2 遇到的問題

a.2.1 忘了引入 React

a.2.2 不可以寫成 counter ++,只能寫成 counter + 1

a.2.3 畫面會在 return 之後輸出

a.2.4 函式包在 fn App 裡面

// App.js code
import React from 'react'
function App() {
  const [counter, setCounter] = React.useState(0)
  const handleButtonClick = () => {
    setCounter(counter + 1)
    // 12/5 補充,其實用下面這種比較好
    setCounter(counter => counter + 1)
  }
  return (
    <div className="App">
      Counter: {counter}
      <button onClick={handleButtonClick}>increment</button>
    </div>
  );
}

關於 setCounter(counter => counter + 1)setCounter(counter + 1) 寫法更好,詳情可以參考 作業檢討 或者 關於 useState,你需要知道的事

a.2.5 出現錯誤:Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop

setCounter(counter + 1) 不能直接在 APP 裡面 render,要用一個函式包起來

5. 怎麼用 useState 新增 todo,基礎篇

前言:影片在 「初探 state」裡,在 React 裡面,要 render 一系列相關的資料時會用 map()

a. 05'51": 當點擊按鈕時,新增一個 todo

可以把每個 component 切開,分成不同檔案,並把 fn 匯出

這裡利用上一個影片 setCounter 的類似原理,看了 state 運作方式

這個練習比較不熟的地方是 .map() 的用法

map() 用法:列表與 Key

map((第一個參數是要放的東西) => <TodoItem content={todo} />)
// 把要放的東西,放進去後面的 component 裡面
import "./App.css";
import { useState } from "react";
import TodoItem from "./TodoItem";

function App() {
  const [todos, setTodo] = useState([123, 456]);
  const handleButtonClick = () => {
    setTodo([...todos, Math.random()]);
  };
  return (
    <div className="App">
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo, index) => (
        <TodoItem key={index} content={todo} />
      ))}
    </div>
  );
}

export default App;

6. 怎麼用 useState 新增 todo,這次我們來真的

前言:影片在「再探 state 之新增 todo」,其實這裡想要表達的重點就是「所有在 UI 上會動的東西,幾乎都是個 state」、「state is immutable 不可變的」

component 有兩種類型:controlled component, uncontrolled component,差別在於前者是把資料放到 state 裡面;後者沒有

我們可以偵測輸入框裡面的打字內容,每打一個字母就改變一個 state,接著再把內容拿出來(應該是為了解釋 controlled component 所以做的示範,但通常不會這樣做)

a. 要怎麼把輸入框的內容拿出來?(controlled component )

a.1 05'29" 用 DOM 元素的方法把輸入框的值拿出來(uncontrolled component )

// 先在新增 input 裡面新增一個 className="input-todo"
// 用 DOM 的方法拿出來
document.querySelector(".input-todo").value()

a.2 05'32": 用 React 裡面的 useRef

a.2.1: 先引入 import { useState, useRef } from "react";

a.2.2: 拿個變數來存值 const inputRef = useRef();

a.2.3: inputRef 會有個物件叫做 current,其 value 就是 input 裡面的值 console.log(inputRef.current.value);

// App.js code

import "./App.css";
import { useState, useRef } from "react";
import TodoItem from "./TodoItem";

function App() {
  const [todos, setTodos] = useState([123]);
  const [value, setValue] = useState("");
  const inputRef = useRef();

  const handleButtonClick = () => {
    console.log(inputRef.current.value);
    setTodos([...todos, 123]);
  };
  return (
    <div className="App">
      <input ref={inputRef} />
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo) => (
        <TodoItem content={todo} />
      ))}
    </div>
  );
}

export default App;

b. 影片在「再探 state 之新增 todo」07'41" 實作:當點擊按鈕時,拿到輸入框的內容,並新增一個 todo(uncontrolled component

最後老師選擇用 useState 取得輸入框的內容

import "./App.css";
import { useState } from "react";
import TodoItem from "./TodoItem";

function App() {
  const [todos, setTodos] = useState([123, 456]);
  const [value, setValue] = useState("");

  const handleButtonClick = () => {
    setTodos([value, ...todos]);
    setValue("");
  };

  const handleInputChange = (e) => {
    setValue(e.target.value);
  };

  return (
    <div className="App">
      <input
        value={value}
        type="text"
        placeholder="get things done"
        onChange={handleInputChange}
      />
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo, index) => (
        <TodoItem key={index} content={todo} />
      ))}
    </div>
  );
}

export default App;

b.1 小結,到目前為止,如果我要做「新增 todo 的功能」,該怎麼做呢?

b.1.1: 先刻畫面,被包成 component 的該引入的要引入

b.1.2: useState 讓 todo 跟 useState 連結

這裡的重點是要學會怎麼用 useState

// const [資料, 動作] = useState(一定要傳初始值)
const [todos, setTodos] = useState([123]);

b.1.3: 監聽按鈕點擊事件,先新增一列 todo

新增建議用 map

<button onClick={handleButtonClick}>Add todo</button>
{todos.map((todo) => (
  <TodoItem content={todo} />
))}

b.1.4: 規範行為,要記得用解構語法

const handleButtonClick = () => {
  setTodos([...todos, value]);
};

b.1.5 一樣的邏輯,把 value 給 input,用 state 做掛鉤,監聽 onChange 事件

這裡在實作的時候,常常忘記要用 e.target.value 才拿的到內容

const handleInputChange = (e) => {
  setValue(e.target.value);
};

c. 影片在「再探 state 之新增 todo」8'52" 新增 todo 時也新增 id

// App.js code
import "./App.css";
import { useState } from "react";
import TodoItem from "./TodoItem";

// 每次 render 就會重新呼叫一次 fn App(),所以如果把 let id = 2 放在函式裡面,
// 那 id 就會一直從 2 開始
let id = 2;
function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      content: "abc",
    },
  ]);

  const [value, setValue] = useState("");

  const handleButtonClick = () => {
    setTodos([
      {
        id,
        content: value,
      },
      ...todos,
    ]);
    setValue("");
    id++;
  };

  const handleInputChange = (e) => {
    setValue(e.target.value);
  };

  return (
    <div className="App">
      <input
        value={value}
        onChange={handleInputChange}
        type="text"
        placeholder="get things done"
      />
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

export default App;
// TodoItem.js code
import "./App.css";
import { MEDIA_QUERY_MD, MEDIA_QUERY_LG } from "./constants/breakpoint.js";
import styled from "styled-components";

const TodoItemWrapper = styled.div`
  display: flex;
  align-item: center;
  justify-content: space-between;
  padding: 8px 16px;
  border: 1px solid black;

  & + & {
    margin-top: 4px;
  }
`;

const TodoContent = styled.div`
  color: red;
  font-size: 12px;

  ${(props) =>
    props.size === "XL" &&
    `
    font-size: 20px;
  `}
`;

const TodoButtonWrapper = styled.div``;

const Button = styled.button`
  padding: 4px;
  color: black;
  font-size: 20px;

  ${MEDIA_QUERY_MD} {
    font-size: 16px;
  }

  ${MEDIA_QUERY_LG} {
    font-size: 20px;
  }

  &:hover {
    color: red;
  }

  & + & {
    margin-left: 4px;
  }
`;

const BlueButton = styled(Button)`
  color: red;
`;

export default function TodoItem({ className, size, todo }) {
  return (
    <TodoItemWrapper className={className} data-todo-id={todo.id}>
      <TodoContent size={size}>{todo.content}</TodoContent>
      <TodoButtonWrapper>
        <Button>已完成</Button>
        <BlueButton>刪除</BlueButton>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}

覺得還是沒有很好的「把功能切成最小的練習」,像是 todo id 這裡,就有點卡住。

c.1 另一個辦法是用影片「再探 state 之新增 todo」11'42“  useRef,概念有點像是可以當成 state 也可以直接操作,但是他不會改變,把它想成一個物件,要拿東西,就要去 current 裡面拿。

c.2 犯的錯誤:

c.2.1 新增時重複兩次

// key 要傳的是 todo 的 id,如果直接傳 id 那就會一直重複
{todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}

c.2.2 對於 map() 還是不夠熟悉,然後 todo 這個參數就是要傳的東西,跟 setTodos 之間的關係有點疑惑

在 React 裡面用表單有點麻煩,要自己處理那些事件

7. todo 刪除功能

a. 這裡看得超級驚奇,很反面思考耶,用 .filter() 把想要留的東西留下來,就是刪除功能。一般我的思考會是「把要刪掉的刪除,再重新給一個 state」之類的,這裡用了 .filter() 也印證了我對一般的內建函式不熟悉。

b. 當點擊刪除按鈕時,刪除該 todo list

犯的錯誤:

b.1 onClick 後面可以傳 cb 函式啊...

<BlueButton onClick={() => {handleDeleteTodo(todo.id);}}>刪除</BlueButton>

b.2 對 .filter() 不熟

const handleDeleteTodo = (id) => {
  setTodos(todos.filter((todo) => todo.id !== id));
};

b.3 不管是 .map().filter() 或是用 useState 之前的 const,都有一種異曲同工之妙,就是資料與行為之間的關係。

// todo 是要傳的資料,把這個資料放到 component 裡面是行為
{todos.map((todo) => (
  <TodoItem key={todo.id} todo={todo} />
))}
// todo 是要傳的資料,後面是行為
// .filter() 只有 true 才會留下來
const handleDeleteTodo = (id) => {
  setTodos(todos.filter((todo) => todo.id !== id));
};
// value 是資料,setValue 是行為
const [value, setValue] = useState("");

b.4 在這裡犯的錯誤是當按下刪除按鈕時,第三個 todo list 跟第二個會同時受到已完成/未完成影響。

原本以為是邏輯寫錯了,後來發現是因為用 let id = 2,如果原本預設值有兩個,應該要改成 let id = 3 才對。

b.5 被函式名稱搞亂了,看來對原理不太熟悉。一個很重要的概念就是要在父層做事,要把 props 傳到子層

比如說,我要執行 handleDeleteClick,那就要在父層 TodoItem 裡面傳 props,接著在子層按鈕上加上監聽。

而搞混的地方在於這裡不寫 inline fn,而是單獨把 fn 抽出來寫。其實就是為了可讀性,再包一層而已,原理是一樣的,想清楚下次就不會搞混了。

// App.js code
return (
  <div className="App">
    <input onChange={handleInputChange} type="text" value={value} />
    <button onClick={handleButtonClick}>Add todo</button>
    {todos.map((todo) => (
      // 在父層寫一個函式當作 props,並且傳到子層
      <TodoItem handleDeleteClick={handleDeleteClick} todo={todo} />
    ))}
  </div>
);
// TodoItem.js code
export default function TodoItem({ className, size, todo, handleDeleteClick }) {
  const handleDeleteTodo = () => {
    handleDeleteClick(todo.id);
  };
  return (
    <TodoItemWrapper className={className} data-todo-id={todo.id}>
      <TodoContent size={size}>{todo.content}</TodoContent>
      <TodoButtonWrapper>
        <Button>已完成</Button>
        // 在子層按鈕上加上監聽
        // 這裡不寫 inline fn,而是單獨把 fn 抽出來。
        <BlueButton onClick={handleDeleteTodo}>刪除</BlueButton>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}

b.6 蠻多函式用法還是不熟,尤其是「要傳什麼參數」,這又反應到了「其實對於這個函式要做什麼事不夠熟悉」。

知道要實現什麼功能 → 運作過程(就是這裡還不熟練,尤其是運作過程中的每一個元素,例如要實現刪除功能,要用 xx 函式;而這個函式裡面,要傳的參數是 oo, xx,在這裡需要 id,才能夠執行某一項功能…)

b.7 透過跟自己解釋,終於搞懂了。

所以說 todo.idid,前者是獨一無二,只存在 todo 這個物件裡面,當要刪除某一列 todo 時,必須找到 todo.id,才能知道要刪哪一個。

接著用 .filter() 去做比對,重點在於它是「true 成立」,所以寫成 todos.filter((todo) => todo.id !== id) 就是指說「todo.id 跟 id 不一樣的留下來」這件事為 true,藉此產生一個新的 state。

// App.js code

import "./App.css";
import { useState } from "react";
import TodoItem from "./TodoItem";

let id = 2;
function App() {
  const [todos, setTodos] = useState([{ id: 1, content: 123 }]);
  const [value, setValue] = useState("");

  const handleButtonClick = () => {
    setTodos([...todos, { id, content: value }]);
    setValue("");
    id++;
  };

  const handleInputChange = (e) => {
    setValue(e.target.value);
  };

  const handleDeleteTodo = (id) => {
    setTodos(todos.filter((todo) => todo.id !== id));
  };

  return (
    <div className="App">
      <input value={value} onChange={handleInputChange} />
      <button onClick={handleButtonClick}>Add todo</button>
      {todos.map((todo) => (
        <TodoItem todo={todo} handleDeleteTodo={handleDeleteTodo} />
      ))}
    </div>
  );
}

export default App;
// TodoItem.js code

export default function TodoItem({ className, size, todo, handleDeleteTodo }) {
  return (
    <TodoItemWrapper className={className} data-todo-id={todo.id}>
      <TodoContent size={size}>{todo.content}</TodoContent>
      <TodoButtonWrapper>
        <Button>已完成</Button>
        <BlueButton
          onClick={() => {
            handleDeleteTodo(todo.id);
          }}
        >
          刪除
        </BlueButton>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  );
}

8. todo 已完成 / 未完成功能

影片 React 基礎 > 加上編輯 todo 功能

a. 只想傳 props 做為 style 用的話,可以加上 $ 在 props 名稱前面,就不會被傳到 DOM 元素上面,例如:

<TodoContent $isDone={todo.isDone} size={size}>

React 範例:

Transient props v5.1: If you want to prevent props meant to be consumed by styled components from being passed to the underlying React node or rendered to the DOM element, you can prefix the prop name with a dollar sign ($), turning it into a transient prop.

In this example, $draggable isn't rendered to the DOM like draggable is.

const Comp = styled.div`
  color: ${props =>
    props.$draggable || 'black'};
`;

render(
  <Comp $draggable="red" draggable="true">
    Drag me!
  </Comp>
);

b. 遇到的問題:

b.1 我發現其實一整個規律就是,先有畫面 → 再做功能(新增 → 刪除(需要 id)→ 完成/未完成);因此在這裡的練習從先把畫面作成想要的樣子開始(已完成加上刪除線/未完成沒有線;已完成則按鈕顯示未完成,反之亦然)

b.2 為什麼 $isDone={todo.isDone} 裡面要放 todo.isDone,這裡的 todo.isDone 是判斷 true or false 的,沒寫就是 true

<TodoContent $isDone={todo.isDone} size={size}>

 {todo.content}

</TodoContent>

因為 props 的寫法是,如果 oo 那就(&&)xx;假設 && 改成 ||,那邏輯就會相反了。

${(props) =>
  props.$isDone &&
  `
  text-decoration: line-through;
`}

b.3 在寫的時候忘了加 &&,所以就是學了後面忘了前面,哎呀。

b.4 這裡的邏輯跟刪除有點類似,要做的功能是點擊按鈕切換完成/未完成。

會有兩種情況,「沒有被點擊到的 todo」、「被點擊到的 todo」

const handleToggleIsDone = (id) => {
  setTodos(
    todos.map((todo) => {
      // 沒有被點擊到的 todo,就直接回傳
      if (todo.id !== id) return todo;
      // 被點擊到的 todo,就用解構語法修改裡面的 isDone 的內容,
      // true -> false;false -> true
      return {
        ...todo,
        isDone: !todo.isDone,
      };
    })
  );
};

b.5 要修改的應該是 todo,而不是 todos

// 在這裡,todo.isDone 指的就是後面的內容,也就是 true or false,在前面加上否定就可以了
return {
  ...todo,
  isDone: !todo.isDone,
};

b.6 隔了幾天再做,又犯了兩個錯:

b.6.1 對於傳參數這件事還是沒有那麼的直覺

// TodoItem.js code
// 這裡忘了傳 todo.id 進去
const handleDeleteClick = () => {
  handleDeleteTodo(todo.id);
};

b.6.2 true or false 不可以包起來

// App.js code
// 不可以寫成這樣 "true"

{ id: 1, content: "done", isDone: true },

b.6.3 超過一行的程式碼,要用大括號包起來

// 下面第三行,如果只有一行,可以不用 {};超過一行就要包起來
const handleIsDoneSwitch = (id) => {
  setTodos(
    todos.map((todo) => {
      if (todo.id !== id) return todo;
      return {
        ...todo,
        isDone: !todo.isDone,
      };
    })
  );
};

這裡的重點是:

  • 修改 -> .map()
  • 刪除 -> .filter()
  • 新增 -> 解構語法

9. todo list 中場總結

Event handler: SyntheticEvent

下半場預告:React 裡最重要的兩個 hook,useState & useEffect


四、重要概念補充資料

以下概念摘自這一系列好文內容:

1. 如何排解困難

唯一需要你主動去做的,就是當你在閱讀中看到不懂、不確定用法的東西時,如果是 JavaScript 語法部分,請你使用「JS 關鍵字 + MDN」去做搜尋,例如,當你看到程式中出現 const { age } = data 解構賦值的用法而你不知道這是什麼意思時,請試著使用關鍵字「解構賦值 MDN」去尋找並閱讀資料。

[Day 01] 沒學過 React 可以從 Hooks 開始嗎?

2. js 語法介紹

[Day 02] React 中一定會用到的 JavaScript 語法

3. HTML 介紹

[Day 04 - 計數器] 把 HTML 寫在 JavaScript 中!? - JSX 的使用

4. css 介紹

[Day 05 - 計數器] 將計數器頁面改成用 JSX 來寫

5. useState 介紹

a. 在 React Hooks 中的慣例,只要開頭為 use 的函式,就表示它是個 "Hook"。

b. 一旦我們呼叫了 useState 這個方法,它實際上會回傳一個陣列,這個陣列中的第一個元素會是我們「想要監控的資料」,第二個元素會是「修改該資料的方法」

c. 透過 useState 得到的變數和方法名稱是可以自己取的,而慣例上用來改變變數的方法名稱會以 set開頭;預設值也可以不一定要是字串或數值,而是可以帶入物件。

d. 「透過 useState 建立了一個需要被監控的資料變數(count),並且透過 setCount 方法來改變 count 的數值時,React 會幫我們重新渲染畫面」,下面兩個要素缺一不可:

d.1 setCount 被呼叫到

d.2. count 的值確實有改變

e. React 只會更新畫面中有變化的部分

[Day 06 - 計數器] 醒醒啊!為什麼一動也不動 - useState 的基本使用

6. 介紹 &&||

[Day 07 - 計數器] 幫計數器設個最大最小值吧 - JSX 中條件渲染的使用

7. 將資料從父層組件傳遞到子層組件

[Day 11 - 網速轉換器] 那個...資料可以分享給我嗎 - 將資料傳入組件

8. Emotion 介紹 -- 類似 styled-component

[Day 14 - 即時天氣] 把 CSS 寫在 JavaScript 中!? - CSS in JS 的使用










Related Posts

hexo 部署在 AWS S3 上面

hexo 部署在 AWS S3 上面

AI輔導室|不讓標點符號出現在段落前端

AI輔導室|不讓標點符號出現在段落前端

D19_開始第三週

D19_開始第三週


Comments