筆記 - React Intro (1) Components

正式進入 React 的世界! 依照 React 官網 React Docs BETA 內容學習。

規則很多,筆記下來~避免搞混。

Your First Component

  • What a component is
  • What role components play in a React application
  • How to write your first React component

React component 是可以添加 markup 語言(例如 HTML)的 Javascript function。

建立 component 三步驟

  1. Export the component: export default 之後可以在其他地方 import 後使用
  2. Define the function: function Profile() { } React component 命名一定大寫開頭
  3. Add markup: return <img /> tag. JSX 語法,很像 HTML,可以讓我們在 JavaScript 嵌入 markup 語法(實際上還是 JavaScript)。 如果 return 多行,要用小括號包起來

使用 component

定義好的 component 可以 nest inside other component 中。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
/>
);
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
  • <section> tag 是小寫,所以 React 會知道這是 HTML tag
  • <Profile /> 是大寫開頭,所以 React 知道這是名為 Profile 的 component

Profile 元件裡面包含 HTML <img />,所以最終瀏覽器會看到以下:

1
2
3
4
5
6
<section>
<h1>Amazing scientists</h1>
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
<img src="https://i.imgur.com/MK3eW3As.jpg" alt="Katherine Johnson" />
</section>

注意事項

Nesting and organizing components:

  1. component 是 js function,所以一個檔案可以包含複數個 components
  2. 在上面的範例中,ProfileGallery 裡面被渲染,所以稱 Gallery 為父元件 (parent component), Profile 為其子元件(child component)。
  3. component 中可以渲染其他 component,但不要在裡面寫定義,如同 js function,一個 function 定義一個行為就好。如果 parent 要傳參數給 child,後續會說明如何傳遞。

Importing and Exporting Components

  • What a root component file is
  • How to import and export a component
  • When to use default and named imports and exports
  • How to import and export multiple components from one file
  • How to split components into multiple files

The root component file

npx create-react-app my-app 建立專案的話,root component file 為 src/App.js,如果使用其他框架(ex. Next.js) 就會不同。

Exporting and importing a component

當專案規模變大,元件變多時,可以分離元件,使其模組化更容易重複使用。當想要把 component 分離到新的檔案時,有三個步驟:

  1. 建立一個新的 .js file,把 component 放進去
  2. 將 function component export 出去 (default 或 named exports 皆可)
  3. 在要使用的檔案中 import component (根據之前是 default or named exports 寫法些許不同)

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 新建立 Gallery.js
function Profile() {
return <img src="https://i.imgur.com/QIrZWGIs.jpg" alt="Alan L. Hart"/>
}

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}

// App.js
import Gallery from './Gallery.js'
import { Profile } from './Gallery.js'

export default function App() {
return (
<Gallery />
);
}

Default vs named exports 寫法注意事項

原則:一個檔案可以有多個 named exports 但是只能有一個 default export。

寫法:

Syntax Export statement Import statement
Default export default function Button() {} import Button from './button.js'
Named export function Button() {} import { Button } from '.button.js'

Note:

  1. import 檔案名稱 './button.js'有無寫副檔名皆可,但有寫比較符合 native JavaScript Module 規則。
  2. default import 後面可以改名字,例如 import Banana from './button.js',但在 named imports 兩邊名字要相同。
  3. 通常如果一個檔案中只有一個 function component 要 export, 就使用 default exports;如果有多個 components 或 values 要匯出,就使用 named exports。
  4. 不要使用匿名函式 export default () => {}

Exporting and importing multiple components from the same file

上方例子中,若想將 Profile component 也匯出作為 App 的子元件,修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Gallery.js
export function Profile() {
return <img src="https://i.imgur.com/QIrZWGIs.jpg" alt="Alan L. Hart"/>
};

export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}

// App.js
import Gallery from './Gallery.js'

export default function App() {
return (
<Gallery />
);
}

Writing Markup with JSX

  • Why React mixes markup with rendering logic
  • How JSX is different from HTML
  • How to display information with JSX

以前網頁分成 HTML 頁面內容, CSS 頁面外觀, JavaScript 邏輯三個檔案,隨著網頁的互動性需求提升,邏輯逐漸掌管了內容,可以說是 JavaScript 主導 HTML 內容,所以 React 將渲染邏輯與 markup 都放在同個地方:component 之中。

React component 是包含了 rendering logic and markup 的 JavaScript function。使用 JSX syntax extension 讓我們在 js 檔案中寫 markup。JSX 長得很像 HTML, 但稍微比較嚴格,且可以顯示動態資訊。

Converting HTML to JSX

假設目前有一些 HTML 想要塞到 component 中,直接貼在 return () 裡面是會 error 的,需要 follow JSX 的規則:

Return a single root element

React component function 中只能 return 一個元素,所以用 <div></div> ,若位置不適合放 div 時(例如tbody裡面)或其他原因不想出現在 HTML tree 時,則可使用空的 tag <> </> (稱作Fragment)。

THINK: 為什麼 JSX 要把 tag 打成一包才能 return 呢?

JSX 雖然長得像 HTML,但他終究是會被轉成 JavaScript objects 的。一個 function 也不能 return 多個 objects,所以要用另一個 tag 或 Fragment 打包起來。

Close all the tags

JSX 嚴格規定 tag closing 的部分

  • self-closing tags: <img />
  • wrapping tags: <li> ... </li>

camelCase all most of the things!

JSX 會轉成 JavaScript 而 JSX attribute name 會成為 JavaScript object 的 key,注意 class 為 reserved word, 所以在 React 中寫 className 做代替。

aria-*data-* 還是保持與 HTML 同樣用 dash 連接。

也可以試試 JSX Converter


Javascript in JSX with Curly Braces

  • How to pass strings with quotes
  • How to reference a JavaScript variable inside JSX with curly braces
  • How to call a JavaScript function inside JSX with curly braces
  • How to use a JavaScript object inside JSX with curly braces

Passing strings with quotes

JSX 中,屬性值字串可以用單或雙引號(''""),如果是變數就用單大括號 {} 包。

Using curly braces: A window into the JavaScript world

{}除了變數,可以放任何的 JavaScript expression,下面例子為 function formatDate():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const today = new Date();

function formatDate(date) {
return new Intl.DateTimeFormat(
'en-US',
{ weekday: 'long' }
).format(date);
}

export default function TodoList() {
return (
<h1>To Do List for {formatDate(today)}</h1>
);
}

// To Do List for Sunday

使用 {}注意事項:

  1. 當成文字使用在 JSX tag 內,tag 本身不行。例如 <h1> {name}'s todo </h1> 可以,<{tag}> 不行。
  2. 當成屬性放在 = 後面。例如 scr={avatar}

Using double curly braces: CSS and other objects in JSX

JavaScript object 也可以放到 JSX 中,object 本身就用 {} 包著,例如 { name: "Hedy Lamarr", inventions: 5 },所以在使用的時候,就會寫兩個括號 person={{ name: "Hedy Lamarr", inventions: 5}}

如果有需要的話,JSX 中也可以使用 inline CSS styles,使用時也是將屬性用 object 傳入 style 中。

要注意的事為 inline style properties 要用 camelCase 寫。HTML 中會寫 style="background-color: black", JSX 中寫 style="backgroundColor: black"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function TodoList() {
return (
<ul style={
{
backgroundColor: 'black',
color: 'pink'
}
}>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
);
}

總之在 JSX 看到雙花括,不是 JavaScript object 就是 inline CSS style 屬性值。

綜合應用

JavaScript object person 包含 name 字串以及 theme 物件。用 {person.name} 傳入字串,用 {person.theme} 傳入 inline CSS style 屬性物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const person = {
name: 'Gregorio Y. Zara',
theme: {
backgroundColor: 'black',
color: 'pink'
}
};

export default function TodoList() {
return (
<div style={person.theme}>
<h1>{person.name}'s Todos</h1>
<img
className="avatar"
src="https://i.imgur.com/7vQD0fPs.jpg"
alt="Gregorio Y. Zara"
/>
<ul>
<li>Improve the videophone</li>
<li>Prepare aeronautics lectures</li>
<li>Work on the alcohol-fuelled engine</li>
</ul>
</div>
);
}

Passing Props to a Component

  • How to pass props to a component
  • How to read props from a component
  • How to specify default values for props
  • How to pass some JSX to a component
  • How props change over time

Props (應該)就是 properties 的縮寫

Familiar props

Props 是傳遞給 JSX tag 的資訊,例如可以傳 className, src, alt, width, height<img> tag,基本上與 HTML tag 相同。

在 React 中,可以傳入任何 properties 給 component。

Passing props to a component

Profile component pass some props to its child component, Avatar.

Pass props to the child component

此範例中,父元件 Profile 傳兩個 props: person (an object), and size (a number) 給子元件 Avatar:

1
2
3
4
5
6
7
8
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

傳入後,在 Avatar component 中可以讀到 props 的值。

Read props inside the child component

function Avatar 後面列出要讀取的 props 名稱,用({ }) 包起來,像是函數變數一樣。

1
2
3
4
5
6
7
8
9
10
11
12
function Avatar({ person, size }) {
// person and size are available here
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
)
}

props 是 React component function (only) argument,所以也可以寫成下面的方式,就不用列出所有 props 裡面的名字(destructuring):

1
2
3
4
5
function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

Specify a default value for a prop

想給 prop 一個預設值,可以這樣寫:

1
2
3
function Avatar({ person, size = 100 }) {
// ...
}

如果 <Avatar person={...} /> 渲染時沒有 size 的屬性值時,就會預設為 100,避免出現 size={undefined}

Forwarding props with the JSX spread syntax

當 component 要傳 所有 的 props 給其 child component 時,可以用 spread syntax 來減少重複的 code。

1
2
3
4
5
6
7
8
9
10
11
12
function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

寫成

1
2
3
4
5
6
7
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

Passing JSX as children

像是 HTML tags 會 nesting 一樣,有時也會將 components nesting

1
2
3
<Card>
<Avatar />
</Card>

When you nest content inside a JSX tag, the parent component will receive that content in a prop called children. For example, the Card component below will receive a children prop set to <Avatar /> and render it in a wrapper div:

children props 這個寫法在 visual wrappers (like panels, grids) 很常使用,可以想成 Card 裡面有個 children 的洞,可以放入 Avatar, text 等不同內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// @App.js
import Avatar from './Avatar.js';

function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}

export default function Profile() {
return (
<Card>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</Card>
);
}

// @Avatar.js
import { getImageUrl } from './utils.js';

export default function Avatar({ person, size }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}

How props change over time

prop 值不能改變,但 component 可動態從 parent component 接收新的 prop object。

如果要想要 component 隨著使用者輸入而改變,會需要 set state(後續學習)。