React.js - 太弱的我,把 Hooks 點滿就對了

都 2020 年了,還不趕快用 Hooks 參加勾肥大戰?

React 在 16.8 版之後納入 Hook 的功能。

 

 

 

前言

去年還在研究所打混,經過老闆再三囑咐。對於額外技術也暫時停止學習。

身為一位學術研究員,不專注於非研究領域的程式發展,也很合邏輯 QQ。

 

目前 CRA ( create-react-app ) 所使用的版號,也來到 16.12.0,

學習永遠不嫌遲,趁著一年國軍 online,趕緊補一下!

 

本篇文章僅簡單介紹、筆記,建議大家可以閱讀:

  1. React 介紹 Hook
  2. 阮一峰 - React Hooks 入门教程

 

 

 

1. Hooks 介紹

React 在 2019 年 2 月 Release v16.8.0

該版本推出了 Hooks ,鉤子 (?

可以讓我們在 functional component 進行一些 state 的控管。

 

儘管 Angular 已經來到 8.3 ...

儘管 Vue 已經來到 2.6 ...

但 Hooks 的名詞,勢必會影響不少生態圈(X

春秋百家爭鳴 大家抄來抄去 到底誰有道理 誰的比較高明

 

你知道嗎?Hooks 其實就是希望能像鉤子一樣,可以隨手勾取所需工具、物品。

 

 

 

2. Functional Component

建議讀者可以直接拿官方的專案建置練習:create-react-app

 

在使用之前, Hooks 都是建構在 functional component 上,

因此建議至少先看過長什麼樣子:

import React from 'react';

function App() {
  return (
    <div>
      Hello React With Hooks
    </div>
  );
}

export default App;

 

當然你也可以選擇使用 ES6 const 進行 arrow function 宣告:

import React from 'react';

const App = () => {
  return (
    <div>
      Hello React With Hooks
    </div>
  );
}

export default App;

 

注意! 好你現在看過了,可以繼續看下去囉!

 

 

 

3. Hooks 鉤鉤種類

ReactHooks 基本上常見的包含:

  • useState
  • useContext
  • useReducer ( 本篇不介紹不是懶是忙投票 )
  • useEffect ( 本篇不介紹不是懶是忙投票 )

 

useState

setState 是同樣意思的,useState 內直接放入初始值即可。

返回陣列的第一個為當前值,第二個 setUser 則是用於設定。

import React, { useState } from 'react';

function App() {

  const [user, setUser] = useState('Anonymous');

  return (
    <div>
      <h1 onClick={() => setUser('Robby')}>
        Hi,
        <small>{user}</small>
      </h1>
    </div>
  );
}

export default App;

 

你知道嗎?設定值的變數名稱,以最佳實踐來說,大多使用 setUser, setData 等,駝峰式的命名。

 

 

useContext

如果我們要從父組件傳值給子子孫孫等組件,通常要 props 很多層,

而你的專案可能又包了一堆 HOC,例如:

export default withTranslation()(connect(mapStateToProps)(Todo));

 

嚴重時會破壞你的結構,大 guy 會變成下麵這葛樣子, ( 俗稱 Wrapper Hell ):

Image

 

如今!!!

只要你會丟出鉤子,稍微彎拉一下...

 

就能實現並解決這葛垢病!

請搭配著下方 createContext 章節閱讀。

 

 

createContext

在使用 useContext 之前,要先認識 createContext,它是用來創建 ContextProvider 。

Context ??? 

Provider ???

 

以下使用常見的套件來比喻:

  1. react-redux 的 Provider
  2. styled-components 的 ThemeProvider 
如果這比喻不太明白,簡單說就是要從頂部灌水泥,灌資料下去喇 QQ

 

筆者簡單舉例父子組件傳遞值範例:

import React from 'react';

const name = 'Robby';

function App() {

  return (
    <div>
      <User name={name} />
    </div>
  );
}

function User(props) {
  return (
    <div>{props.name}</div>
  );
}

export default App;

 

現在將試著使用 createContex 改寫看看:

引入 createContex,宣告變數 UserContext,其參數就是初始值。

import React, { createContext } from 'react';

const name = 'Robby';

const UserContext = createContext(name);

 

接著於父子組件中使用:

在 UserContext 中,提供了 Provider 以及 Consumer

  1. Provider:用於父層,並利用 value,來塞入指定的初始值
  2. Consumer:顧名思義,就是用於接收子組件,內容為回傳一個 function

 

請務必命名為 value,不可為其他屬性名稱。

function App() {
  return (
    <UserContext.Provider value={name}>
      <User />
    </UserContext.Provider>
  );
}

function User() {
  return (
    <UserContext.Consumer>
      {name => name}
    </UserContext.Consumer>
  );
}
  • Provider 內需放入一個指定的 component,因此這邊建立一個 User 組件。
  • Consumer 內需放入一個 function,因此這邊使用一個簡易的 arrow function

 

 

過度謹慎的你可能會想改寫...

 

沒錯,你也可以拉出來再寫一個 const arrow function

function User() {

  const renderName = name => {
    return name;
  }

  return (
    <UserContext.Consumer>
      {renderName}
    </UserContext.Consumer>
  );
}

 

需要注意的是,不可以在 Consumer 同層額外寫其他東西。

但你可以在 Consumer 組件中寫其他元素。

 

 

過度謹慎的你可能會不相信...

 

沒錯,如果你用以下方式寫,是有問題的。
return (
  <UserContext.Consumer>
    {renderName}
    <div>123</div>
  </UserContext.Consumer>
);

 

沒錯,如果你用以下方式寫,是沒問題的。
return (
  <div>
    <p>Hi, my name is</p>
    <UserContext.Consumer>
      {renderName}
    </UserContext.Consumer>
  </div>
);

 

-

 

 

過度謹慎的你可能會越想越不對勁...

 

一開始已經在 createContext 設定過初始值:

const UserContext = createContext(name);

為何在又要在 UserContext.Provider 內設定 value 初始值?

<UserContext.Provider value={name}>

 

:「沒錯親愛的,是多寫了。」

 

如果父層省略掉 Provider ...,

那就是直接以當初 createContext 的初始值為主,

否則會優先以 Providervalue 為主。

import React, { createContext } from 'react';

const name = 'Robby';

const UserContext = createContext(name);

function App() {
  return (
    <User />
  );
}

function User() {
  return (
    <UserContext.Consumer>
      {name => name}
    </UserContext.Consumer>
  );
}

export default App;

 

所以上述範例才故意這樣寫兩次

 

-

 

 

過度謹慎的你可能會想省時間...

 

每次都寫 UserContext.Consumer 也太大坨麻煩了吧?

 

:「沒錯親愛的,終於可以使用 useContext 了!」

 

useContext 必須放入經由 createContext 所建立的變數。

由於當初設定的 name 為字串,因此勾出來的時候,當然也就是字串囉!

import React, { createContext, useContext, Fragment } from 'react';

const name = 'Robby';

const UserContext = createContext(name);

function App() {
  return (
    <User />
  );
}

function User() {
  const name = useContext(UserContext);
  return (
    <Fragment>
      {name}
    </Fragment>
  );
}

export default App;

 

你知道嗎? 組件不想用任何元素包住子元素時, 可以使用 Fragment (片段) ,避免冗余的元素。

 

 

 

 

礙於時間不早,加上趕著早起投票。

本篇僅教學到 useStateuseContextcreateContext 應用。

 

為了不讓大家覺得筆者本篇... 過於敷衍過於廢文

最後提供一葛整合今日所學的奇淫技巧

 

你知道嗎?奇淫技巧不一定代表是好的作法。

 

 

 

4. useState+useContext+createContext

前面學到的 useContext,依照本篇的內容,目前僅能讀取,卻不能改變。

大多的使用方式為: useContext + useReducer

有興趣的人,建議可以先利用「神O海O」詢問看看有沒有文章可以看。

 

 

接著,來為大家介紹這種邪門歪道殘害新人的範例應用,

參照剛剛的範例,我們繼續修改:

 

首先在上面 import 我們要使用的鉤鉤,

接著將 createContext 初始值改為 null, 因為等等要改在 component 內初始化。

import React, { createContext, useContext, useState, Fragment } from 'react';

const UserContext = createContext(null);

 

接著我們利用 useState,建立一葛 namestate

並使用 Provider 將初始值設定為陣列 [name, setName]

function App() {
  const [name, setName] = useState('Robby');

  return (
    <UserContext.Provider value={[name, setName]} >
      <User />
    </UserContext.Provider>
  );
}
你知道嗎?為了方便管理,可以使用物件,將 [name, setName] 用物件包起來,讀取時就會是物件!

 

 

最後在子組件同樣使用 useContext 提取出來。

就可以進行讀取,以及使用 setName 修改囉~

function User() {
  const [name, setName] = useContext(UserContext);

  return (
    <Fragment>
      {name}
      <br />
      <button onClick={() => setName('Jack')}>ChangeName</button>
    </Fragment>
  );
}

 

 

完整程式碼如下:

import React, { createContext, useContext, useState, Fragment } from 'react';

const UserContext = createContext(null);

function App() {
  const [name, setName] = useState('Robby');

  return (
    <UserContext.Provider value={[name, setName]} >
      <User />
    </UserContext.Provider>
  );
}

function User() {
  const [name, setName] = useContext(UserContext);

  return (
    <Fragment>
      {name}
      <br />
      <button onClick={() => setName('Jack')}>ChangeName</button>
    </Fragment>
  );
}

export default App;

 

雖然上述做法可以達到 Context 的修改,不過很少這樣使用。

你知道嗎?筆者沒有特別這樣使用過... 是不是很 GY

 

 

因為基於 useState 所產生的值,應由所屬的 component 控管。

因此正規來說,還是會以 useContext + useReducer 的方式進行管理。

 

-

 

 

最後

什麼是勾肥大戰?你不知道!?太弱惹喇!

「勾肥大戰」的圖片搜尋結果

 

 

那麼就先這樣,投票去,ㄅㄅ!

 

有勘誤之處,不吝指教。ob'_'ov