Matsu

Nothing more than curiosity

Hooks家族 - useContext

Believe in yourself. You are braver than you think, more talented than you know, and capable of more than you imagine.
-Roy T. Bennett

昨晚聊聊關於useEffect的使用方式,今天要來介紹管理變數的問題。React發展得相當迅速,常常會看到不同的寫法,卻能達到一樣的結果。對於初學者的我來說,其實會常常搞混,反倒有點困擾,但是一想到當自己完全釐清各種不同寫法,能夠在適當的時機,選擇最適當的方式去處理同樣的問題就覺得很有意思,就好像身懷各種武藝。那,我們開始吧!

  • useContext
    • useContext使用方式
  • 結語

useContext

我們都對於React的運作方式有一定程度的理解,對於props的傳遞,我們更是了解。我們不僅可以將props往子組件傳遞,甚至能夠從子組件傳遞到父組件當中。什麼意思? 請看下圖:
app

假設一個購物網站,App分別有兩個子組件,一個是Login,另外一個則是Cart。當我們想要將使用者的購物資訊與清單分別傳遞到Cart,再由Cart將資料傳遞給Cartlist,最簡單的作法就是將User的資料一步步往上prop到App,再由App一步步往下Prop到Cartlist,如下圖所示:
app2

沒錯,這是最有React風範的方式,也是官方文件最先告訴我們的實作方式。甚至我們可以利用提升狀態(Lifting the state up)的方式去將我們想要傳遞的資料,放置在子組件共有的父組件上,在範例中,就是放置在App身上。

乍看下,這的確是解決方式其二,但是仔細想想,有可能我們的App不會用到User的任何資料,甚至若是我們的組件變得更多,中間有其餘的組件是不需要使用到User的資料,那這樣不是很難管理資料嗎? 我們現在希望的是有個方式可以直接將User的資料指向Cart,如同下圖的概念:
app3

這個時候useContext就會跳出來說: 選我!選我!選我! 我們先來看看useContext的定義與概念。

useContext使用方式

useContext的定義大致上官方定義是: 接收一個Context Object(React.createContext回傳的值)並回傳。是不是有點難懂? 我也這麼認為,對於直接下手Hooks來說,其實不太好理解,因為底層包覆的東西有點多,但我們還是試著努力看看!

useContext的長相大概如下所示:
const context = useContext(AuthUser)
我們設立一個context的變數用於儲存useContext回傳的值,在useContext裡面則寫入一個內文物件(context object)。
這個內文物件在我們的範例包含AuthUser的值,換句話說,我們會透過React.createContext建立AuthUser這個內文物件,看看下方:

1
2
3
4
5
6
7
8
<!-- In AuthContext.js -->
import React from 'react';

const AuthUser = React.createContext({
isLoggedIn: false,
onLogin: (email, password) => {},
onLogout: () => {}
});

要使用createContext函數必須匯入React,這邊應該沒有太大問題。我們可以看到說AuthUser是一個內文物件,一般來說,我們會在source的資料夾下建立一個store的資料夾負責統一管理資料,盡量讓資料和組件邏輯隔離開來,但當然,我們也可以直接在AuthContext檔案裡面直接更改Provider寫入共用的方法,讓AuthUser變得更完整:

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
<!-- In AuthContext.js -->
...上方的程式碼...

export const AuthContextProvider = props => {
const [isLogged, setIsLogged] = useState(false);

const loginHandler = () => {
setIsLogged(true);
}

const logoutHandler = () => {
setIsLogged(false);
}

return
<AuthContext.Provider
value=
{{
isLogged,
onLogin: loginHandler,
onLogout: logoutHandler
}}>
{props.children}
</AuthContext.Provider>
}

export default AuthUser;

在AuthUser內文物件中,改寫加入是否登入的狀態。
我們要注意到createContext本身會有兩個子組件分別是

  • Provider組件: 傳遞內文物件的資料
  • Consumer組件: 接收內文物件的資料

這也是為何我們會看到在AuthUser出現Provider,因為在這邊我透過props一次傳入內文物件的物件,物件包含isLogged的狀態以及登入登出的方法。可以注意到Provider中間有props.children,還記得我們提到children的那篇文章嗎? 透過這一點,我們可以知道Provider包覆的組件都可以接收剛才透過props的資料。

回到圖表,我們要在User和Cart使用到關於User的資訊和方法,現在我們可以統一在App處理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- In App.js -->
import {useContext} from 'react';
import AuthContext from './components/store/AuthContext';
import Login from './components/User/Login';
import Cart from './components/Cart/Cart';

const App = () => {
const ctx = useContext(AuthUser);

return (
<>
{!ctx && <Login />}
{ctx &&<Cart />}
</>
);
}

export default App;

因為User是在Login下方,所以我們此處匯入Login,接著再透過Login傳遞到User。這邊在顯示組件會先檢驗是否已經登入,如果已經登入就會顯示Cart的組件,若沒有登入則會顯示Login的組件。透過這樣的方法,我們同時有把提升狀態也有將資料隔離開來的感覺,而不需要一層一層傳遞,像洋蔥一樣撥開我們的組件。

我們到目前為止,檢視如何使用useContext的方法。我們在使用useContext先前必須建立內文物件才能夠使用這個Hook,目前還沒能直接看到Cart使用User的感覺,但其實我們已經朝向這一步邁進。

在以往沒有useContext這個Hook的情況下,我們在建立React.createContext後,必須在每一個需要使用到某個內文物件的組件裡面,去接收(Consumer)這些資料,換句話說,我們沒辦法直接透過指令變數去將資料傳遞到組件,程式碼大概會像下方這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- In Cart.js -->
import AuthContext from '../../store/AuthContext';
import CardList from './CardList';

const Cart = props => {
return (
<AuthContext.Consumer>
{(context) => {
<CardList />
<Button onLogout={onLogout}>
}}
</AuthContext.Consumer>
)
};

export default Cart;

大致上會像上方這樣的寫法,需要多寫Consumer這一段語法糖,去將組件包裹起來。在沒有Hook的出現確實是一件再正常不過的事情,但是現在有了useContext,實在是沒有理由將程式碼寫的過於繁瑣,畢竟React是屬於宣告式,若能減輕開發網站的負擔,沒有理由堅持使用上方的寫法。但,寫程式沒有對錯,這些都是非常棒的方法!

-重點整理-

  • useContext接收內文物件
    • React.createContext建立內文物件
      • Provider子組件傳遞資料
      • Consumer子組件接收資料

結語

今天主要講述useContext的使用方式,算是非常粗淺的概念。要在這裡像各位讀者說一聲抱歉,希望未來我能夠對React更加熟稔,蠻擔心自己寫的不夠清楚或者是寫錯,但即便如此還是想要挑戰自己試試看,以教學的角度,來檢視自己學習的成效。若有誤,請不吝於糾正。

最後,謝謝看到結束的各位。

Hooks家族 - useEffect

It is better to fail in originality than to succeed in imitation.
― Herman Melville

距離上一次寫useState的文章已經事隔些許日子,今天想來寫寫關於useEffect的事蹟。useState和useEffect在React當中非常有份量,甚至因為它們的出現改寫React的Coding Style。這幾天在研究useEffect,但卻又覺得自己似懂非懂,因此決定透過文章來整理自己所學,我想要試著放慢腳步,再繼續往前。那,我們開始吧!

  • useEffect
    • useEffect使用方式
  • 結語

useEffect

程式往往都會有許多的副作用(Side Effect),useEffect這個Hook正是React開發團隊為了解決副作用的問題而研發出來的程式撰寫方式,就像是JSX是為了將JS和HTML合併的概念。

useEffect使用方式

useEffect大致上寫法如下:

1
2
3
useEffect(()=> {
console.log("Hi! I\'m useEffect in Hooks Family!");
}, [])

我們可以注意到在useEffect這個Hook函式中有兩個參數,參數一是匿名函數,參數二則是一組陣列。如同先前所述,這個Hook是為了解決副作用的問題,那具體來說,我們應該如何在React專案中使用他? 我們一樣來看點範例。

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 { useState, useEffect } from 'react';
const Hook = props => {
const [email, setEmail] = useState('');

const submitHandler = (event) => {
event.preventDefault();
};

const setEmailHandler = (event) => {
setEmail(event.target.value);
};

useEffect(() => {
console.log("Hi! I\'m useEffect in Hooks Family!");
}, []);

return (
<div className="hook">
<form onSubmit={submitHandler}>
<label htmlFor="email">E-mail</label>
<input type="text" id="email" value={email} onChange={setEmailHandler} />
<button type="submit">Submit</button>
</form>
</div>
)
};

使用Hooks前先匯入,接著我們試著建立一個簡單的Email表單,並且給它一個state,到目前為止都和上一篇文章提的差不多。我們透過useState這個Hook來控管狀態,緊接著我們直接在setEmailHandler下方加上useEffect Hook。實際將這個組件匯入後執行後,可以在Chrome的Console裡面發現以下我們在useEffect列印的字串。

補充: event.preventDefault()是為了防止submit按鈕送出請求給Http後端伺服器而導致頁面重載

console

我們發現useEffect執行過的痕跡,但是它究竟如何執行呢?我們試著在它給定的第二個陣列裡面填入我們的email狀態值。

1
2
3
useEffect(() => {
console.log("Hi! I\'m useEffect in Hooks Family!");
}, [email]);

同樣地,我們回到Chrome去執行,試著在欄位裡面隨意輸入。接著我們發現如下圖:
console2
useEffect會在Hook組件完成載入後執行一次,接著隨著我們輸入N個字元,就多印出N次訊息。這個結果我們可以理解useEffect這個Hook會根據陣列二的狀態值而改動,而這個狀態值指的就是email這個狀態(變數)的改變。

接著我們再將useEffect更改成以下的形式:

1
2
3
4
5
6
useEffect(() => {
console.log("Hi! I'm useEffect in Hooks Family!");
return () => {
console.log("Return comes first!");
}
}, [email]);

在開發者工具觀察實際在欄位輸入後的訊息:
console3

我們發現return所列印的訊息會優先於匿名函數內的訊息,我們現在可以清楚理解藉由useEffect,可以很輕鬆地去解決在撰寫React時可能出現的副作用問題。能夠透過useEffect去決定特定情況下的程式碼,降低程式邏輯的模糊與不確定性。

-重點回顧-

  • useEffect用於處理程式的副作用
  • 參數一是執行的函數
    • 函數內的return會優先執行
  • 參數二是狀態陣列
    • 根據狀態的改變而執行useEffect內的函數
    • 若為空陣列則僅執行一次

結語

本篇算是淺談useEffect,在useEffect身後還有useReducer的存在。礙於小弟的才疏學淺,僅能夠將最基礎的應用和各位讀者分享。useEffect往往還會搭配計時器的使用去處理問題,舉例來說,我們可以驗證Email欄位是否包含@符號,但我們不會想在每輸入一個字就對欄位執行驗證,而是在使用者輸入完畢後才進行驗證,在這樣的情況下,就需要配合計時器去處理。

未來若有任何想法想補充都會來更新這系列的文章,期望能夠將Hooks講述的更完善。

最後,謝謝各位的閱讀!

Hooks家族 - useState

The measure of intelligence is the ability to change.
- Albert Einstein

今天要來談談Hooks家族的第一位成員useState。Hooks的出現使React更加脫穎而出,以往處理State時都必須透過建構、綁定去撰寫,而且每一個步驟都必須明確,但在今天Hooks都幫我們偷偷處理完成,我們只需要告訴React我們想做什麼,那就是Hooks的強大。

  • useState
    • 幕後推手JSX
    • 鬼鬼祟祟State

useState

認識useState這個Hook前,我們先來理解一下JSX。

幕後推手JSX

我們看看下方這段程式碼:

calculate

這段程式碼裡面有一個組件Count,我們給它一個變數count用來倒數,接著把count傳到HTML的h3元素上。可是問題來了,我現在想要做的是從10開始倒數,這樣一來我不就永遠不能倒數了嗎? 可能這段程式碼本身有問題,那我們試著把程式碼更改一下,用JS的思維寫它。

calculatejs

對,用迴圈去改變值似乎很合理,但是等等,為什麼一堆紅標? 那自然是因為我們的語法出問題,可是這在JS裡面不應該有問題啊! 問題出在我們並不是在寫JS。因為我們沒辦法用這種方式將JS和HTML合併在一起,我們可以選擇使用HTML與JS,但就是沒辦法單純使用JS去達到倒數的結果,除非我們用JSX的方式寫。

提到JSX,就會想到React。我們在React基本概念文章有提到React的兄弟JSX,但是我們並沒有太深入研究它的由來與經過,只知道它負責轉譯瀏覽器看得懂的程式碼。

JSX起初構想就是想在JS檔案裡面去撰寫HTML,並且只要使用唯一的HTML檔案作為埠口去連接。若想編譯JSX就必須在每一個JS檔案匯入React,像這樣子import React from 'react',現在我們沒有匯入是因為在下create-react-app指令時,已經將React編譯JSX的函式庫都匯入到每一個JS的檔案。往後,我們才能順利編譯JSX的檔案,那問題回到最一開始,我們要如何在JSX倒數?

鬼鬼祟祟useState

要在JSX裡面控制數值同時又控制介面,就比需仰賴React核心概念其一的State。所謂的狀態(State)就是一個組件的各種樣式,比方說有兩顆螺絲,它們都叫做螺絲,一個是比較大的螺絲,一個是比較小的螺絲,它們的長寬高都不一樣,這時候長寬高就是它們的狀態。

若我們想要改變狀態,就要去更改State。但我們並不能夠直接改變變數的值,因為React不會因為更動變數的值改動組件。這時候useState就出場了!

首先我們把Count程式碼改成下方這個樣子:

countdown

我們在return前加入了const [count, setCount] = useState(10),這就是典型的使用useState的方式。useState有三點要記住的是,useState會回傳一個陣列,陣列裡只有兩個元素,第一個元素是初始值,第二個元素則是更新初始值的函數。ES6版本釋出後,我們在宣告時可以直接將陣列元素拆解,這就是所謂的解構賦值(Destructuring),講白就是單純拆開陣列對應的元素順便給它們變數名稱。我們在ES6快速入門那篇文章也有提到這部分,若想回顧可以點一下下面的連結。

解構賦值: https://reurl.cc/DvzmzR

接著可以注意到我們在h3下方加入一個按鈕,單純只是因為要去觸發倒數這個事件。特別注意到,為了區分純JS和JSX,JSX命命往往會用onClick, className的方式去命名,是為了避免跟JS關鍵字對撞的關係。

實際打開瀏覽器,我們就可以開心地倒數了。

final

-重點回顧-

  • JSX就是HTML和JS的混合體
  • React不因變數而更動組件
  • useState回傳陣列
    • 第一個參數是初始值
    • 第二個參數是更新函數

結語

這篇文章對於使用useState講的非常淺,但若要快速上手使用這個Hook,我想沒問題。組件有兩種寫法,一個就是目前文章範例的函數寫法,另外一種寫法則是使用類別(Class),同樣也是受惠於ES6才出現的寫法。不過在Hook出來前,類別寫組件在調整狀態的時候還必須透過建構子、綁定才能夠去實現更新狀態。甚至還必須去顧及React的生命週期,Mount來Mount去的。但是現在有Hooks,很多情況下,其實並不需要考慮到這麼細,幾乎可以說是減輕React工程師極大的負擔,卻能做到更多的事情,要說是福音也不為過。

useState寫法相當多元,除了上述最常見的以外還有其他方式可以去達到同樣的效果,甚至用物件一起打包所有的狀態等等。礙於小弟學識淺薄,就先在此停筆。未來對於React有更深入理解,會再來補充現在沒能提到的部分。

最後,謝謝看完這篇文章的各位,希望對你們多少有點幫助就好了。

Props講解與使用

Do what is right, not what is easy nor what is popular.
- Roy T. Bennett

  • Props的概念
    • Props使用實例
    • Props也有孩子?!
  • 結語

早上風很大,天氣很好,適合寫寫關於props的故事。那,我們馬上來說說props吧!


Props的概念

我們在第一篇React系列文提到React以組件的方式組成,單一個組件就好比合成金屬一樣,由HTML、CSS、JS三種元素所組成。但是當今天有數十、百個組件的時候,如何去把這些「金屬」組裝在一起,就是一門學問!

這時候就是Props(Properties)登場的時候了!React提供我們把每一個組件鑲嵌在一起就是透過使用Props。我們來直接看範例。

Props使用實例

建立專案的指令
npx create-react-app <project name>

延續我們系列文第一篇所建立好的專案,大致上會有下面這些檔案:

  • first-react-project -> 專案資料夾名稱
    • node_modules -> NPM安裝後的套件
    • public -> 放置圖片和唯一的index.html
    • src -> 放置組件的資料夾
    • package-lock.json -> 鎖定專案某版本描述檔
    • package.json -> 專案規範描述檔

首先我們把APP裡面的預設程式碼更改成像下面一樣:

root

可以看到App.js這個檔案變得相當乾淨,我們通常稱App這個檔案為根組件,也就是說它會擁有許多的子組件,最後將這些子組件拼湊在一起。在Cmd輸入npm run start就可以看到本地端的瀏覽器出現「我是根組件!」這段文字。

rootme

這就是我們第一個組件,接著我們在src檔案夾建立兩個資料夾分別是componentsstyles。首先我們在components的資料夾內建立叫Calculate.js的組件。組件其實都是由函數構成,因此一定是以函數起頭,而函數名稱同檔案名稱。特別注意到,必須把這個組件匯出才可以在其他組件使用。我們目前會在App.js這個根組件使用Calculate.js這個子組件。

cal

這就是構建組件的第一步,緊接著我們在App.js匯入Calculate組件。

appcal

我們匯入Calculate組件後,再將組件用HTML的形式包覆在App根組件當中,切換到瀏覽器後會發現多了一個12的數字。

num

我們現在已經完成最最最基礎的組件使用方式。可以看到我們最後順利地使用Calculate這個組件。可是等等…,說了這麼多,到底跟props有什麼關係? 對,到目前為止都還沒有關係,因為必須先把組件嵌在一起的方式解說後才能理解使用props。總得先學會發動車子,才能開始練習開車對吧?

我們在App裡面輸入一組物件,這很Hard Core,不過沒關係,我們只是為了示範這個過程。請注意到return前的程式碼是寫JS,return後的程式碼則是寫JSX。接著我們把propme這個物件傳入給Calculate這個子組件。

propass

我們藉由JSX的形式給定一個變數名稱value,把在App組件裡的物件propme透過所謂的props方式傳遞給Calculate,接著才能回到Calculate組件裡面去接收這個資料。

回到Calculate組件(函數)後,注意到參數props可以任意命名,但一般會以props命名。為了讀取從App傳來的資料,必須在參數後方寫上我們在App設好的變數才能夠去存取App的物件屬性。

props本身是一個物件,因此可以容納相當多元的資料。

cals

我們可以看到Calculate組件讀取傳遞過來參數的方式,就跟讀取物件屬性一樣,差別只在於要先讀取到在App的變數名value。

ass

成功傳遞資料到其他組件!! 現在我們終於講解完props的概念和使用方式,接著要來討論有點神祕的children。

什麼? props也有孩子? 還真的有!


Props也有孩子?!

當我們想給我們組件的孩子保護,這時候children就出現了! 啊不是,在說什麼…。
所謂的props.children就是用外殼(nutshell)包覆子組件,詳細我們一樣來看例子比較清楚。

首先我們在components裡面再新建立一個組件叫做Card,Card比較類似統稱,只要有外殼功能的組件都會這樣命名。看看下圖:

card

我們看到Card同樣透過props傳入資料,宣告一個const用來儲存class的名稱把card的css類別傳到包覆住的子組件,接著在JSX檔寫入props.children這段程式碼。children是保留字,每一個組件在建立時都會預設好。children獨到之處,在於它的使用方式,而不是單純的寫成組件而已。我們來看看它如何使用,看看下圖:

calcard
我們把div替換成Card,Card是客製化的組件。我們順便給Calculate本身的這個div區塊一個class名稱calculate。現在重新檢視瀏覽器,並不會有任何改變,因此我們可以給Card一點點樣式。選定styles資料夾裡頭,新增card.css這個CSS樣式表,並且給它簡單的框線以及背景顏色。
如下:

cardcss

記得要在Card這個元件去匯入樣式表import '../styles/card.css'才會套用。

我們緊接著來檢視一下是否成功使用Card了呢? 打開你/妳的瀏覽器看看吧!

final

哇! 成功套用到Calculate這個組件身上,至於組件外的則沒有套用成功,因為「我是根組件」那段文字是寫在App裡面。如果要套用到整個App,則修改App的div為Card就可以成功套用這段文字。各位讀者不妨自己動手試試看,一定會覺得很有意思。

-本日回顧-

  • props使組件彼此傳遞資料
  • children統一管理組件共同性質

結語

今天主要把昨日學到關於props的概念和使用方式和大家分享,我相信我目前理解的程度還非常的淺,若有任何問題歡迎告訴我。

我在剛開始學children覺得有點抽象,但在實際動手做過幾次後,就對於它的概念比較清楚一些。不過我想children應該不會只有這點使用方式,這樣的話就太小看它了!以後若遇到進階的使用方式或有趣的應用再和大家分享!

寫文章總會花上數小時,但是很有趣,我們下回再見!
謝謝各位,祝大家都有美好的周末!

分不清指令式與宣告式程式語言?

Lack of direction, not lack of time, is the problem. We all have twenty-four hour days.
― Zig Ziglar

今天要來和各位聊聊指令式(Imperative)和宣告式(Declarative)程式語言,聽起來會有點像講古喔!但,仔細想一想,學習一門外語,最難的並不是會使用那門語言,而是語言背後的文化才是真正難以摸透的。我想,程式語言也一樣!因為它也是「語言」!我相信在了解程式語言的歷史後,對於自己在寫的程式會更有深入的理解,而不是在瞬息萬變的世界下走馬看花。

  • 指令式程式語言(Imperative Programming)
    • 低階指令式
    • 高階指令式
  • 宣告式程式語言(Declarative Programming)
    • 控制流程(Control flow)
    • 資料庫語言(Database Programming)
    • 函式語言(Functional Programming)
  • 結語

指令式程式語言(Imperative Programming)

Imperative programming is a programming paradigm that uses statements that change a program's state.
我們來仔細思考一下這一句擷取自Wiki頁面的敘述。
指令式程式語言是使用陳述式語法去改變程式狀態的一種程式範式(典範)。嗯…相當的文謅謅,可不可以用更直覺的方式來理解指令式程式語言? 沒問題,我們馬上看下面的程式碼:

指令式程式語言有分低階與高階

低階指令式程式語言(Low-Level)

廣義上來說,低階與高階是相對的概念,以現今來看,就像是C++相對於Python語言就能夠劃分成低階與高階。但狹義上來說,我們劃分低階與高階指令式,可以用組合語言與高階語言來劃分。我們看看下方的例子:

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
ITER EQU 10           ; number of iterations
OVERHEAD EQU 15 ; 15 for Pentium, 17 for Pentium MMX

RDTSC MACRO ; define RDTSC instruction
DB 0FH,31H
ENDM

.DATA

ALIGN 4
COUNTER DD 0 ; loop counter
TICS DD 0 ; temporary storage of clock
RESULTLIST DD ITER DUP (0) ; list of test results

.CODE
BEGIN: MOV [COUNTER],0 ; reset loop counter

TESTLOOP: ; test loop
;**************** Do any initializations here: ******
FINIT
;**************** End of initializations *************

RDTSC ; read clock counter
MOV [TICS],EAX ; save count
CLD ; non-pairable filler

REPT 8
NOP ; eight NOP's to avoid shadowing effect
ENDM

;**************** Put instructions to test here: ************************
FLDPI
FSQRT
RCR EBX,10
FSTP ST
;********************* End of instructions to test ************************

以上的程式碼是組合語言,在我們的定義上來說就是低階的程式語言。現在前後端工程師比較少碰到組合語言,基本上都是處理硬體、韌體時比較有機會接觸到底層的程式語言,也就是低階指令式語言,大部分的情況都是為了使硬體效能最佳化,才會去撰寫組合語言。不過小弟對於組合語言也是一知半解,如果有誤,還請多多指正!

高階指令式程式語言(High-Level)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

bool isPalindrome(int x)
{
long reverse = 0;
int compare = x;

if(x < 0)
{
return false;
}
while(x != 0)
{
int popin = x % 10;
x = x / 10;
reverse = reverse*10 + popin;
}
if(reverse == compare)
return true;
else
return false;
}

上面的程式要確認某一個數字是不是回文(Palindrome),舉例來說:12321。上述就是指令式程式語言的一種。可以看到說我們必須一步一步地去規劃程式的每一個步驟。首先,我們建立另外一個新的數值,利用取餘(Mod %)的方式,去反向依序填入x的值,最後在經過數值比對去核對數字是否一樣,若一樣則回傳True,相反則回傳False。這就是現今比較常看到的高階指令式程式語言。

指令式程式語言基本上的概念就像上述所說,可以劃分為低階與高階。特別注意的是,在C語言後出現的C+、C++以及Java都屬於OOP(Object-Oriented Programmin)的程式語言,但它們本質上依舊屬於指令式程式語言,只是又更上一層樓變得更為高階。其實所謂的指令式沒有想像中的難理解,我們日常在料理的過程,本質上一樣是指令式程式語言。倒不如說,指令式程式語言是從現實生活中,逐步發展出來才是正確的理解方式,有點做什麼(What to)的意味。


宣告式程式語言(Declarative Programming)

同樣地,我們來看看Wiki上面如何定義宣告式程式語言。
A style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.
一種不需描述控制流程即可表達運算邏輯的電腦架構與元素風格。嗯…翻譯的似乎不是很到位,不過反覆思考這段話,什麼是不需描述控制流程(Control flow))的架構? 我們先來看看何謂控制流程(Control flow):

控制流程(Control flow)

控制流程可以用一種方式去理解,那就是「順序」。當程式碼的擺放位置不同時,執行過程就會不同,這就是控制流程的概念。
我們來簡單舉個JS的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const world = 'Hello';
switch (world) {
case 'Hello':
console.log('Hello world!');
break;
case 'hello':
console.log('World hello!);
break;
case 'Hi':
console.log('Hi, world!');
break;
default:
console.log(`Sorry, we are out of ${world}.`);
}

上述程式碼就是控制流程的概念,可以看到如果改動case的順序,程式流向就會改變。我們暫且討論到這邊,因為重點還是在宣告式程式語言。
宣告式語言基本上就是由指令式語言逐步發展過來,因為它的最大區別在於避免副作用(Side Effect)。一般來說,撰寫程式免不了會出現副作用,白話一點就是改了這邊的程式碼,壞了那邊的程式碼的概念,因為副作用的其中一種可能性便是改變程式執行的順序,而導致某些程式片段無法成功讀取定義好的函數、物件、陣列等等。

資料庫語言(Database Programming)

最容易理解也不會令人困惑的非屬於資料庫語言,如:MySQL、PostgreSQL等等的資料庫語言。
同樣舉例來看:
SELECT name FROM tw.male ORDER BY name
這就是宣告式程式語言的一種,因為我們並不知道SELECT以及FROM還有ORDER BY的內部程式碼,就算我們更改name以及tw.male的欄位,都不會對整體程式造成任何副作用,頂多回報格式不符合或是查無資料的訊息。

函式語言(Functional Programming)

函式語言同樣是宣告式程式語言,你正拿來寫網站的JS就是其中的一名。可是你會說寫JS可能會有副作用啊? 沒錯,確實有可能產生,但那只是符合宣告式定義的一種。因為副作用不是必要條件,而是充分條件。換句話說,沒有副作用一定是宣告式程式語言,但有副作用也有可能是宣告式程式語言。直接來看看JS的範例吧!

1
2
3
4
const filterNums = (...args) => {
return args.filter(el => el > 5);
}
console.log(filterNums(1,3,5,7,9,11,13,15,17));

這段程式碼很眼熟對吧! 因為在ES6快速入門那篇文章也有提到。我們仔細看一下回傳過後的那段程式碼return args.filter(el => el > 5);這段程式碼讀來沒有任何問題,但是仔細想想filter是什麼? 它是一個函數,可是函數就不會有副作用嗎? 不一定,但至少在這裡它確實不會有副作用,因為不管傳入什麼樣的陣列,都不會影響filter()本身。


結語

今天概略講過指令式和宣告式程式語言的差別,因為在Udemy上課程時,講師剛好提到React是宣告式程式語言,就很好奇到底什麼是宣告式程式語言。從React是宣告式語言這一點來看,我們可以知道HTML、CSS、JS都可以歸類為宣告式語言,甚至是XML,畢竟JSX就是JS-XML形式的程式碼,具有標記語言的性質。

這一篇希望對能了解這兩個概念的讀者有點幫助,一開始查資料時,覺得內容蠻多而且還有點乏味。不過仔細去品味程式語言的整個發展,就像發現新世界一樣,一切都是環環相扣。礙於篇幅,還有許多沒有提到,如:DSL(Domain-Specific Language)就沒有提到,但是加上DSL其實就會變得不單純。

那今天就先這樣囉! 我們下回見!

組合語言程式片段來源: https://www.csie.ntu.edu.tw/~b5506058/optimize.html

0%