changed 3 years ago
Linked with GitHub

[FE302] React 基礎:hooks(1)

[FE302] 2. React 基礎

[FE302] 2-1 環境建置——Create react app

現成線上環境:CodeSandBox

優點:debug 分享方便
缺點:

在自己電腦上建置環境

自己建置環境:webpack、React、Babel
現成建置環境:npm modules Create-react-app

  1. 已安裝 Node version >= 10.16
  2. 已安裝npm version >= 5.6
  3. 安裝 npm 套件 —— Create react app
npx create-react-app my-app // create-react-app -v # 查看版本有無安裝完成 cd my-app npm start # 或 npm run build # 啟動 dev server Compiled successfully! You can now view my-app in the browser. Local: http://localhost:3000 On Your Network: http://xxx.xxx.x.xxx:3000 Note that the development build is not optimized. To create a production build, use yarn build.

一拿到專案,先看 README.md,再看package.json

環境建置起來後(可以從 localhost:3000看到)

專案拿到的一開始,先看 README.md,再看package.json

  • README.md
yarn start # yarn 跟 npm 一樣,只是 yarn 比 npm 快
  • package.json
    可以看用哪些 npm、測試怎麼使用
{ "name": "my-app", "version": "0.1.0", "private": true, "dependencies": { // 使用些測試 "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", // create-app 主要的東西 "web-vitals": "^1.0.1" }, "scripts": { // 包好套件,方便執行一些檔案 "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { // 支援的 browser "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }

開啟 ./src/index.js

前面環境建置好後,就可以開始寫 react app:

import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; // step2: 從下面 render app 然後去找 app 從哪來。 import reportWebVitals from './reportWebVitals'; //和網站效能有關 ReactDOM.render( <React.StrictMode> // 嚴格模式,會幫我們檢查一些不該用的東西。 // 但是會影響 console.log() 所以通常都沒有使用 <App /> // step1: render App 這個 component </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();

發現 <App /> 這個 component 從 ./App.js 引入,所以就來看看這個檔案:
可以發現在 http://localhost:3000 的網頁內容來自此處,可以相對應修改,然後在 http://localhost:3000 會做相應改變。

import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload save to reload save to reload save to reload save to reload . </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;


嘗試修改 app.js,再觀察 localhost:3000 及 terminal:

  • app.js
import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> hello world!! </div> ); } export default App;
  • http:localhost:3000
  • terminal
    因為在 create-react-app 有預設 ESLint,所以 ESLint 的 warning & 提示都會在 terminal 出現。
Compiled with warnings. src/App.js Line 1:8: 'logo' is defined but never used no-unused-vars Search for the keywords to learn more about each warning. To ignore, add // eslint-disable-next-line to the line before.

建立 component

  1. 建立一個 component by function,名字為 Title
  • 在 React,component 名字以大寫鴕峰為命名慣例。
  • return(內容) 在 React 中,可以以 () 括號包起來
  • 將新建好的 component 放入 App() 裡面,放入的是 <Title /> 如果沒有要輸入任何參數,直接 /> 關起來即可。
import logo from './logo.svg';
import './App.css';

function Title() {
  return (
    <h1>hello World!!!</h1>
  )
}
function App() {
  return (
    <div className="App">
      <Title />
    </div>
  );
}

export default App;

component 內沒有參數輸入

  1. props.children
import logo from './logo.svg'; import './App.css'; function Title() { return ( <h1>hello World!!!</h1> ) } + function Description(props) { + return ( + <p> + {props.children} + </p> + ) + } function App() { return ( <div className="App"> <Title /> + <Description> + hihihihihihihihi + </Description> </div> ); } export default App;


2. 解構賦值

import logo from './logo.svg'; import './App.css'; function Title() { return ( <h1>hello World!!!</h1> ) } + function Description({children}) { + return ( + <p> + {children} + </p> + ) +} function App() { return ( <div className="App"> <Title /> <Description> hihihihihihihihi </Description> </div> ); } export default App;

[FE302] 2-2 初探 React 中的 style

有三種方法:

  1. import './App.css'
  2. inline-style
  3. styled-component

2-2-1. import './App.css'

像之前的用法一樣,引入 CSS ,然後就可以使用

2-2-2. inline-style

2-2-2-1 React 寫 CSS:inline-style 一:寫在 style

補充:
直接在 JSX 內的 HTML 標籤內加入 style 屬性(如:style={}),但有幾點需要注意:

  1. JSX 中的 inline style 要放入 object(物件),在 JSX 中只要和 JS 有關的參數,都要加上{}(style={}的外層大括號)

  2. 只能傳入該元素支援的 inline-style

  3. 不能使用「偽元素(after)」或「hover

  4. 因為是 JavaScript 程式碼,需改為小寫駝峰命名(例如:titleStyle
    React CSS 建立的 object 命名慣例為:小寫鴕峰。

在 React 寫一個 Object 名字為 titleStyle。然後在 React 內的 JSX 中有一個屬性為 style 專門寫 CSS 的內容,可以使用兩個方法寫 CSS:

  1. <h1 style={<CSS 的 object>}:直接另外新增個 CSS object,然後以 {} 放入。(在 JSX,只要和 JS 有關的,放進去都要以{}包起來)
const titleStyle = { color: 'red', textAlign: 'center' } function Title({ size }) { if (size === 'XL') { return <h1>hello</h1> } return ( <h1 style={titleStyle}>hello</h1> ) }
  1. <h1 style={{ height: '10%' }}:外層大括號為要傳入的參數(JSX 語法),內層為要傳入的 object (大括號)。
const titleStyle = { color: 'red', textAlign: 'center' } function Title({ size }) { if (size === 'XL') { return <h1>hello</h1> } return ( <h1 style={{ color: 'red', textAlign: 'center' }}>hello</h1> ) }

2-2-2-2 React 寫 CSS:inline-style 二:寫在 className

為什麼不能用 class?
在 JSX 語法很特別的原因是不能直接傳 class,因為 class 在 JavaScript 是保留字,所以不希望用 class,所以改用 className

2-2-3 styled-component

syntax

const Title = styled.h1` font-size: 1.5em; text-align: center; color: palevioletred; `;

`` 是 template literals(樣版字面值),也可以當成 function code 使用,

安裝
# with npm
npm install --save styled-components

# with yarn
yarn add styled-components
import 套件
import styled from 'styled-components';
示範
import React from 'react'; import logo from './logo.svg'; import './App.css'; // 因為 webpack 所以可以直接 import css + import styled from 'styled-components'; function Title({ size }) { if (size === 'XL') { return <h1>hello</h1>; } return ( <h2 style={{ display: 'flex', color: 'red', textAlign: 'center', }}>hello</h2> ) } + const Description = styled.p` + color: red; + padding: 20px; + border: 1px solid black; + ` function App() { const titleSize = 'M' return ( <div className="App"> <Title size={titleSize} /> <Description> 衷心感謝 </Description> </div> ) } export default App;


可以發現:Description 這個 component 會預設 className(HOBA-D),這個 className 就是 style-component 幫我加的。
原理:動態產生 className ,然後動態產生 CSS 放在 HTML tag 上。

示範 2

Title 也改成 styled-component

const TitleWrapper = styled.h2` // component 是大寫鴕峰,開投藥大寫。 display: flex; // 像一般 CSS 的寫法 color: blue; &:hover { # 也可以使用 sass 語法 color: red; } span { # 更可以寫子層次的格式。 color: yellow; } ` function Title({ size }) { return ( <TitleWrapper>hello<span>yes</span></TitleWrapper> ) }


滑鼠移過去變成紅色,hover 效果。

實做 todolist

styled-component 會自己加 prefix 不用擔心有網路兼容性問題

import React from 'react'; import logo from './logo.svg'; import './App.css'; // 因為 webpack 所以可以直接 import css import styled from 'styled-components'; const TodoItemWrapper = styled.div` display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; border: 1px solid black; ` const TodoContent = styled.div` color: black; ` const TodoButtonWrapper = styled.div`` const Button = styled.button` padding: 4px; color: black; & + & { margin-left: 4px; } :hover { color: white; background: blue; } ` // button + button 也就是第二個 function App() { const titleSize = 'M' return ( <div className="App"> <TodoItemWrapper> <TodoContent>I am todo</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <Button>刪除</Button> </todoButtonWrapper> </TodoItemWrapper> </div> ) } export default App;

styled-component 也可以加入 props (properties 屬性)
<!-- 方法一:三元運算子 --> const TodoContent = styled.div` color: black; + font-size: ${props => props.size === 'XL' ? '20px' : '12px'} ` <!-- 可以使用 JS 的語法,像是 `${}` 一樣, 一般在裡面會加個箭頭函式,然後 parameters 為 props。 使用三元運算子,如果是 'XL',就顯示 20px,如果不是,就顯示 12px。 --> <!-- 方法二:短路的概念 --> const TodoContent = styled.div` color: black; font-size: 12px; ${props => props.size === 'XL' && ` font-size: 20px; `} ` <!-- 利用短路的概念,當 `props.size === 'XL'` 符合時 就會執行下一個, 然後就會覆蓋掉 font-size:12px,使用 `20px` --> function App() { const titleSize = 'M' return ( <div className="App"> <TodoItemWrapper> + <TodoContent size="XL">I am todo</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <Button>刪除</Button> </todoButtonWrapper> </TodoItemWrapper> </div> ) }

一般會將重複的再寫成一個 component:

const TodoItemWrapper = styled.div` display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; border: 1px solid black; width: 60%; + & + & { + margin-top: 12px; + } ` + + const TodoItem = function ({ size, content }) { + <!-- function TodoItem({ size, content }) { 非箭頭函式--> + <!-- const TodoItem = ({ size, content }) => { 箭頭函式--> + return ( + <TodoItemWrapper> + <TodoContent size={size}>{content}</TodoContent> + <todoButtonWrapper> + <Button>編輯</Button> + <Button>刪除</Button> + </todoButtonWrapper> + </TodoItemWrapper> + ) + } function App() { const titleSize = 'M'; return ( <div className="App"> <TodoItem content={123}></TodoItem> <TodoItem content={456} size='XL'></TodoItem> </div> ) }

[FE302] 2-2 styled component 實戰

此章節介紹比較進階,在切版時會用到的用法:
推薦官方文件: Styled-component Basics

2-2-1 第一個常用的:Restyle

+ const RedButton = styled(Button)` + color: red; + ` function TodoItem({ size, content }) { return ( <TodoItemWrapper> <TodoContent size={size}>{content}</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <RedButton>刪除</RedButton> </todoButtonWrapper> </TodoItemWrapper> ) } function App() { const titleSize = 'M'; return ( <div className="App"> <TodoItem content={123}></TodoItem> <TodoItem content={456} size='XL'></TodoItem> </div> ) }

Restyle,在原本的 Button component 上再加上 CSS 放在刪除按鈕上:

如果今天想加上 <TodoItem> 的黑色背景:

function TodoItem({ size, content }) { return ( <TodoItemWrapper> <TodoContent size={size}>{content}</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <RedButton>刪除</RedButton> </todoButtonWrapper> </TodoItemWrapper> ) } + const BlackTodoItem = styled(TodoItem)` + background: black; + `

背景底色沒有變,因為 styled-component 背後的原理是透過傳 ClassName

所以要在原本的 Component 傳入一個參數 {className},然後把這個 {className} 放在想放的地方:

+ function TodoItem({ className, size, content }) { return ( + <TodoItemWrapper className={className}> <TodoContent size={size}>{content}</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <RedButton>刪除</RedButton> </todoButtonWrapper> </TodoItemWrapper> ) } + const GreyTodoItem = styled(TodoItem)` + background: grey; + ` function App() { const titleSize = 'M'; return ( <div className="App"> <TodoItem content={123}></TodoItem> + <GreyTodoItem content={456} size='XL'></GreyTodoItem> </div> )

變成說某一個 GreyTodoItem 變成某一個 class 有這個 background: grey;,然後再看要把這個 class 給誰,這邊示範是給 ItemWrapper
也可對象為 styled-component,直接如上方寫即可,他會直接處理好;也可以對一般的 component 做一樣的事情(傳入{className}),

2-2-2 第二個常用的:Media Queries (MQ)

通常習慣從 mobile 寫起,先看 mobile 版面,參考W3 School: Media Queries

const Button = styled.button` padding: 4px; color: black; font-size: 20px; + @media screen and (min-width: 768px) { + font-size: 16px; + } & + & { margin-left: 4px; } :hover { color: white; background: blue; } `

從手機版變到平版(最小 768 px)時,字體會從 20px 變成 16px

但是每個 component 都要加上 MQ(Media Queries),很麻煩,通常會建立一個 constants/constants.js 常數名稱的資料夾。

 |-- src
   | -- App.js
   | -- index.js
   | -- constants
      | -- style.js
  • style.js
<!-- style.js --> export const MEDIA_QUERY_MD = '@media screen and (min-width: 768px)' export const MEDIA_QUERY_LG = '@media screen and (min-width: 1024px)'
  • App.js
<!-- App.js --> + import { MEDIA_QUERY_MD, MEDIA_QUERY_LG } from './constants/style' const Button = styled.button` padding: 4px; color: black; font-size: 20px; + ${MEDIA_QUERY_MD} { + font-size: 16px; + } + ${MEDIA_QUERY_LG} { + font-size: 12px; + } & + & { margin-left: 4px; } :hover { color: white; background: blue; } `

2-2-3 Advanced:theming

  1. 提供傳一個 global 的參數,在 App.js 就能用到。
  2. theme 傳遞變數的功能,來達到我們之前寫 SASS 的變數功能。
  3. 好處:
    可以調整主色,如果哪天想要調整主題色,就可以使用這個來調整主題色,從主題色紅色改藍色等等。
const theme = { colors: { primary_300: '#e61111', primary_400: '#440000', primary_500: '#660000' } }
  • index.js
    使用 <ThemeProvide theme={theme}> ,傳入 global 參數:
    然後另外設 object,代入如下一段 code(App.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; //和網站效能有關 + import { ThemProvider } from 'styled-components'; + const theme = { + colors: { + red_300: '#e61111', + red_400: '#440000', + red_500: '#660000' + } + } ReactDOM.render( + <ThemProvider theme={theme}> <App /> + </ThemProvider>, + document.getElementById('root') );
  • App.js
    透過 index.js<ThemeProvider theme={theme}> 傳入的 theme 變數:

記得在 CSS 每個後面加分號,不然會沒有效果。

const TodoContent = styled.div` + color: ${props => props.theme.colors.red_300}; font-size: 12px; ${props => props.size === 'XL' && ` font-size: 20px; `} `

styled-component

  1. theme
  2. restyle
  3. media-Query 可以透過參數來寫的方式
    需要的也可以去官方網站補充

如果後面切版太多 component,可以另建檔案,把 component 獨立出來:
export default function TodoItem() {}

範例:
將 App.js 分開,拆到 TodoItem.js

  • index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // 和網站效能有關 + import { ThemeProvider } from 'styled-components'; const theme = { colors: { red_300: '#e61111', red_400: '#440000', red_500: '#660000' } } ReactDOM.render( <ThemeProvider theme={theme}> <App /> </ThemeProvider>, document.getElementById('root') ); reportWebVitals();
  • App.js
import React from 'react'; import logo from './logo.svg'; import './App.css'; // 因為 webpack 所以可以直接 import css import styled from 'styled-components'; import { MEDIA_QUERY_MD, MEDIA_QUERY_LG } from './constants/style' import TodoItem from './TodoItem' const GreyTodoItem = styled(TodoItem)` background: grey; ` function App() { const titleSize = 'M'; return ( <div className="App"> <TodoItem content={123}></TodoItem> <GreyTodoItem content={456} size='XL'></GreyTodoItem> </div> ) } export default App;
  • TodoItem.js
import React from 'react'; import logo from './logo.svg'; import './App.css'; // 因為 webpack 所以可以直接 import css import styled from 'styled-components'; import { MEDIA_QUERY_MD, MEDIA_QUERY_LG } from './constants/style' const TodoItemWrapper = styled.div` display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; border: 1px solid black; & + & { margin-top: 12px; } ` const TodoContent = styled.div` color: ${props => props.theme.colors.red_300}; font-size: 12px; ${props => props.size === 'XL' && ` font-size: 20px; `} ` //JS 放 function 一般放 arrow func,一般用 props, 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: 12px; } & + & { margin-left: 4px; } :hover { color: white; background: blue; } ` const RedButton = styled(Button)` color: red; ` // button + button 也就是第二個 + export default function TodoItem({ className, size, content }) { return ( <TodoItemWrapper className={className}> <TodoContent size={size}>{content}</TodoContent> <todoButtonWrapper> <Button>編輯</Button> <RedButton>刪除</RedButton> </todoButtonWrapper> </TodoItemWrapper> ) }

[FE302] 2-3 [一探 JSX 背後的秘密]

為什麼可以使用 return(),就建立起來了呢?

export default function TodoCard({ content }) { return ( <TodoItem> <TodoContent> <IconUnchecked /> <TodoText>{content}</TodoText> </TodoContent> <TodoButtonWrapper> <IconEdit /> <IconDelete /> </TodoButtonWrapper> </TodoItem> ) }

背後其實是通過 Babel 轉換而來:

  • React
<TodoList content='Todo 1'>Hello</TodoList>
  • Babel
/*#__PURE__*/ React.createElement(TodoList, { content: "Todo 1" }, "Hello"); /* React.createElement(TodoList, { content: "Todo 1" -> props }, "Hello"); --> 文字內容 */

試試看 使用被 Babel 轉成的 JS:
示意:

<TodoList content='Todo 1'>Hello</TodoList> // 等同於:以下 { React.createElement(TodoList, { content: "Todo 1" }, "Hello"); }

其實是一個 function,把 objectchildren

另外如果 childredn 裡面是另外一個 component 的話:

  • React
<TodoList content='Todo 1'><div>hello</div>Hello</TodoList>

  • Babel 將 React 語法轉成 JS
    再在裡面做一次 React.createElement()
/*#__PURE__*/ React.createElement(TodoList, { content: "Todo 1" }, /*#__PURE__*/React.createElement("div", null, "hello"), "Hello");

在 React 17 以前:
每個有使用到 JSX 的地方,都要 import React from 'react';,因為他會使用到 React.createELement() 的語法,不然會壞掉

在 React 17 之後:
Babel 會幫我們做

[FE302] 2-4 [初探 State]

UI 只是 State 的 mapping。
畫面永遠由 state 產生。

UI = f(state)

在 React 要使用 State,需要引入 React modules。
React.useState()state 就是一個 hook
← React 16.8 → 才出現 hooks,在 function component 使用 state

syntax:

import React from 'react'; const [counter, setCounter] = React.useState(`<初始值>`) <!-- 為解構語法,回傳一個陣列,counter 為 state 的值,第二個為 setCounter,如何去 set state。--> <!-- 解釋這個解構語法--> function useState() { return [counter, setCounter] }

在 component 中,有兩種:

  1. function component
  2. class component

試試看做一簡單的 state 點擊後會增加次數

點擊 increment 按鈕後,會實行 handleButtonClick 的 function,也就是會執行SetCounter(counter + 1),然後在畫面上的 counter: 顯示數字會+1。

function App() { + const [counter, setCounter] = React.useState(0) + function handleButtonClick(e) { + setCounter(counter + 1) + } const titleSize = 'M'; return ( <div className="App"> + counter: {counter} <TodoItem content={123}></TodoItem> + <button onClick={handleButtonClick}>increment</button> <GreyTodoItem content={456} size='XL'></GreyTodoItem> </div> ) }


再點擊一次,變成+1,從 9 變成 10

原理解釋:
React 流程

  1. 動作:執行 App() 這個 function,會 return JSX,React 會把這些 JSX 放到畫面上,這個動作就是 mount(瀏覽器 render 出來畫面)
  2. 當 state 有更新時,就會 rerender(就是再 call 一次 App())。
    以下圖解釋:
  3. 重整頁面,counter: 0
  4. 按 increment 按鈕,此時 counter + 1 = 0 + 1(setCounter(counter+1)
  5. console
App.js:16 button click 當按鈕按下去時,就會重新執行 `App()` 
所以才會出現 render 1
App.js:14 render 1
  1. React 重新呼叫 App(),再 Render 一次,就是 Rerender。

useState 傳入陣列,再搭配 .map(),把每一個 array element render 成 component。

  • 最開始,沒有講 state 概念時:
function App() { const titleSize = 'M'; return ( <div className="App"> <button onClick={handleButtonClick}>Add todo</button> <TodoItem content={123}></TodoItem> <GreyTodoItem content={456} size='XL'></GreyTodoItem> </div> ) }
  • 加入 state 概念,並使用陣列傳入 123, 456
function App() { const [todos, setTodos] = React.useState([ 123, 456 ]) // render 陣列 function handleButtonClick(e) { } const titleSize = 'M'; return ( <div className="App"> <button onClick={handleButtonClick}>Add todo</button> <!-- {} 為輸入 JavaScript 語法時需要 這個就是把兩個 todo 利用陣列([])的 component。 效果同上> --> { [<TodoItem content={123}></TodoItem>, <TodoItem content={456} size='XL'></TodoItem>] } </div> ) }

  • array.map()
    react 沒有迴圈,只能用 map,好處是可以把每一個 array element map 成 component。就是可以 render 很多component。
    所以在 React 如果要 render 一系列的資料,就會使用 .map 來做。
function App() { const [todos, setTodos] = React.useState([ 123, 456 ]) // render 陣列 function handleButtonClick(e) { } const titleSize = 'M'; return ( <div className="App"> <button onClick={handleButtonClick}>Add todo</button> // react 沒有迴圈,只能用 map,好處是可以把每一個 array element map 成 component。就是可以 render 很多component。 { todos.map(todo => <TodoItem content={todo} />) }

錯誤訊息:

Warning: Each child in a list should have a unique "key" prop.

index.js:1451 Warning: Each child in a list should have a unique "key" prop. Check the render method of `App`. See https://reactjs.org/link/warning-keys for more information. at TodoItem (http://localhost:3000/static/js/main.chunk.js:264:3) at App (http://localhost:3000/static/js/main.chunk.js:119:74) at Fe (http://localhost:3000/static/js/0.chunk.js:43408:67)

在使用 .map() 產生 component 時,需要產生 keyreact 去辨別是哪一個 item,先以{index} 為 key,但不推薦名稱命名為 index

{ + todos.map((todo, index)=> <TodoItem key={index} content={todo} />) }

當 React 判定 old statue = new state,就不會更新

statue is immutable

如何改 state?

function App() {
  const [todos, setTodos] = React.useState([
    123, 456, 555
  ])
}

const handleButtonClick = () => {
  // 如何改變 state?
}
  1. todos.push(777)

不能直接這樣改,要使用 setTodos()

  1. todos.push(777); setTodos(todos)

點選 「add todo」 沒有反應。
因為
todos.push(777) //此時舊的 todos [123, 456, 55, 777]
setTodos(todos) //在 setTodos之後新的 todos [123, 456, 55, 777]
當 React 判定舊的跟新的 state 一樣時,就表示 state 沒有更新,那就不會 rerencer。

State Immutable

  1. setTodos([todos, 777]) // 前面加上 todos element,後面再加上新的值。
function App() { const [todos, setTodos] = React.useState([ 123, 456, 555 ]) function handleButtonClick(e) { setTodos([...todos, Math.random()]) <!-- > setTodos([Math.random(), ...todos]) --> <!-- state 改變(前後不一樣),會再 render 一次 --> } const titleSize = 'M'; return ( <div className="App"> <button onClick={handleButtonClick}>Add todo</button> { todos.map(todo => <TodoItem content={todo} />) } </div> ) } export default App;

不應該去修改 state (immutable, like todos) 的內容,應該是透過 setTodos 去修改內容,產生一個新的陣列,然後再 render 到頁面上。


另外,也可以改成:
import {useState} from 'react';
直接解構

[FE302] 2-4 [再探 State]

state 分成兩種:

  1. controlled:useState
  2. uncontrolled

複習 useState,

import {useState} from 'react'; function App() { <!-- 1. 使用 useState,state 的值、如何改變 state 的值 --> const [todos, setTodos] = useState([ 123, 456, 555 ]) function handleButtonClick(e) { <!-- 2. 要改變時,需要 call setTodos 來改變 state 的值 --> setTodos([...todos, Math.random()]) } <!-- 3. state is immutabl,不能直接改,是要改的話,需要產生新的--> const titleSize = 'M'; return ( <div className="App"> <button onClick={handleButtonClick}>Add todo</button> { todos.map(todo => <TodoItem content={todo} />) } </div> ) }

controller component & uncontrolled

1. controlled state

所有在 UI 上會動的東西 幾乎都是個state


其實在輸入 qwer 時,也會在 React 的 state,可以在自己的裡面控制。,如下範例的

function App() { + const [ value, setValue = useState('') return ( <div className="App"> + <input type='text' placeholder='new todo' value={value} /> <button onClick={handleButtonClick}>Add todo</button> { todos.map(todo => <TodoItem content={todo} />) } </div> ) } export default App;


無法輸入任何字母、數字進去,沒辦法改變這個 state
但是輸入了,什麼都沒有改變,因為 state 沒有變,還是 '' 空字串

state 還是狀態不會變(immutable),要怎麼改變 state 呢?
可以在 <input type='text '> 裡面加入 onChange 的事件。

+      <input type='text' placeholder='new todo' value={value} onChange={handleInputChange}/>

onChange={handleInputChange}
onCLick={handleButtonClick}
命名慣例:handle 開頭。

那加上了 onChange={handleInputChange},就要來實作功能:

function App() { + function handleInputChange(e) { + console.log(e.target.value) + } return ( <div className="App"> + <input type='text' placeholder='new todo' value={value} onChange={handleInputChange} /> <button onClick={handleButtonClick}>Add todo</button> { todos.map(todo => <TodoItem content={todo} />) } </div> ) } export default App;

每輸入一個字母,就會 console.log(e.target.value),會把新輸入的 value 印出來

現在要改成 setValue 改變 state 的值:

function handleInputChange(e) { + setValue(e.target.value) }


今天輸入 a,觸發了 handleInputCHange 事件,此時e.target.value 為 a,所以 value 值為 a,只要 state 改變,就會 rerender,現在 <input value={value} />{value} 變成 a。

今天我再來輸入 b,觸發 handleInputCHange 事件,此時e.target.value 為 ab,所以 value 值為 ab,只要 state 改變,就會 rerender,現在 <input value={value} />{value} 變成 ab。

透過這個方式,就可以把 input 的 state 值,放入 state 內。

小結:放在 controlled state 裡面

2. uncontrolled state

第二種是 uncontrolled state


React 官方教學

什麼是 component?props?

其中前面提到:{props.children},不太懂為什麼要使用 props.children
所以從Components 與 Props介紹起:

Component 使你可以將 UI 拆分成獨立且可複用的程式碼,並且專注於各別程式碼的思考。本章節旨在介紹 component 的相關概念,你也可以在此參閱詳細的 API 文件

概念上來說,component 就像是 JavaScript 的 function,它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。
from: Components 與 Props

component 組件

就很像 CSS 切版的概念,將各小區塊的 UI寫在 JavaScript,還可以有著 JavaScript 的輔助,套用到程式的邏輯。

如何建立一個 component?

建立一個 component 最簡單的方法就是

定義 component 最簡單的方法即是撰寫一個 Javascript function:

function Welcome(props) { return <h1>Hello, {props.name}</h1>; }

此 function 是一個符合規範的 React component,因為它接受一個「props」(指屬性 properties)物件並回傳一個 React element。我們稱之為 function component,因為它本身就是一個 JavaScript function。

現在我們用一個 function 叫做 Welcome 輸入一個 React 參數 props,回傳一個 React element(<h1> hello, {props.name}</h1>)。

現在做好了一個 component,這樣就可以用程式的邏輯,將 UI 放在 JavaScript 上,我們會希望這個 {props.name} 如變數一般,可以替換。
回顧一下,先前我們要在 ReactDOM.render()插入 component 時,都是只有加入 component,沒輸入參數,所以使用的時候,就會如以下情形:

ReactDOM.render( <Welcome />, // DOM 標籤的 React element document.querySelector() )

如果今天想要替換 Welcome component 裡面的變數的話,就可以這樣寫:
props 是指prperties 屬性,這個用 function Welcome 符合 React component 的規則,也回傳了一個 React element<h1> hello, {props.name}</h1>),其中 properties 為屬性。

// 建立一個 component function Welcome(props) { return <h1>Hello, {props.name}</h1>; }

當 React 看到由使用者定義 component 的 element 時,它將 JSX 屬性和 children 作為 single object 傳遞給該 component。我們稱這個 object 為「props」。

這邊自定義一個 name 的屬性(properties),我覺得應該是長相挺像 HTML 標籤,然後寫在標籤裡面的名稱,很像是自定義的屬性,

const element = <Welcome name="Sara" />; // React element 也可以是使用者自定義的 component

然後將 React element 放到 ReactDOM.render() 的 JSX 裡面:

ReactDOM.render( element, document.getElementById('root') );

React 官方文件的說明:

  1. 我們對 <Welcome name="Sara" /> 這個 element 呼叫了 ReactDOM.render()。
  2. React{name: 'Sara'} 作為 props 傳入 Welcome component 並呼叫。
  3. Welcome component 回傳了 <h1>Hello, Sara</h1> 這個 element 作為返回值。
  4. React DOM 有效的將 DOM 更新為 <h1>Hello, Sara</h1>。ˋ

現在知道怎麼使用跟建立自定義 component,然後先前說的 React component 的概念很像是把他分成很多個 UI 小區塊,再利用 JavaScript 程式的語法跟邏輯寫好 HTML、CSS 等等,現在切出了一塊小的 UI,但是如果這些小的 UI 組件,如果又像是 HTML 一樣,有幾個寫幾次的話,那就不符合有程式邏輯的寫法,所以現在要說明的就是以程式邏輯的寫法,寫 HTML 重複性的組件(component)。

function Welcome(props) { return <h1>Hello, {props.name}</h1>; } function App() { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
  1. component 命名慣例:大寫鴕峰
  2. component 使用:
    (1) 建立:利用 function component or class component(ES6)定義跟建立一個 component
    (2) 使用:<Welcome />,如果有輸入參數的話就是像以上的範例:<Welcome name='Sara' />
    (3) 使用方法有幾種:
    • 直接放到 ReactDOM.render() 的 JSX 裡面。
    • 或者是再放到另一個 Component 內。


上面提到了怎麼使用 Component 並 render 到頁面上,就像是上圖的 TodoList 只有一個 component。
不過前面提到其實 component 就像是 UI 一小塊一小塊的切版那樣,所以 component 裡面可能還會有 component,然後要靈活運用相似的區塊,讓裡面的 component 也要可以輸入參數改變值,所以下面需要學習怎麼讓 component 裡面還有 component 並可以傳入參數,就像是上圖的 todolist 在待辦清單的部分,有待辦事項、刪除按鈕、新增的輸入框等,由幾個小組件組成。
下面以React 官方文件的範例為練習

概念矯正

react element

// 宣告一個 name 的變數,然後在 JSX 內使用 {} 來插入宣告的這個變數 name。 const name = 'Josh Perez'; // 這個 element 宣告 JSX 內容,後面要用在 JSX 處。 const element = <h1>Hello, {name}</h1>; // 錯誤寫法:const element = `<h1>Hello, {name}</h1>`; ReactDOM.render( element, document.getElementById('root') );

JSX 的大括號中寫入任何合法的 JavaScript expression。舉例來說,2 + 2user.firstName 以及 formatName(user)
From:React:介紹 JSX

另外在從 Hooks 開始,讓你的網頁 React 起來系列 第 5 篇也提到:

我們也可以把 JSX 的內容抽成一個 JavaScript 變數,只需要把 JSX 的內容用小括號(())包起來就好

但此時就在想,為什麼可以用 () 包起來呢?在React 介紹 JSX中有提到:

為了方便閱讀,我們將 JSX 拆成很多行表達。雖然這並不需要,我們建議將多行 JSX 包在括號中來避免遇到自動分號補足的麻煩。

舉例:在第 10 ~ 14 行,可以發現將 JSX 「多行」內容(JSX內容:<h1>Hello, {formatName(user)}!</h1>)以() 包起來,這樣可以省去自動分號補足的麻煩

function formatName(user) { return user.firstName+ ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const element = ( <h1> Hello, {formatName(user)}! </h1> ); ReactDOM.render( element, document.getElementById('root') );

此外這個例子還示範了在 JSX 內容,可以嵌入呼叫 JavaScript function (formatName(user)) 的回傳值

小結:

  1. JSX 可以將 HTML 等等的內容抽出,以() 包覆,並令成變數,進階一步在 JSX 的內容裡,利用 {} 來插入一些 JavaScript 的變數(如:<h1>Hello {name}</h1>或者是 function裡回傳的值(如:<h1>hello functionName(user))</h1>

JSX 本身也是 expression

在經過編譯的 JSX expression 會變成 JavaScript 的 function,表示也可以使用 iffor中使用 JSX:

function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }

在 JSX 中指定屬性

可以在 JSX 指定 HTML tag 內的屬性(attribute),有兩種方法:

  1. 如同 HTML attribute 一樣輸入「字串」
const element = <div tabIndex="0"></div>;
  1. 在屬性中使用大括號來嵌入一個 JavaScript expression
const element = <img src={user.avatarUrl}></img>;

不建議在 JSX 內的屬性混用 {}"string"

注意:
JSX 是寫在 JavaScript 內,所以要避開一些 JS 的語法像是 class(類別)就會和 HTML 的 CSS class 搞混,所以在此將 HTML CSS 的慣用的 class ,在 React 使用時,要改成 className,此外也將 HTML tag 的屬性(attribute)以小寫鴕峰表示,像是:<div maxLength='10px'</div> 中的 maxLength 改為小寫鴕峰。

在 JSX 中指定 Children

也可以在 JSX 內指定要哪些子 HTML tag 標籤,像是原本我們在 JavaScript 是:

const element = ( <img src={user.avatarUrl} /> )

只有將一個 <img> 包成 JSX。
現在也可以在裡面指定子標籤:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

React element(JSX 表示物件)

參考:JSX 介紹
舉例:

const element = ( <h1 className="greeting"> Hello, World! </h1> );

等同於以下:

const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, World!' );

感覺使用 React.createElement 是將其拆解,然後官方文件上寫說會「會進行一些檢查以幫助你寫出沒有 bug 的程式」,會產生類似的物件(下面這個已經簡化過):

const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world!' } };

這種物件被稱呼為「React element」。你可以想像他們描述的是你想要在螢幕上看到的東西,React 會讀取這些物件並用這些描述來產生 DOM 並保持他們在最新狀態。

Render Element——

React element 是單純的 object,而且很容易被建立。React DOM 負責更新 DOM 來符合 React element。

React element 是單純的 object,可以從前一篇章介紹 JSX 表示物件——element 複習起,然後在建立 React 應用程式的最小單位是「(React)
element」。

// 要 render 一個 element 到 root DOM node ,就傳入 element 及 DOM node 兩者到 ReactDOM.render() const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root'));

React elementimmutable,那要怎麼更新?

React element 是 immutable 的。一旦你建立一個 element,你不能改變它的 children 或是 attribute。Element 就像是電影中的一個幀:它代表特定時間點的 UI。
憑藉我們迄今為止對 React 的認識,更新 UI 唯一的方式是建立一個新的 element,並且將它傳入到 ReactDOM.render()。

官網以 setInterval 在每秒來重新 render 當前時間。那這樣 React element 是 immutable,也不能總用 setInterval 來更新吧?
後面會提到一個 stateful component 的概念。

resources referenct

React Function Components

tags: MTR05 Lidemy 學習筆記
Select a repo