前一篇介紹了基本組件,今天來介紹 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})) } 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
Style.module.scss
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 直接放到組件處理
首先安裝
命名後當成標籤來使用
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;
|
🚀實體工作坊分享
最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!
🚀線上課程分享
線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。
Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住
如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!
Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。