My Common Patterns

Pattern CSS/JS ที่มักจะใช้บ่อยๆ ตอน Dev จะได้เปิดมา Copy ง่ายๆ

git bisect


เวลา dev ไปเรื่อยๆ แล้วฟีเจอร์ใดฟีเจอร์หนึ่งระเบิด ถ้ามีอยู่ไม่กี่ commit เราอาจจะยังพอไล่ checkout ได้ แต่ถ้ามีจำนวน commit มากๆ เราสามารถใช้วิธีการ bisect เพื่อหา commit ที่ทำเจ๊งได้

  1. git bisect start — เริ่มการ bisect
  2. git bisect good <sha> — บอกว่า commit ตัวไหนที่ยังไม่พัง ตรงนี้อาจจะดูตัวที่ test ผ่าน/build ผ่านล่าสุด ก็ได้
  3. git bisect bad <sha> — บอกว่า commit ไหนพัง จะใส่ commit ล่าสุดก็ได้
  4. git จะเริ่ม binary search หาว่า commit ไหนพัง
    • ถ้า commit ที่ git หยิบมาดี: git bisect good
    • ถ้า commit ที่ git หยิบมาเจ๊ง: git bisect bad
  5. ทำวนไปเรื่อยๆ จนกระทั่ง good กับ bad มาติดกัน — ตัวที่เป็น bad คือจุดที่เริ่มพัง
  6. ออกด้วย git bisect reset

ทริคจาก @suphon-t

ถ้าสมมติมี command ที่เราจะทดสอบว่าตายตอนไหน สามารถใช้คำสั่ง git bisect run <command> ได้ เช่น git bisect run pnpm build


ตัดคำภาษาไทยใน JS

JS/TS

const segment = (str) => [...new Intl.Segmenter('th-TH', {
  granularity: 'word'
}).segment(str)].map(w => w.segment);

CanIUse: 89.02% | ❌ Firefox


Conditional property in object

JS/TS

const busNum = 75
const btsName = undefined

const data = {
  ...(busNum && { bus: busNum }),
  ...(btsName && { bts: btsName })
}

console.log(data)
// { bus: 75 }

Falsy values (null, undefined, false, NaN, 0, -0, 0n, "") สามารถ spread ได้ ได้ผลลัพธ์เหมือน ...{}


ซ่อน Scrollbar

CSS

.scrollbar-none {
  scrollbar-width: none;
}

.scrollbar-none::-webkit-scrollbar {
  display: none;
}

ตัดลิงก์ไม่ให้ overflow

CSS

a {
  overflow-wrap: break-word;
  word-wrap: break-word;
  word-break: break-word;
  hyphens: none;
}

ลิงก์ยาวๆ ที่เป็นพวก url อาจจะ overflow container ได้, declarations ข้างบนจะทำให้มันตัดขึ้นบรรทัดใหม่ได้


for

JS/TS

for มีแบบไหนบ้าง?

  • for ทั่วไป: for (let i = 0; i < 10; i++)
  • for ... in: for (let key in object)
  • for ... of: for (let item of array)
  • (Array) forEach: array.forEach((item) => { /* ... */ })

for ... in หรือ for ... of?

  • for ... in ใช้กับของที่มี key เช่น Object
  • for ... of ใช้กับ iterable object เช่น Array, String

วิธีการจำ: จำว่า ถ้าเราต้อง if (key in object) แปลว่าเราต้อง for (let key in object)

for ไหน break ได้บ้าง?

for ทุกตัวที่ไม่ใช่ forEach สามารถ break ได้หมด — ถ้าพูดง่ายๆ เลยคือ forEach ต้องรับ callback function ซึ่งเราจะสั่งหยุดกลางทางไม่ได้

ทำยังไงให้ Array forEach สามารถ break ได้?

ทำได้ 2 แบบ

1. ใช้ .some

Keyword: true 1 อัน

.some เป็นฟังก์ชันที่เอาไว้เช็คว่ามีตัวหนึ่งตรงตามเงื่อนไขไหม โดยมันจะค่อยๆ วิ่ง linear ไปเรื่อยๆ

เราสามารถทำให้มัน loop ไปเรื่อยๆ แล้วหยุดตัวที่ตามเงื่อนไขได้โดยการ return true ออกไป มันจะหยุด loop ทันที

const maybeHasNull = [1, 2, 3, null, 5, 6]

// เขียนด้วย for ปกติ
// จะหยุดถ้าเจอค่า null
for (let c of maybeHasNull) {
  if (c === null) break
  console.log(c)
}
// => 1 2 3

// เขียนด้วย `.some`
maybeHasNull.some((c) => {
  if (c === null) return true
  console.log(c)
  return false
})
// => 1 2 3

2. ใช้ .every

Keyword: ไม่มี false

.every เป็นฟังก์ชันที่เอาไว้เช็คว่าทุกตัวตรงตามเงื่อนไขไหม โดยจะค่อยๆ วิ่ง linear ไปเรื่อยๆ เหมือนกับ .some

ถ้าพูดง่ายๆ ก็คือตรงกันข้ามกับ .some แหละ โดยจะหยุดถ้ามีค่า false โดน return ออกมา

const maybeHasNull = [1, 2, 3, null, 5, 6]

// เขียนด้วย `.some`
maybeHasNull.some((c) => {
  if (c === null) return true
  console.log(c)
  return false
})
// => 1 2 3

// เขียนด้วย `.every`
maybeHasNull.every((c) => {
  if (c === null) return false
  console.log(c)
  return true
})
// => 1 2 3

เช็ค Value ยังไงไม่ให้บ้ง

JS/TS

NaN

NaN ต้องใช้ isNaN() (Number.isNaN()) เท่านั้น

a = NaN
isNaN(a) // -> true

undefined/null

เช็คตรง (===) ได้เลย

a = undefined
a === undefined // -> true

a = null
a === null // -> true

Primitive อื่นๆ

string (ทั้งเปล่าและไม่เปล่า), number (<เลข>, 0, -0, Infinity, -Infinity ที่ไม่ใช่ NaN), boolean, undefined, null เช็คตรง (===) ได้เลย

ปล. 0 === -0 แต่ในความเป็นจริง -0 ก็คือ 0 แหละ so ¯\_(ツ)_/¯

Array

เช็คว่าว่างไหม ใช้ .length === 0

a = []
a.length === 0 // -> true

เช็คว่าเป็น Array ไหม ใช้ Array.isArray()

a = []
Array.isArray(a) // -> true

Object

เช็คว่ามี property x ไหม ใช้ in — ข้อควรระวังคือ ต่อให้มีแต่ค่าเป็น undefined ก็ถือว่ามี (เพราะเอาไว้เช็คว่ามีไหม)

a = { x: undefined }
x in a // -> true

delete a.x
x in a // -> false

(คห.) ส่วนใหญ่ถ้าจะเช็ครวดไปเลยว่ามี property + ไม่เป็น falsy ก็ . หา property ไปเลย (if (a.x) ...)


Method .sort

JS/TS

ข้อควรรู้ของ method .sort

  • เป็น method แบบ in place แปลว่ามันจะ sort ตัวแปร/ref เดิมที่เก็บ (มี side effect)
  • เมื่อก่อนมันจะ sort แบบไม่ stable แต่ในปัจจุบัน stable แล้ว

Stable Sort คืออะไร? เวลาเราต้องเรียงข้อมูลหลายอย่าง เช่น ตามชื่อ -> ตามอายุ เรามักจะชินกับการ .sort() หลายๆ รอบ ซึ่งเมื่อก่อน JS sort แบบไม่ Stable แปลว่าชื่อที่เรียงไว้ก่อนอายุมีโอกาสไม่เรียงตามเดิม ทำให้เราต้องเขียน condition ใน sort เยอะๆ

data.sort((a, z) => {
  if (a.age === z.age) return a.name.localeCompare(z.name);
  return a.age - z.age;
});

แต่ในปัจจุบันการ sort ใน JS มัน Stable แล้ว แปลว่าตอนนี้เราทำตามข้างล่างได้แล้ว

data
  .sort((a, z) => a.name.localeCompare(z.name))
  .sort((a, z) => a.age - z.age);

ทำยังไงให้รู้ว่า sort จากน้อยไปมากหรือมากไปน้อย

วิธีง่ายๆ ที่จะทำให้รู้ว่ามันเรียงจากมากไปน้อยหรือน้อยไปมาก คือให้ใช้ params ของ lambda เป็น (a, z)

  • เรียงจากน้อยไปมาก ให้ return เป็น a - z (.sort((a, z) => a - z)) ก็คือเรียงจาก A ไป Z
  • กลับกันถ้าเรียงจากมากไปน้อย ให้ return เป็น z - a (.sort((a, z) => z - a)) ก็คือเรียงจาก Z ไป A

เรียงภาษาไทยให้ถูก

อย่างที่เรารู้ๆ กันว่า ถ้าจะ sort string จากน้อยไปมาก ใช้ .sort() เฉยๆ ก็ได้ หรือใช้ <, > ก็ได้

const data = ['Alex', 'Steve', 'Conner', 'xQc', 'Benjamin'];

// data.sort(); ใช้ได้เหมือนกัน 👍
data.sort((a, z) => {
  if (a < z) return -1; // -1 คือ เอา a ไปวางก่อนหน้า z
  if (a > z) return 1; // 1 คือ เอา a ไปวางหลัง z
  return 0; // 0 คือมันเท่ากัน
});
// -> ['Alex', 'Benjamin', 'Conner', 'Steve', 'xQc']

ดู algorithm ของ SortCompare(x, y) (default) ได้ที่ https://262.ecma-international.org/6.0/#sec-sortcompare

แต่ถ้าเป็นข้อมูลภาษาไทยจะมีโอกาสเรียงผิด เช่น

const data = ['เชษฐา', 'ชัชชาติ', 'สมเกียรติ', 'ไชยวัฒน์', 'เยาว์ลักษณ์'];

data.sort();
// -> ['ชัชชาติ', 'สมเกียรติ', 'เชษฐา', 'เยาว์ลักษณ์', 'ไชยวัฒน์'] ❌
// ต้องเอา `ช` ขึ้นก่อน `ย` และ `ย` ต้องอยู่ก่อน `ส` ด้วย

วิธีแก้คือ ให้ใช้ <string>.localeCompare(<string>)

const data = ['เชษฐา', 'ชัชชาติ', 'สมเกียรติ', 'ไชยวัฒน์', 'เยาว์ลักษณ์'];

data.sort((a,z) => a.localeCompare(z));
// -> ['ชัชชาติ', 'เชษฐา', 'ไชยวัฒน์', 'เยาว์ลักษณ์', 'สมเกียรติ'] ✅

System Font Stack

CSS

Sans-serif

body {
  font-family: FONT_NAME, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji",
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif;
}

Monospace

code {
  font-family: FONT_NAME, ui-monospace, SFMono-Regular, Menlo, Monaco,
    Consolas, "Liberation Mono", "Courier New", monospace, "Segoe UI Emoji",
    "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji";
}

Serif

h1 {
  font-family: FONT_NAME, ui-serif, Georgia, Cambria, "Times New Roman", Times,
    "Segoe UI Emoji", "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji",
    sans-serif;
}

Tips

  • ชื่อฟอนต์ที่มีช่องว่าง ควรมี "..." เสมอ
  • เอาฟอนต์ไทยขึ้นก่อนฟอนต์ละติน แต่ถ้าเป็นโค้ด ให้เอาละตินขึ้นก่อนไทย (ฟอนต์ไทย 99.99% ไม่ใช่ Monospace เวลาหาตัวไทยไม่เจอจะได้ Fallback ไปหาตัวไทยที่ไม่ monospace)

Ref


Array splice หรือ slice

JS/TS

TL;DR ถ้าจะเอามาใช้แบบ .map หรือ .filter ใช้ .slice(i_เริ่ม, i_จบ)

Slice

Slice เอาไว้คัท shallow copy ของ array ออกมา

// .slice(i_เริ่ม, i_จบ)
const a = [1, 2, 3, 4, 5]

a.slice() // [1, 2, 3, 4, 5]
a.slice(1) // [2, 3, 4, 5]; เริ่มจาก i=1
a.slice(1,2) // [2]; เริ่มจาก i=1 ไปจนกว่าจะถึง i=2 (ไม่เอา i=2)
a.slice(1,1) // [];
a.slice(1,-1) // [2, 3, 4]
a.slice(-1) // [5]; เอา Last
a.slice(-2) // [4, 5]; เอา Last 2

a // [1, 2, 3, 4, 5]; ยังเหมือนเดิม ไม่มี side effect

Splice

Splice เอาไว้แก้ array ต้นทาง ลบได้ เพิ่มได้

// .splice(i_เริ่ม, ลบกี่ตัว, ...ตัวที่เพิ่ม)
let a = [1, 2, 3, 4, 5] // const ได้แหละถ้าไม่ reassign

a.splice(1, 0, 1.5) // [1, 1.5, 2, 3, 4, 5]; เพิ่ม 1.5 ที่ i=1 โดยไม่ต้องลบอะไรหลังจากนั้น
a.splice(3, 3) // [1, 1.5, 2]; ลบ 3 ตัวตั้งแต่ i=3
a.splice(3, 0, 3, 4) // [1, 1.5, 2, 3, 4]; เพิ่ม 3, 4 ไปตั้งแต่ i=3
a.splice(-1) // [1, 1.5, 2, 3]; เก็บไว้ -1 + 5 (a.length) = 4 ตัว ถ้าเลขติดลบจะเหมือน a.splice(เลข + length) (a.splice(-1 + 5) = a.splice(4))
a.splice(1) // [1]; เก็บไว้ 1 ตัว
a.splice() // [1]; ไม่ทำอะไร

a // [1]; ไม่เหมือนเดิมแล้วเพราะมัน modify มาตลอดทาง

Param i_เริ่ม

เงื่อนไขค่าที่ใช้
0 <= i_เริ่ม < lengthi_เริ่ม
-length <= i_เริ่ม < 0i_เริ่ม + length
i_เริ่ม < -length0
length <= i_เริ่มi_เริ่ม ตัวที่ไม่มีก็ข้ามไป เป็นตัวชี้สำหรับ ...ตัวที่เพิ่ม

Ref


Safari 100vh + Target Safari

CSS

@supports (-webkit-touch-callout: none) {
    body {
        min-height: -webkit-fill-available;
    }
}

ใช้ @supports (-webkit-touch-callout: none) เพื่อ target Safari อย่างเดียว


Target Firefox only

CSS

@supports (-moz-appearance: none) {
  /* Target Firefox */
}

Format วันที่ พ.ศ.

JS/TS

const date_formatter = new Intl.DateTimeFormat('th-TH', {
  dateStyle: 'short'
});
const formatDate = (date: Date) => date_formatter.format(date);

formatDate( new Date(2022,9,31) ) // 31/10/65

เวลากรอก กรอก ค.ศ. ตามปกติ แล้วเดี๋ยว Intl จะ format เป็น พ.ศ. ให้เลย

dateStyle แบบต่างๆ

dateStyleผลลัพธ์
'short''31/10/65'
'medium''31 ต.ค. 2565'
'long''31 ตุลาคม 2565'
'full''วันจันทร์ที่ 31 ตุลาคม พ.ศ. 2565'

Ref

DateTimeFormat() - MDN


ทำให้ Svelte Component ที่มี class อยู่แล้ว ใส่ class จากข้างนอกได้


let clazz = '';
export { clazz as class };

Usage

<script>
  let clazz = '';
  export { clazz as class };
</script>

<div class="scoped-class {clazz}">
...
</div>

อย่าลืมครอบ :global(...) ใส่ class ของตัวข้างนอกด้วย


Comment Folding in VSCode


// #region REGION NAME

// Etc. code

// #endregion

บริเวณใน #region ถึง #endregion จะสามารถกดพับได้ในแถบบรรทัดข้างซ้าย


JS/TS groupBy

JS/TS

const groupBy = <T, K extends keyof any>(arr: T[], groupFn: (element: T) => K): Record<K, T[]> =>
  arr.reduce(
    (r, v, _i, _a, k = groupFn(v)) => ((r[k] || (r[k] = [])).push(v), r),
    {} as Record<K, T[]>
  );
const groupBy = (arr, groupFn) =>
  arr.reduce((r, v, _i, _a, k = groupFn(v)) => ((r[k] || (r[k] = [])).push(v), r), {});

Usage

const data = [
  { name: 'John', status: 'online' },
  { name: 'Jane', status: 'online' },
  { name: 'Linda', status: 'offline' },
]

groupBy(data, e => e.status)
// {
//   "online": [
//     { "name": "John", "status": "online" },
//     { "name": "Jane", "status": "online" }
//   ],
//   "offline": [
//     { "name": "Linda", "status": "offline" }
//   ]
// }

Credits:


Filter null in TypeScript

JS/TS

const removeNull = (element: any): element is Exclude<typeof element, null> => !!element;

Usage

[1, true, null, 'lmao'].filter(removeNull) // [1, true, 'lmao'] with narrowed type 

Credit to @dtinth. From https://twitter.com/dtinth/status/1315714173242728448/photo/1


แปลง boolean string เป็น boolean

JS/TS

variable === 'true'

Example

let str = 'true';
console.log(str === 'true'); // true

str = 'false'
console.log(str === 'true'); // false

External link attributes

HTML

<a href="..." target="_blank" rel="nofollow noopener noreferrer">...</a>

nofollow

  • ใช้เพื่อไม่ให้ SEO Bot ตามลิงก์นั้นไปต่อ ซึ่งอาจส่งผลให้ได้คะแนน Backlink น้อยลง
  • เกิดขึ้นมาเพื่อแก้ปัญหา spam comment เอา backlink
  • สามารถเอาไว้ใช้กับลิงก์ไปหน้า technical แบบ signup form/dashboard ได้
  • ช่วยลดความเสี่ยงว่าเราไปสแปมเว็บอื่นเอา backlink ได้

noopener

  • ไม่ให้เบราเซอร์เอา object window ของหน้าก่อนหน้าใส่เข้าไปใน window.opener ของหน้าถัดไป
  • ไม่อย่างนั้นหน้าถัดไปจะสามารถแอบแก้หน้าก่อนหน้าด้วยการใช้ window.opener ได้
  • ในปี 2021 เบราเซอร์ต่างๆ จะแอบเซ็ตค่า rel=noopener ของ a[target=_blank] ไว้ให้แล้ว (ตามสเปกนี้)

noreferrer

  • บอกให้เบราเซอร์ไม่ต้องใส่ header Referer ลงไปใน request
  • มีผลข้างเคียงแค่ว่า analytic จะมองว่าเป็น direct traffic (พิมพ์ลิงก์เข้ามาเองโดยตรง) แทนที่จะมาจากเว็บอื่น แค่นั้น

Refs


ใส่ CSS ให้ <details> กับ <summary>

CSS HTML

details {
  border: 1px gray solid;

  > summary {
    padding: 8px;
    cursor: pointer;

    list-style: none;

    &::-webkit-details-marker {
      display: none;
    }

    > span {
      display: flex;
      justify-content: space-between;

      width: 100%;

      &::after {
        content: "👇";
      }
    }
  }

  > div {
    padding: 8px;
    border-top: 1px gray solid;
  }

  &[open] > summary > span::after {
    transform: rotate(180deg);
  }
}

Structure:

<details>
  <summary><span>What is Details & Summary?</span></summary>
  <div>
    The &lt;details&gt; HTML element creates a disclosure widget in which information is visible only when the widget is toggled into an "open" state. A summary or label must be provided using the &lt;summary&gt; element.
  </div>
</details>

วิธีลบ ▼ ของเดิมที่มากับ <details>

summary {
  list-style: none;

  &::-webkit-details-marker {
    display: none;
  }
}

ทำไมต้อง nest <span> ข้างใน <summary>

  1. Safari (ในเวอร์ชันนี้) ทำ <summary> เป็น flex ไม่ได้ แก้ไขแล้วใน Safari Technology Preview
  2. <summary> ใส่ได้แค่ Phrasing content หรือ Heading content เท่านั้น (MDN ของ <summary>)

จาก 2 ข้อข้างบน ทำให้ไม่สามารถจับ <summary> flex โดยตรงได้ และไม่สามารถยัด <div> ลงไปใน <summary> ได้


บังคับ Emoji ใน HTML

HTML

Emoji บางตัวบน Windows เช่น ♥ 🗺 ⚙ จะ render เป็นตัวอักษร (♥︎ 🗺︎ ⚙︎) ถ้าต้องการกันไม่ให้มัน render เป็น text ให้ใส่

&#xFE0F;

ตามหลัง เช่น ♥&#xFE0F; จะได้ ♥️

ในทางกลับกัน ถ้าอยากให้ Emoji render เป็น text ให้ใช้

&#xFE0E;

เช่น ♥&#xFE0E; จะได้ ♥︎

อธิบายเพิ่มเติม

&#xFE0E; และ &#xFE0F; คือ Variation Selector สำหรับ Emoji โดยเฉพาะ เอาไว้กำกับว่าจะ render เป็นแบบไหน โดยให้ใส่ตามหลัง Emoji ที่ต้องการ