0%

延續 上一集
確定元件的輸入輸出介面後
(回顧四個基本屬性: isVisible, displayDate, mode, onConfirm)
現在要開始實作功能啦

我們可以把實作步驟切分成幾個部分:

  1. 排版: 如何動態顯示每個月的日期數以及星期
  2. 單日日期操作: 每次點選都會更新 data.date
  3. 多日日期操作: 有時點選是設定開始日date.startDate,有時是結束日endDate,需設計操控流程
  4. 效能問題: 目前為止每次當元件內有一堆按鈕的時候都會有不可忽略的卡頓(因為每次點擊都會讓每個按鈕重新渲染),希望我能研究出如何減少這個卡頓時間

今天這章節就來看看如何做日期的動態顯示吧!

動態顯示日期

首先我們來想一想
一般的日曆app都怎麼排版?
每個月第一天的星期都不一樣
並且前後分別會補上個月最後幾天和下個月前幾天
直到補滿整行,像這樣(網路找的圖):

要達到這個目標,必須先有三個資訊:

  1. 本月有幾天? days
  2. 本月第一天是星期幾? firstDay
  3. 上個月有幾天? prevMonthDays

所以我打算做一個useDaysOfMonthcustom hook 來提供這些訊息

  • Custom Hook 是一組包裝起來的邏輯,有自己的 life cycle,你可以在其中使用useStateuseEffect之類的 React hook。如果你還記得,hook 不是有很多使用上的限制嗎? 例如只能存在於元件內的第一層而不能被包在元件內的函數內,這項限制讓你沒辦法把重複用的邏輯包起來。而 custom hook 就是為此而生。

Custom Hook: useDaysOfMonth

custom hook 有個條件,就是一定要是use開頭,如此一來 React 運行時才能判斷元件內的 hook 是不是都在最上層,所以別忘了這步。
除此要求之外,一個 custom hook 看起來就像是不做渲染的 React 元件,或是可以放 hook 的一般函數。

那就開始吧,首先我們建立一個檔案叫 useDaysOfMonth.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// in useDaysOfMonth.jsx
import { useState } from 'react'

const useDaysOfMonth =(inputTime)=>{ //輸入一個時間,這會是來自元件的 displayDate 屬性
const [days, setDays] = useState(0);
const [firstDay, setFirstDay] = useState(0)
const [prevMonthDays, setPrevMonthDays] = useState(0);

// 這裡要做點事

return {days, firstDay, prevMonthDays} //輸出三個我們要的資訊,皆為 number type
}

export default useDaysOfMonth

接下來我們需要拆解輸入進來的日期物件,得到年份,月份

1
2
const year = inputTime.getFullYear()
const month = inputTime.getMonth()

然後我們就要依照年月來獲得 days, firstDay, prevMonthDays 三個資訊
*注意不要用日期物件的setDate來做這些事,因為Date()的 method 都會 mutate(會改變物件自身的值,而不會另外建立一個物件)

1
2
3
4
5
6
7
8
9
10
11
12
setDays( new Date(year,month+1,0).getDate() )
// 在 Date constructor 中,如果日期填0,則會視為上個月的最後一天
// new Date(2021,2,1) => 三月一號
// new Date(2021,2,0) => 二月二十八號
// new Date(2021,2,-1) => 二月二十七號
// 所以若要獲得本月最後一天的日期,只要把月份+1,然後日期設為0即可

setFirstDay(new Date(year,month,1).getDay())
// 很直觀,取本月第一天的星期

setPrevMonthDays(new Date(year, month, 0).getDate())
// 同 setDays() 不過月份不用+1

過程很簡單,都是基本的Date物件取值
再來我們希望在任何使用這個 hook 的元件中,每當 input 值改變時,輸出值也要跟著更新
這不就是useEffect的功能嗎!
所以我們將以上運算放進useEffect中,並將inputTime設為 dependency

1
2
3
4
5
6
7
useEffect(()=>{
let year = inputTime.getFullYear()
let month = inputTime.getMonth()
setDays(new Date(year, month + 1, 0).getDate())
setFirstDay(new Date(year, month, 1).getDay())
setPrevMonthDays(new Date(year, month, 0).getDate())
},[inputTime])

完成! 完整的code如下:

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

const useDaysOfMonth = (inputTime) => {
const [days, setDays] = useState(0);
const [firstDay, setFirstDay] = useState(0)
const [prevMonthDays, setPrevMonthDays] = useState(0);

useEffect(() => {
let year = inputTime.getFullYear()
let month = inputTime.getMonth()
setDays(new Date(year, month + 1, 0).getDate())
setFirstDay(new Date(year, month, 1).getDay())
setPrevMonthDays(new Date(year, month, 0).getDate())
}, [inputTime])
return { days, firstDay, prevMonthDays }
}

export default useDaysOfMonth

一個小細節,我把輸出的參數用大括號(object literal)包起來,這樣在存取參數時就不需考慮順序
什麼意思呢? 看以下的使用方式就知道了

1
const {firstDay, prevMonthDays, days} = useDaysOfMonth(new Date())

可以看到我取得三個參數的順序不同,但仍然會work
缺點是這樣就一定要把參數名稱打對了,想要更改參數名就要打{firstDay: xxx}
反之,如果你回傳的是用中括號(array literal)包起來[firstDay, prevMonthDays, days]
取得參數時的順序就很重要了,不過就可以直接改名

在建置網站的 contact 頁面時
為了讓訪客能聯繫自己
寄 email 應該是最簡單直接的方式了
這裡介紹兩種從網站寄 email 的方法

1. FormSubmit

FormSubmit 讓你可以用 RESTful API,由他們的 server 把訪客的訊息寄給你

優點是完全 free 沒有流量限制

首先在你的網站的 form tag 加入actionmethod
別傻傻直接貼上,記得把your@email.com改成你的信箱喔

1
<form action="https://formsubmit.co/your@email.com" method="POST" />

接著確保你的 input tag 都有加name的屬性
這個屬性會影響到你收到信件時的內容呈現方式
你可以設為: email message 之類的

接下來只要 submit 這個 form 就可以了
如果你的收件 email 是第一次在這個 API 使用的話
在第一次送出時你會收到一封認證信,認證完成之後接下來的每次 submit 都會順利寄信給你囉!

  • 補充

通常在訪客做出 submit 時,會自動跳出 reCAPTCHA 認證框

並且在成功寄出郵件後會跳轉到他們提供的 thankyou page

你可以視情況決定是否要顯示這些訊息
這邊就來示範一下

關閉 reCAPTCHA

<form>裡面加入這個

1
<input type="hidden" name="_captcha" value="false">

跳轉到自己的 thankyou page

一樣在<form>中加入:

1
<input type="hidden" name="_next" value="https://yourdomain.co/thanks.html">

並改成該網頁的路徑

還有很多其他的功能,就留給讀者自己去發掘囉


2. emailJS

比起第一個方法,emailJS 好像比較多人用
而且網路上的教學大多是搭配 React 使用

首先,到官網註冊帳號,記得用你希望收到信的信箱註冊
(不過用別的也行,這個可以事後更改)

登入後會進到 dashboard
我們先來到 Email Service 點擊 Add New Service

先跟大家釐清一件事(因為我當初搞混)
emailJS 並不會幫大家寄信,而是幫你建立起網站與 email server 之間的橋樑
這個橋梁就是 email service,
至於這個 server 需要由第三方提供,如果你流量不大,可以用 gmail、yahho mail 之類的
如果潛在使用量很高,你可能就需要使用下方的 Transactional Services
不過這些我目前都沒聽過XD Personal Services 應該就很夠用了
但是要記住,無論你選了什麼都不代表它是你的收件信箱,而是寄信信箱
就像我前面提的,你註冊的信箱才是

做好選擇,依照指示進行,你就會得到一組 service ID (記得它, 待會會用到)

接下來到 Email Templates 設定你收件時所見的內容格式
這邊其實可以直接 Create New Template,什麼都不改直接

一樣我們會得到一組 template ID (記得它, 待會會用到)

下一步,我們要把 emailJS 和網站做串接啦

如果你用的是 React,請先 npm 安裝

1
npm install emailjs-com

接著在專案中:

1
2
import {init} from 'emailjs-com'
init("YOUR_USER_ID") // 在 EmailJS dashboard 的 integration 頁面可以找到自己的 user id

如果你用的是 html,就在 <head> 裡面加:

1
2
3
4
5
6
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/emailjs-com@2/dist/email.min.js"></script>
<script type="text/javascript">
(function() {
emailjs.init("YOUR_USER_ID");
})();
</script>

最後,在你 submit 時運行 emailjs.sendForm()

1
2
3
4
5
6
7
8
9
10
11
12
13
<form id="emailjs-form">
<input type="email" name="email" />
<input type="text" name="message" />
<button type="submit"/>
</form>

<script>
const emailJsForm = document.querySelector('#emailjs-form')
emailJsForm.addEventListener('submit',(event)=>{
event.preventDefault()
emailjs.sendForm('YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', THE_FORM_NODE)
})
</script>

注意emailjs.sendForm()有三個必填參數,其中最容易忽略的是第三個:

  • THE_FORM_NODE: 放入 node,以這個例子來說就是emailJsForm

sendForm() 會回傳 promise,所以可以接 .then() .catch()


好ㄌ
寄 email 的方法就介紹到這邊
最近幾乎都在研究建置個人網站的眉眉角角
再過一陣子或許就能開始動工了!!!
不過網站的設計也是一份大工程
要練習 figma 要讀 design guideline
真的有好多東西要學ㄚ~~~

我決定要來做一個 for RN 的日期選擇器

原因是因為雖然之前有查到兩個套件,但都有點不合需求:

第一個是 react-native-modal-datetime-picker
是調用 android/ios 原生的日期選擇器
雖然用起來很順又好看 (畢竟是原生元件)
但唯一的缺點是不能選日期區間 QQ
而我們想要用的就是選時段的功能 r
像是設定旅行日期之類的

第二個是 react-native-daterange-picker
這看起來是 Naxulanth 這位網友自製的元件
畫面簡潔,也可以選擇日期區間
但是,使用上有很多限制
doc 也沒有寫的很完整,實際使用方式跟示範的 code 有點不同
還有不少 bug

正好我也想當練習
就來做一個屬於自己的日期判斷器吧
如果開發的不錯,就順便發布出去 讚

寫這篇文的現在其實還沒做好
所以可能會分成幾個章節吧

輸入介面規劃 Interface Design

我希望這個元件的畫面、以及使用方式都夠簡單
並沿用 react-native-modal 的使用介面做開發
暫定最簡短的介面如下:

1
2
3
4
5
6
<DatePicker
// 是否顯示
// 開啟時顯示的日期
// 模式:單日還是期間
// 按完成後回傳的日期
/>

這四個是 required attributes
後續可能可以再加一些 styling 的屬性進來
但目前還是先 focus 在這幾個上面就好

1. 是否顯示

名稱: isVisible
類型: Boolean
說明: 這應該沒什麼,就是開啟或關閉日期選擇器

2. 開啟時顯示的日期

名稱: displayDate
類型: Date物件
說明: 選填但是建議輸入,沒設定的話就預設為現在。當選擇器開啟時會顯示這個時間的月份,你可以利用這個屬性讓選擇器顯示使用者上次所選的日期的月份

3. 單日還是期間

名稱: mode
類型: string
輸入值: 'single' / 'range'
說明: 模式選擇: 要回傳單一日期就選 ‘single’;要回傳一段時間就選 ‘range’

4. 按完成後回傳的日期

名稱: onConfirm
類型: function
輸入值: (data)=>{ }
說明: 回傳一個物件。如果是單日模式物件的結構會是:

1
2
3
4
5
data={
date:Date(),
startDate:null,
endDate:null
}

反之如果是期間模式則是:

1
2
3
4
5
data={
date:null,
startDate:Date(),
endDate:Date()
}

好啦 第一篇就先到這
這可能會是個大系列(會分很多篇的意思XD)