owned this note
owned this note
Published
Linked with GitHub
# [FE302] React 基礎:hooks(1)
[TOC]
## [FE302] 2. React 基礎
## [FE302] 2-1 [環境建置——Create react app](https://zh-hant.reactjs.org/docs/create-a-new-react-app.html)
### 現成線上環境:CodeSandBox
優點:debug 分享方便
缺點:
### 在自己電腦上建置環境
:::info
自己建置環境:webpack、React、Babel
現成建置環境:npm modules -- Create-react-app
:::
1. 已安裝 Node version >= 10.16
2. 已安裝npm version >= 5.6
3. 安裝 npm 套件 —— Create react app
```bash=
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`看到)
:::warning
專案拿到的一開始,先看 `README.md`,再看`package.json`
:::
* `README.md`
```bash=
yarn start # yarn 跟 npm 一樣,只是 yarn 比 npm 快
```
* `package.json`
可以看用哪些 npm、測試怎麼使用
```json=
{
"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:
```javascript=
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` 會做相應改變。
```javascript=
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;
```
![](https://i.imgur.com/TJ81PRQ.png)
嘗試修改 `app.js`,再觀察 localhost:3000 及 terminal:
* `app.js`
```javascript=
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
hello world!!
</div>
);
}
export default App;
```
* `http:localhost:3000`
![](https://i.imgur.com/Twbisek.png)
* terminal
因為在 create-react-app 有預設 ESLint,所以 ESLint 的 warning & 提示都會在 terminal 出現。
```bash=
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`
```javascript=
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;
```
![](https://i.imgur.com/XQw6E0A.png)
2. 解構賦值
```javascript=
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
:::info
有三種方法:
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` 內
:::success
補充:
直接在 JSX 內的 HTML 標籤內加入 style 屬性(如:style={}),但有幾點需要注意:
1. JSX 中的 inline style 要放入 object(物件),在 JSX 中只要和 JS 有關的參數,都要加上`{}`(style=`{}`的外層大括號)
3. 只能傳入該元素支援的 inline-style
4. 不能使用「偽元素(`after`)」或「`hover`」
6. 因為是 JavaScript 程式碼,需改為小寫駝峰命名(例如:`titleStyle`)
React CSS 建立的 object 命名慣例為:小寫鴕峰。
:::
在 React 寫一個 Object 名字為 titleStyle。然後在 React 內的 JSX 中有一個屬性為 `style` 專門寫 CSS 的內容,可以使用兩個方法寫 CSS:
1. `<h1 style={<CSS 的 object>}`:直接另外新增個 CSS object,然後以 `{}` 放入。(在 JSX,只要和 JS 有關的,放進去都要以`{}`包起來)
```react=
const titleStyle = {
color: 'red',
textAlign: 'center'
}
function Title({ size }) {
if (size === 'XL') {
return <h1>hello</h1>
}
return (
<h1 style={titleStyle}>hello</h1>
)
}
```
2. `<h1 style={{ height: '10%' }}`:外層大括號為要傳入的參數(JSX 語法),內層為要傳入的 object (大括號)。
```react=
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` 內
:::success
為什麼不能用 class?
在 JSX 語法很特別的原因是不能直接傳 class,因為 `class` 在 JavaScript 是保留字,所以不希望用 `class`,所以改用 `className`,
:::
#### 2-2-3 [`styled-component`](https://styled-components.com/docs/basics#getting-started)
:::info
syntax
```react=
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
```
`` 是 [template literals(樣版字面值)](https://pjchender.blogspot.com/2017/01/javascript-es6-template-literalstagged.html),也可以當成 `function code` 使用,
:::
##### 安裝
```
# with npm
npm install --save styled-components
# with yarn
yarn add styled-components
```
##### import 套件
```react=
import styled from 'styled-components';
```
##### 示範
```react=
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;
```
![](https://i.imgur.com/9GUQ71J.png)
可以發現:`Description` 這個 component 會預設 className(HOBA-D),這個 className 就是 `style-component` 幫我加的。
原理:動態產生 className ,然後動態產生 CSS 放在 HTML tag 上。
##### 示範 2
將 `Title` 也改成 `styled-component`,
```react=
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>
)
}
```
![](https://i.imgur.com/G0XN2m9.png)
滑鼠移過去變成紅色,hover 效果。
##### 實做 todolist
`styled-component` 會自己加 prefix 不用擔心有網路兼容性問題
```react=
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;
```
![](https://i.imgur.com/rUSJHi3.png)
##### `styled-component` 也可以加入 `props` (properties 屬性)
```react=
<!-- 方法一:三元運算子 -->
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>
)
}
```
![](https://i.imgur.com/2qfI8AL.png)
一般會將重複的再寫成一個 component:
```react=
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>
)
}
```
![](https://i.imgur.com/RswCeDm.png)
## [FE302] 2-2 styled component 實戰
:::info
此章節介紹比較進階,在切版時會用到的用法:
推薦官方文件: [`Styled-component Basics`](https://styled-components.com/docs/basics)
:::
### 2-2-1 第一個常用的:Restyle
```react=
+ 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 放在刪除按鈕上:
![](https://i.imgur.com/cJeq2vA.png)
如果今天想加上 `<TodoItem>` 的黑色背景:
```React=
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`
![](https://i.imgur.com/RRifqgx.png)
所以要在原本的 `Component` 傳入一個參數 `{className}`,然後把這個 `{className}` 放在想放的地方:
```react=
+ 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}`),
![](https://i.imgur.com/u25ipfS.png)
### 2-2-2 第二個常用的:Media Queries (MQ)
:::info
通常習慣從 mobile 寫起,先看 mobile 版面,參考[W3 School: Media Queries](https://www.w3schools.com/css/css3_mediaqueries_ex.asp)
:::
```react=
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`。
![](https://i.imgur.com/CHXjxce.png)
![](https://i.imgur.com/4IlNin8.png)
:::success
但是每個 component 都要加上 MQ(Media Queries),很麻煩,通常會建立一個 `constants/constants.js` 常數名稱的資料夾。
```
|-- src
| -- App.js
| -- index.js
| -- constants
| -- style.js
```
:::
* `style.js`
```react=
<!-- 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
```react=
<!-- 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;
}
`
```
![](https://i.imgur.com/LdniHsS.png)
### 2-2-3 [Advanced](https://styled-components.com/docs/advanced):theming
:::info
1. 提供傳一個 `global` 的參數,在 `App.js` 就能用到。
2. 以 `theme` 傳遞變數的功能,來達到我們之前寫 SASS 的變數功能。
3. 好處:
可以調整主色,如果哪天想要調整主題色,就可以使用這個來調整主題色,從主題色紅色改藍色等等。
```react=
const theme = {
colors: {
primary_300: '#e61111',
primary_400: '#440000',
primary_500: '#660000'
}
}
```
:::
* index.js
使用 `<ThemeProvide theme={theme}>` ,傳入 `global` 參數:
然後另外設 object,代入如下一段 code(`App.js`)
```react=
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` 變數:
:::warning
記得在 CSS 每個後面加分號,不然會沒有效果。
:::
```react=
const TodoContent = styled.div`
+ color: ${props => props.theme.colors.red_300};
font-size: 12px;
${props => props.size === 'XL' && `
font-size: 20px;
`}
`
```
![](https://i.imgur.com/ePZkZZU.png)
:::info
styled-component
1. theme
2. restyle
3. media-Query 可以透過參數來寫的方式
需要的也可以去官方網站補充
如果後面切版太多 component,可以另建檔案,把 component 獨立出來:
`export default function TodoItem() {}`
:::
範例:
將 App.js 分開,拆到 `TodoItem.js`
* index.js
```react=
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
```react=
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
```react=
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()`,就建立起來了呢?
```react=
export default function TodoCard({ content }) {
return (
<TodoItem>
<TodoContent>
<IconUnchecked />
<TodoText>{content}</TodoText>
</TodoContent>
<TodoButtonWrapper>
<IconEdit />
<IconDelete />
</TodoButtonWrapper>
</TodoItem>
)
}
```
背後其實是通過 Babel 轉換而來:
* React
```react=
<TodoList content='Todo 1'>Hello</TodoList>
```
* Babel
```javascript=
/*#__PURE__*/
React.createElement(TodoList, {
content: "Todo 1"
}, "Hello");
/* React.createElement(TodoList, {
content: "Todo 1" -> props
}, "Hello"); --> 文字內容 */
```
試試看 使用被 Babel 轉成的 JS:
示意:
```javascript=
<TodoList content='Todo 1'>Hello</TodoList>
// 等同於:以下
{
React.createElement(TodoList, {
content: "Todo 1"
}, "Hello");
}
```
:::success
其實是一個 function,把 `object`、`children`、
:::
另外如果 childredn 裡面是另外一個 component 的話:
* React
```
<TodoList content='Todo 1'><div>hello</div>Hello</TodoList>
```
* Babel 將 React 語法轉成 JS
再在裡面做一次 `React.createElement()`
```javascript=
/*#__PURE__*/
React.createElement(TodoList, {
content: "Todo 1"
}, /*#__PURE__*/React.createElement("div", null, "hello"), "Hello");
```
:::warning
在 React 17 以前:
每個有使用到 `JSX` 的地方,都要 `import React from 'react';`,因為他會使用到 `React.createELement()` 的語法,不然會壞掉
在 React 17 之後:
Babel 會幫我們做
:::
## [FE302] 2-4 [初探 State]
:::success
UI 只是 State 的 mapping。
畫面永遠由 state 產生。
### `UI = f(state)`
![](https://i.imgur.com/0fM6JNV.png)
:::
:::info
在 React 要使用 State,需要引入 React modules。
`React.useState()` 的 `state` 就是一個 `hook`。
← React 16.8 → 才出現 hooks,在 function component 使用 `state`
syntax:
```react=
import React from 'react';
const [counter, setCounter] = React.useState(`<初始值>`)
<!-- 為解構語法,回傳一個陣列,counter 為 state 的值,第二個為 setCounter,如何去 set state。-->
<!-- 解釋這個解構語法-->
function useState() {
return [counter, setCounter]
}
```
:::warning
在 component 中,有兩種:
1. function component
2. class component
:::
### 試試看做一簡單的 state 點擊後會增加次數
點擊 `increment` 按鈕後,會實行 `handleButtonClick` 的 function,也就是會執行`SetCounter(counter + 1)`,然後在畫面上的 `counter:` 顯示數字會+1。
```react=
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>
)
}
```
![](https://i.imgur.com/U0jSQyR.png)
再點擊一次,變成+1,從 9 變成 10
![](https://i.imgur.com/K20oDAG.png)
原理解釋:
React 流程
1. 動作:執行 App() 這個 function,會 return JSX,React 會把這些 JSX 放到畫面上,這個動作就是 `mount`(瀏覽器 render 出來畫面)
2. 當 state 有更新時,就會 rerender(就是再 call 一次 App())。
以下圖解釋:
![](https://i.imgur.com/8DBlmxC.png)
1. 重整頁面,counter: 0
2. 按 increment 按鈕,此時 counter + 1 = 0 + 1(`setCounter(counter+1)`)
3. console
```
App.js:16 button click 當按鈕按下去時,就會重新執行 `App()`
所以才會出現 render 1
App.js:14 render 1
```
4. React 重新呼叫 App(),再 Render 一次,就是 Rerender。
### useState 傳入陣列,再搭配 `.map()`,把每一個 array element render 成 component。
* 最開始,沒有講 state 概念時:
```react=
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
```react=
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>
)
}
```
![](https://i.imgur.com/erWQ38g.png)
* `array.map()`
react 沒有迴圈,只能用 map,好處是可以把每一個 array element map 成 component。就是可以 render 很多component。
所以在 React 如果要 render 一系列的資料,就會使用 `.map` 來做。
```react=
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} />)
}
```
![](https://i.imgur.com/FcPFA0b.png)
錯誤訊息:
:::warning
Warning: Each child in a list should have a unique "key" prop.
:::
```javascript=
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` 時,需要產生 `key` 讓 `react` 去辨別是哪一個 item,先以`{index}` 為 key,但不推薦名稱命名為 `index`。
```react=
{
+ todos.map((todo, index)=> <TodoItem key={index} content={todo} />)
}
```
#### 當 React 判定 old statue = new state,就不會更新
:::danger
## statue is immutable
:::
#### 如何改 state?
```
function App() {
const [todos, setTodos] = React.useState([
123, 456, 555
])
}
const handleButtonClick = () => {
// 如何改變 state?
}
```
1. todos.push(777)
> 不能直接這樣改,要使用 `setTodos()` 改
2. 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。
> :::info
> ### State Immutable
> :::
3. setTodos([...todos, 777]) // 前面加上 todos element,後面再加上新的值。
> ```react=
> 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;
> ```
>
> ![](https://i.imgur.com/W0ZgBRT.gif)
>
> :::success
> 不應該去修改 state (immutable, like todos) 的內容,應該是透過 `setTodos` 去修改內容,產生一個新的陣列,然後再 render 到頁面上。
> <br>
> 另外,也可以改成:
> `import {useState} from 'react';`
> 直接解構
> :::
## [FE302] 2-4 [再探 State]
:::info
state 分成兩種:
1. controlled:useState
2. uncontrolled
:::
複習 useState,
```react=
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
:::success
## 所有在 UI 上會動的東西 幾乎都是個state
:::
![](https://i.imgur.com/4SCujef.png)
其實在輸入 `qwer` 時,也會在 React 的 state,可以在自己的裡面控制。,如下範例的
```react=
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;
```
![](https://i.imgur.com/5aYYUOV.png)
無法輸入任何字母、數字進去,沒辦法改變這個 state
但是輸入了,什麼都沒有改變,因為 state 沒有變,還是 `''` 空字串
state 還是狀態不會變(immutable),要怎麼改變 state 呢?
可以在 `<input type='text '>` 裡面加入 `onChange` 的事件。
```
+ <input type='text' placeholder='new todo' value={value} onChange={handleInputChange}/>
```
:::info
`onChange={handleInputChange}`
`onCLick={handleButtonClick}`
命名慣例:handle 開頭。
:::
那加上了 `onChange={handleInputChange}`,就要來實作功能:
```react=
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 印出來
![](https://i.imgur.com/lqBrZ3F.png)
現在要改成 `setValue` 改變 state 的值:
```react=2
function handleInputChange(e) {
+ setValue(e.target.value)
}
```
![](https://i.imgur.com/hjy3cO6.png)
今天輸入 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 官方教學
![](https://i.imgur.com/4D37oKc.png)
### 什麼是 component?props?
其中前面提到:`{props.children}`,不太懂為什麼要使用 `props.children`。
所以從[Components 與 Props](https://zh-hant.reactjs.org/docs/components-and-props.html)介紹起:
> ### Component 使你可以將 UI 拆分成獨立且可複用的程式碼,並且專注於各別程式碼的思考。本章節旨在介紹 component 的相關概念,你也可以在此參閱[詳細的 API 文件](https://zh-hant.reactjs.org/docs/react-component.html)。
> 概念上來說,component 就像是 JavaScript 的 function,它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。
> from: [Components 與 Props](https://zh-hant.reactjs.org/docs/components-and-props.html)
#### component 組件
就很像 CSS 切版的概念,將各小區塊的 UI寫在 JavaScript,還可以有著 JavaScript 的輔助,套用到程式的邏輯。
#### 如何建立一個 component?
建立一個 component 最簡單的方法就是
> 定義 component 最簡單的方法即是撰寫一個 Javascript function:
```javascript=
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,沒輸入參數,所以使用的時候,就會如以下情形:
```javascript=
ReactDOM.render(
<Welcome />, // DOM 標籤的 React element
document.querySelector()
)
```
如果今天想要替換 Welcome component 裡面的變數的話,就可以這樣寫:
`props` 是指`prperties` 屬性,這個用 function Welcome 符合 React component 的規則,也回傳了一個 `React element`(`<h1> hello, {props.name}</h1>`),其中 `properties` 為屬性。
```javascript=
// 建立一個 component
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
```
> 當 React 看到由使用者定義 component 的 element 時,它將 JSX 屬性和 children 作為 single object 傳遞給該 component。我們稱這個 object 為「props」。
這邊自定義一個 `name` 的屬性(properties),我覺得應該是長相挺像 `HTML 標籤`,然後寫在標籤裡面的名稱,很像是自定義的屬性,
```javascript=
const element = <Welcome name="Sara" />; // React element 也可以是使用者自定義的 component
```
然後將 `React element` 放到 `ReactDOM.render()` 的 JSX 裡面:
```javascript=
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)。
```javascript=
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')
);
```
:::info
1. component 命名慣例:大寫鴕峰
2. component 使用:
(1) 建立:利用 function component or class component(ES6)定義跟建立一個 component
(2) 使用:`<Welcome />`,如果有輸入參數的話就是像以上的範例:`<Welcome name='Sara' />`,
(3) 使用方法有幾種:
- 直接放到 `ReactDOM.render()` 的 JSX 裡面。
- 或者是再放到另一個 Component 內。
:::
![](https://i.imgur.com/vjx5qGD.png)
上面提到了怎麼使用 Component 並 render 到頁面上,就像是上圖的 `TodoList` 只有一個 component。
不過前面提到其實 `component` 就像是 UI 一小塊一小塊的切版那樣,所以 `component` 裡面可能還會有 `component`,然後要靈活運用相似的區塊,讓裡面的 component 也要可以輸入參數改變值,所以下面需要學習怎麼讓 component 裡面還有 component 並可以傳入參數,就像是上圖的 todolist 在待辦清單的部分,有待辦事項、刪除按鈕、新增的輸入框等,由幾個小組件組成。
下面以[React 官方文件的範例為練習](https://zh-hant.reactjs.org/redirect-to-codepen/components-and-props/composing-components)
### 概念矯正
#### react element
```javascript=
// 宣告一個 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 + 2`、`user.firstName` 以及 `formatName(user)`
> -- From:[React:介紹 JSX](https://zh-hant.reactjs.org/docs/introducing-jsx.html)
另外在[從 Hooks 開始,讓你的網頁 React 起來系列 第 5 篇](https://ithelp.ithome.com.tw/articles/10218644)也提到:
> 我們也可以把 JSX 的內容抽成一個 JavaScript 變數,只需要把 JSX 的內容用小括號(`()`)包起來就好
但此時就在想,為什麼可以用 `()` 包起來呢?在[React 介紹 JSX]()中有提到:
> 為了方便閱讀,我們將 JSX 拆成很多行表達。雖然這並不需要,我們建議將多行 JSX 包在括號中來避免遇到自動分號補足的麻煩。
舉例:在第 10 ~ 14 行,可以發現將 JSX 「**多行**」內容(JSX內容:`<h1>Hello, {formatName(user)}!</h1>`)以`()` 包起來,這樣**可以省去自動分號補足的麻煩**
```javascript=
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)) 的回傳值`
:::info
小結:
1. JSX 可以將 HTML 等等的內容抽出,以`()` 包覆,並令成變數,進階一步在 JSX 的內容裡,利用 `{}` 來插入一些 JavaScript 的變數(如:`<h1>Hello {name}</h1>`或者是 function裡回傳的值(如:`<h1>hello functionName(user))</h1>`。
:::
#### JSX 本身也是 expression
在經過編譯的 JSX expression 會變成 JavaScript 的 function,表示也可以使用 `if`、`for`中使用 JSX:
```react=
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
```
#### 在 JSX 中指定屬性
可以在 JSX 指定 HTML tag 內的屬性(attribute),有兩種方法:
1. 如同 HTML attribute 一樣輸入「字串」
```react=
const element = <div tabIndex="0"></div>;
```
2. 在屬性中使用大括號來嵌入一個 JavaScript expression
```react=
const element = <img src={user.avatarUrl}></img>;
```
:::danger
不建議在 JSX 內的屬性混用 `{}` 及 `"string"`。
:::
:::warning
> 注意:
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 是:
```react=
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 介紹](https://zh-hant.reactjs.org/docs/introducing-jsx.html)
舉例:
```react=
const element = (
<h1 className="greeting">
Hello, World!
</h1>
);
```
等同於以下:
```react=
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, World!'
);
```
感覺使用 `React.createElement` 是將其拆解,然後[官方文件](https://zh-hant.reactjs.org/docs/introducing-jsx.html)上寫說會「會進行一些檢查以幫助你寫出沒有 bug 的程式」,會產生類似的物件(下面這個已經簡化過):
```react=
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」。
```react=
// 要 render 一個 element 到 root DOM node ,就傳入 element 及 DOM node 兩者到 ReactDOM.render()
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
```
### `React element` 是 `immutable`,那要怎麼更新?
> 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](https://www.robinwieruch.de/react-function-component#react-function-component-props)
###### tags: `MTR05` `Lidemy 學習筆記`