---
# System prepended metadata

title: React - 三大屬性 Refs
tags: [React]

---

---
title: React - 三大屬性 Refs
tags: React
date: 2021/8/4
---

###### tags: `React`

###### *date: 2021/8/4*

# ⚛️ React - 三大屬性 **Refs**

[TOC]

## React 起手式

- 老規矩，先建立一個 HTML，並搭建好環境

    :::spoiler 還不知道起手式請點我

    ```htmlembedded=
    <div id="container"></div>

    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>

    <script type="text/babel">

    </script>
    ```

    :::

## 創建組件

1. 在 script 中，要引用 React 的三大屬性，必須使用類式組件。

    ```jsx=+
    class Dream extends React.Component {
        render(){
            // 需注意標籤閉合
            return (
                <div>
                    <input type="text" placeholder="輸入你的夢想"/><br/>
                    <button>點擊</button>
                </div>
            )
        }
    }
    ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
    ```

    ![show](https://i.imgur.com/Li5QE8n.png)
    *畫面呈現*

2. 幫按鈕新增點擊事件

    ```jsx=10
    class Dream extends React.Component {
    
        // 使用賦值語句 + 箭頭函式
        showDream = () => {
            console.log( '你點擊按鈕了!!!' );
        }
    
        // 注意 onClick 為 React 給的方法，需注意大小寫
        render(){
            return (
                <div>
                    <input type="text" placeholder="輸入你的夢想"/><br/>
                    <button onClick={ this.showDream }>點擊</button>
                </div>
            )
        }
    }
    ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
    ```

    ![click](https://i.imgur.com/UL7nww9.gif)
    *畫面呈現*

3. 讓點擊事件能過獲取到`input`裡面的值
    - 原生 JS 是利用`document.getElementById('id_name')`
    - jQuery 則是利用`$('#id_name')`
    - React 本身也有提供 ref 來取得值

## Refs 取得的三種方式

### 字串型態的 Ref ( 不推薦：未來 React 可能會廢棄掉 :-1:)

- 在要取得的節點設置一個`ref="your_id"`
- 並利用`this.refs`取得節點

```jsx=10
class Dream extends React.Component {

    showDream = () => {
        // 取得節點
        const { dream } = this.refs
        alert( `祝你 ${dream.value} 夢想成真` )
    }
    // 在 input 裡添加 ref="dream"
    render(){
        return (
            <div>
                <input ref="dream" type="text" placeholder="輸入你的夢想"/><br/>
                <button onClick={ this.showDream }>點擊</button>
            </div>
        )
    }
}
ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
```

![alert](https://i.imgur.com/zqncGMd.gif)

### 回調形式的 Ref

- 使用回調形式的箭頭函數取得當前節點`( currentNode ) => { this.your_id = currentNode }`
- 因為使用回調形式，所以可以透過 this 取得屬性

```jsx=10
class Dream extends React.Component {

    showDream = () => {
        const { dream } = this
        alert( `祝你 ${dream.value} 夢想成真` )
    }
    // 在 input 裡使用回調形式的箭頭函數取得節點
    // 利用箭頭函數的 this 向外找的特性綁定到實例自己身上
    // 箭頭函數左側如果只有一個參數，可以省略括弧
    // 箭頭函數右側如果只有一句函數，可以省略括弧
    render(){
        return (
            <div>
                <input 
                    ref={ c => this.dream = c } 
                    type="text" 
                    placeholder="輸入你的夢想"/>
                <br/>
                <button onClick={ this.showDream }>點擊</button>
            </div>
        )
    }
}
ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
```

==注意==：
> 如果是使用行內函數的回調方式，會再更新頁面時，行內回調函數會被呼叫兩次，第一次是 null，第二次就是用 DOM，這過程是為了清空函數並重新賦予，大多數情況下並不會有什麼影響，如果想避免出現這個情況，可以用類綁定的函數解決
> [name=React - Refs & DOM]

#### 類綁定的回調函數

- 這樣更新畫面也不會一直呼叫函數了

```jsx=10
class Dream extends React.Component {

    showDream = () => {
        const { dream } = this
        alert( `祝你 ${dream.value} 夢想成真` )
    }
    // 類綁定的回調函數
    saveDream = (c) => {
        this.dream = c
    }
    
    render(){
        // 改成使用函數
        return (
            <div>
                <input 
                    ref={ this.saveDream } 
                    type="text" 
                    placeholder="輸入你的夢想"/>
                <br/>
                <button onClick={ this.showDream }>點擊</button>
            </div>
        )
    }
}
ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
```

### createRef 的使用 ( 目前 React 最推薦使用的方式 :+1:)

- 使用 createRef 創建一個容器，並掛在實例自身
- 容器跟 id 一樣，同個名字只能設定一個

```jsx=10
class Dream extends React.Component {
    // 使用 createRef() 創建容器
    dreamRef = React.createRef()
    
    showDream = () => {
        const { dreamRef } = this
        // 必須使用 current 來取得當前節點
        alert( `祝你 ${dreamRef.current.value} 夢想成真` )
    }
    
    render(){
        // 改成使用 createRef 的方式
        return (
            <div>
                <input 
                    ref={ this.dreamRef } 
                    type="text" 
                    placeholder="輸入你的夢想"/>
                <br/>
                <button onClick={ this.showDream }>點擊</button>
            </div>
        )
    }
}
ReactDOM.render( <Dream/>, document.getElementById( 'container' ) )
```

## 總結 Refs

1. refs 最簡單的寫法就是字串形式寫法 ( 不推薦使用 :-1: )
2. 回調形式的 refs 雖麻煩一點，但比較不會出現奇怪的錯誤
3. createRef 雖最麻煩，但官方網站最推薦使用

### 參考資料

- [React 官網 - Refs & DOM](https://zh-hant.reactjs.org/docs/refs-and-the-dom.html)
- [React 全家桶](https://www.youtube.com/playlist?list=PLmOn9nNkQxJFJXLvkNsGsoCUxJLqyLGxu)
