Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

React組件應用

React

前一篇介紹了基本組件,今天來介紹 React 的組件應用吧!

Props

props 除了傳值以外,還可以設定預設值、預設型別
範例: 簡單的計數器功能
外層組件傳入 initCount,內層接 props,並設定 props 的預設值 defaultProps、預設型別 PropTypes
如外層沒傳 initCount,將會顯示預設值
如外層傳入型別與預設型別不同,將會跳出錯誤警告

Index.js

1
2
3
4
5
6
7
import React from "react";
import { createRoot } from 'react-dom/client';
import Counter from "./Counter";

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<Counter initCount={10} />);

Counter.js

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
import { Component } from 'react';
import { PropTypes } from "prop-types";

class Counter extends Component {
static defaultProps = {
initCount: 20
}
static propTypes = {
initCount: PropTypes.number
}
constructor(props) {
super(props);
this.state = {
count: props.initCount
}
}
addCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.addCount}>+1</button>
</div>
);
}
}

export default Counter;

你也可以將 defaultProps、propTypes 設定在 Component 外面,將會得到一樣的結果

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
import { Component } from 'react';
import { PropTypes } from "prop-types";

class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: props.initCount
}
}
addCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.addCount}>+1</button>
</div>
);
}
}

export default Counter;

Counter.defaultProps = {
initCount: 20
}
Counter.propTypes = {
initCount: PropTypes.number
}



setState

setState 可傳入 function
setState 是非同步操作,不能期待連續的 setState 內容會立刻更新,
因此我們可以在 setState,將原本傳入的 obj 改成傳入一個函式
這個函式參數吃的是本來的 state,(state) => {} 裡面回傳 return{ } 一個新的物件
新的物件就是新的 state = 原來的 state.count+1

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
import { Component } from 'react';

class Counter extends Component {
state = {
count: 0
}
addCount = () => {
this.setState((state) => ({count: state.count + 1}))
// 等於
// this.setState((state) => {
// return {
// count: state.count + 1
// }
// })
}
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.addCount}>+1</button>
</div>
);
}
}

export default Counter;

setState 可傳入 callback

因 setState 為非同步,為了確保在 state 變更後執行想要的動作,可在 setState 第二個參數傳入callback

1
setState(變更 state 的物件或函式, callback);
1
2
3
4
5
6
7
8
9
10
this.setState({
count: this.state.count + 1,
},
() => {
this.sendCount();
},
);
sendCount = () => {
fetch(`/api/count?value=${this.state.count}`);
}



ref 屬性-指定 DOM 元素

指定 ref 有三種方法,以下為三種範例,其中官方推薦 createRef

情境: 在 input 還沒點擊時就自動 focus

回調模式

傳入function

1
2
3
4
5
6
7
8
9
10
11
12
class Ref extends Component {
setRef = (input) => {
input.focus();
}
render() {
return (
<div>
<input type="text" ref={this.setRef}/>
</div>
);
}
}

createRef

使用 react 提供的 createRef,綁定一個變數給 createRef()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component, createRef } from 'react';

class Ref extends Component {
myInput = createRef();
componentDidMount() {
this.myInput.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.myInput}/>
</div>
);
}
}

export default Ref;

舊版-字符串引用

使用字串引用,官方並不建議使用,因為字符串引用存在一些問題,被認為是遺留問題,並且可能會在未來的某個版本中被刪除

1
2
3
4
5
6
7
8
9
10
11
12
class Ref extends Component {
componentDidMount(){
this.refs.myInput.focus();
}
render() {
return (
<div>
<input type="text" ref="myInput"/>
</div>
);
}
}



組件的父子溝通

兩種溝通方式

父子雙向溝通
父傳子透過 ref 抓到子層的方法,子傳父透過 props
Parent.js

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
import React, { Component, createRef } from 'react';
import Child from './Child';
class Parent extends Component {
childRef = createRef();
state = {
count: 0,
};
addCount = () => {
this.setState({
count: this.state.count + 1,
});
};
addChildCount = () => {
this.childRef.current.addCount();
}
render() {
return (
<div>
<h1>Parent: {this.state.count}</h1>
<button onClick={this.addCount}>+Parent</button>
<button onClick={this.addChildCount}>+Child</button>
<Child ref={this.childRef} addParentCount={this.addCount}/>
</div>
);
}
}
export default Parent;

Child.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react';
class Child extends Component {
state = {
count: 0,
};
addCount = () =>{
this.setState({
count: this.state.count + 1
});
};
render() {
return (
<div>
<h2>Child:{this.state.count}</h2>
<button onClick={this.props.addParentCount}>+Parent</button>
<button onClick={this.addCount}>+Child</button>
</div>
);
}
}
export default Child;



父層傳給子層
所有屬性都在父層宣告,再透過 props 傳給子層
Parent.js

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
import React, { Component } from 'react';
import Child from './Child';
class Parent extends Component {
state = {
count: 0,
childCount: 0
};
addParentCount = () => {
this.setState({
count: this.state.count + 1,
});
};
addChildCount = () => {
this.setState({
childCount: this.state.childCount + 1,
});
}
render() {
return (
<div>
<h1>Parent: {this.state.count}</h1>
<button onClick={this.addParentCount}>+Parent</button>
<button onClick={this.addChildCount}>+Child</button>
<Child
count = {this.state.childCount}
addChildCount = {this.addChildCount}
addParentCount={this.addParentCount}/>
</div>
);
}
}
export default Parent;

Child.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react';
class Child extends Component {
render() {
const { count, addParentCount, addChildCount } = this.props;
return (
<div>
<h2>Child:{count}</h2>
<button onClick={addParentCount}>+Parent</button>
<button onClick={addChildCount}>+Child</button>
</div>
);
}
}
export default Child;



樣式控制 style & className

情境: 點擊 toggle 按鈕控制圖片開關

直接判斷 DOM 元素是否顯示
三元判斷式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react';

class Demo extends Component {
state = {
visible: true
}
toggle = () => {
this.setState({
visible: !this.state.visible
})
}
render() {
return (
<div>
<button onClick={this.toggle}>toogle</button>
<div>
{ this.state.visible ? <img src='/logo512.png' /> : null}
</div>
</div>
);
}
}

export default Demo;

也可以寫成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react';

class Demo extends Component {
state = {
visible: true
}
toggle = () => {
this.setState({
visible: !this.state.visible
})
}
render() {
return (
<div>
<button onClick={this.toggle}>toogle</button>
<div>
{ this.state.visible && <img src='/logo512.png' /> }
</div>
</div>
);
}
}

export default Demo;



用 style 控制是否顯示

在 JSX 裡 style 是一個物件,所以需要用物件的方式傳給 style,style 物件裡面有 display 屬性,可根據 state 裡面的值來控制 display 的值為 block 或是 none

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
import React, { Component } from 'react';

class Demo extends Component {
state = {
visible: true
}
toggle = () => {
this.setState({
visible: !this.state.visible
})
}
render() {
let showImg = { display: this.state.visible ? 'block' : 'none'}
return (
<div>
<button onClick={this.toggle}>toogle</button>
<div>
<img style={showImg} src='/logo512.png' />
</div>
</div>
);
}
}

export default Demo;

也可以直接在 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
import React, { Component } from 'react';

class Demo extends Component {
state = {
visible: true
}
toggle = () => {
this.setState({
visible: !this.state.visible
})
}
render() {
return (
<div>
<button onClick={this.toggle}>toogle</button>
<div>
<img style={{ display: this.state.visible ? 'block' : 'none'}} src='/logo512.png' />
</div>
</div>
);
}
}

export default Demo;



用 className 控制是否顯示

新增一支 CSS 寫入 class,再判斷 className

style.css

1
2
3
.hide {
display: none;
}
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
import React, { Component } from 'react';
import './style.css';

class Demo extends Component {
state = {
visible: true
}
toggle = () => {
this.setState({
visible: !this.state.visible
})
}
render() {
return (
<div>
<button onClick={this.toggle}>toogle</button>
<div>
<img className={this.state.visible ? '' : 'hide'} src='/logo512.png' />
</div>
</div>
);
}
}

export default Demo;



Component 三種組件

以計數器為範例,外層傳入 props 到內層
Progress.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react';
import ProgressBar from "./ProgressBar";

class Progress extends Component {
state = {
count: 0
}
addCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<ProgressBar value={this.state.count}/>
<button onClick={this.addCount}>add</button>
</div>
);
}
}

export default Progress;

Class Component

ProgressBar.js

1
2
3
4
5
6
7
8
9
10
11
import React, { Component } from 'react';
class ProgressBar extends Component {
render() {
const { value } = this.props;
console.count('render')
return (
<h1>{value}%</h1>
);
}
}
export default ProgressBar;

Functional Component (Stateless component)

沒有自己的 state、沒有自訂 method,props 當作參數傳入
ProgressBar.js

1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';
const ProgressBar = (props) => {
const { value } = props;
console.count('render')
return (
<h1>{value}%</h1>
);
}
export default ProgressBar;

Pure Component

和 class component 一樣,兩者差異主要在於『 效能 』
class component 和 functional component,即使你傳入同樣的值 (props 或 state) 還是會重新 render
Pure Component 的運作則是當你傳入同樣的值 (props 或 state) 或深層改變值時,它不會重新 render,
如果第一層的值有改變才會重新渲染,只比較第一層的方式稱為 Shallow Compare

ProgressBar.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { PureComponent } from 'react';

class ProgressBar extends PureComponent {
render() {
const { value } = this.props;
console.count('render')
return (
<h1>{value}%</h1>
);
}
}

export default ProgressBar;

如將 addCount 改成 +0,Pure Component 不會重新 render

Progress.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react';
import ProgressBar from "./ProgressBar";

class Progress extends Component {
state = {
count: 0
}
addCount = () => {
this.setState({
count: this.state.count + 0
})
}
render() {
return (
<div>
<ProgressBar value={this.state.count}/>
<button onClick={this.addCount}>add</button>
</div>
);
}
}

export default Progress;



CSS 模組

使用 CSS 或 SCSS 模組很簡單,只要在附檔名加 .module 就可以呼叫了,模組化可以避免不同檔案相同 className 的衝突
使用 SCSS 記得先安裝 sass

1
npm i sass

Style.module.scss

1
2
3
.btn {
color: red;
}

Btn.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react';
import style from "./Style.module.scss";

class Btn extends Component {
render() {
return (
<div>
<button className={style.btn}>132</button>
</div>
);
}
}

export default Btn;



styled-components

styled-components 可以將 CSS 直接放到組件處理

首先安裝

1
npm i styled-components

命名後當成標籤來使用

Btn.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react';
import styled from "styled-components";

const Button = styled.button`
width: 100px
`

const ButtonText = styled.span`
color: red
`

class Btn extends Component {
render() {
return (
<div>
<Button>
<ButtonText>132</ButtonText>
</Button>
</div>
);
}
}

export default Btn;



高階組件 HOC

Hihger-Order Components
以 Components 為輸入的 Components
return 傳入的 WrapperComponent,帶入共用邏輯的 props、自身的 props

不同組件套用相同邏輯

src/hocs/withOpen.js

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
import React, { Component } from 'react';

const withOpen = (WrapperComponent) => {
return class extends Component {
state = {
open: true,
}
toggle = () => {
this.setState({
open: !this.state.open
})
}
render(){
return (
<WrapperComponent
{...this.props}
open={this.state.open}
toggle={this.toggle}
/>
)
}
}
}

export default withOpen;

Card1.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';

class Card1 extends Component {
render() {
const { open, toggle } = this.props;
return (
<div>
<button onClick={toggle}>Card1</button>
{ open ? <h1>Card1</h1> : null}
</div>
);
}
}

export default Card1;

Card2.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react';

class Card2 extends Component {
render() {
const { open, toggle } = this.props;
return (
<div>
<button onClick={toggle}>Card2</button>
{ open ? <h1>Card2</h1> : null}
</div>
);
}
}

export default Card2;

App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';
import Card1 from "./Card1";
import Card2 from "./Card2";
import withOpen from "./hocs/withOpen";

const Card1withOpen = withOpen(Card1);
const Card2withOpen = withOpen(Card2);

const App = () => {
return (
<div>
<Card1withOpen/>
<Card2withOpen/>
</div>
)
}

export default App;


Hey!想學習更多前端知識嗎?

最近 Lala 開了前端課程 👉【實地掌握RWD - 12小時新手實戰班】👈
無論您是 0 基礎新手,又或是想學 RWD 的初學者,
我們將帶你從零開始,深入了解並掌握 RWD 響應式網頁設計的核心技術,快來一起看看吧 😊



🚀線上課程分享

線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。

Hahow

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️