มาสรุปสิ่งที่ได้จาก js21days challenge [week1]

Learning Apr 15, 2020

รอบแรกลืมกดสมัครคอร์สฟรีและเต็มไว้ในนาทีแรก รอบสองไม่พลาดที่จะกดสมัครจ้า เผื่อเราจะถนัด js ขึ้นมาบ้าง (รอบนี้เหมือนรับสมัครเรื่อยๆยังไม่ปิดรับน้าา)

https://www.js21.dev/

การสมัคร กดเข้าไปในเว็บ https://www.js21.dev/ เพื่อกรอกข้อมูลของเรา จากนั้นจะมี email ส่งมา เป็นรายละเอียด course, source code, video และ Facebook Group

ในคอร์สเป็นการฝึกเขียน javascript นั่นแหละ มีตัวอย่างให้ลองทำด้วย ให้ลองทำถึง 21 ตัวอย่าง แบ่งเป็นวันๆ ซึ่งผู้สอนเองไม่ได้บังคับว่าต้องเป๊ะๆ แต่ควรเรียนวิดีโอแรกก่อน เพราะเพื่อให้เข้าใจโครงสร้างของโปรเจกจ้า ดังนั้นทางนี้เลยเอาเวลาวันหยุดเสาร์อาทิตย์มาเรียนไปเรื่อยๆจนครบเนอะ บล็อกนี้พยายามสรุปแต่พยายามไม่ละเอียดนะ แบบกลับมาอ่านดูได้งี้

ทางเราเลยจะทำหน้าเว็บอีกอันเพื่อเอาไปแสดงผลงานที่เราทำเองเนอะ ตอนนี้ยังไม่ได้แค่ทำหน้าแรกเท่านั้นเองงง

https://js21days-challenge.firebaseapp.com/

ก่อนเรียน จริงๆทำแบบทดสอบในเว็บ Skooldio มานานเป็นปีๆแล้ว ลงสักหน่อย ถือว่า pre-test ก่อนเรียนแล้วกันเนอะ

จิ้มไปทำได้ที่ https://skillscore.skooldio.com/ ก่อนหน้านี้ทำแบบทดสอบไปด้วยความรู้ js แบบงูๆปลาๆได้ 105 คะแนนอยู่ในระดับ Proficient จ้า

โครงสร้างโปรเจก

เราอ้างอิงจากตัว Falling Show เนอะ

  • index.html : หน้าเว็บอะเนอะ ในที่นี้มีการเพิ่ม element ที่ชื่อว่า canvas ลงไป เพื่อวาดรูปต่างๆลงไปในนี้
  • style.css : ในที่น้ีเป็นการ set background image ให้เต็มจอ
  • start.js : เอาไว้แก้ไขโค้ด ซึ่งเราจะเข้ามาเล่นตรงนี้มากที่สุด (ปกติเรามักจะเห็น main.js กันเนอะ)

ส่วนไฟล์ finished.js คือไฟล์ที่เสร็จสมบูรณ์ และมีการ refactor ในการทำเป็นพวก functional สำหรับเอาไปขึ้น production แบบสวยๆ

Day1: Falling Snow

เป็นการให้เจ้าหิมะมาวิ่งเล่นใน canvas ของเรา

  • setup() : กำหนดขนาดของ canvas ที่จะแสดงหิมะ โดยให้แสดงแบบเต็มหน้าจอตามขนาดของหน้าต่าง
  • สร้าง run() เพื่อให้ setup() ทำงาน (ทุก function ที่สร้างมานั้น จะถูกเรียกใช้ใน function นี้)
  • setup() จะ return 3 สิ่งคือ canvas, canvasContext เราจะทำเป็น 2 มิติ ดังนั้นจะ set เป็น canvas.getContext('2d'), numberOfSnowBalls
  • สร้างเจ้าหิมะแต่ละลูก createSnowBalls(canvas, numberOfSnowBalls) สร้าง array ให้มีจำนวนเท่ากับ numberOfSnowBalls และ return พิกัด x และ y ออกมาก่อน โดยการ fix ค่า ผลคือ ทุกลูกอยู่จุดเดียวกันหมด เหมือนวาดที่เดียว จากนั้นสร้าง random() เพิ่มเพื่อ random พิกัดของหิมะ และวาดรูปเจ้าหิมะ drawSnowBall(canvasContext, snowBall) โดยให้วาดวงกลมตามพิกัด และใส่สีขาว ภายหลังมีการ random opacity และ radius ด้วย
  • ตอนแรกขยับทีละ 5 กลายเป็นจุดๆเส้นๆ ดังนั้นทุกครั้งที่มีการวาดใหม่ จะเคลียร์ของเก่าออกก่อน canvasContext.clearRect(0, 0, canvas.width, canvas.height);
  • แล้วมีการเพิ่มความเร็ว moveSnowBall(canvas, snowBall) โดยพิกัดจะบวกเพิ่มกับความเร็วที่ random มาอีกทีนึง แต่หิมะตกลงไปข้างล่างแล้วหายไปเลย ดังนั้น เมื่อลอยไปชนขอบด้านซ้ายหรือขวา หรือตกลงไปแล้ว ให้กลับมาใหม่
  • สรุป snowball 1 ลูก จะมี พิกัด x, y แล้วก็ attribute ของหิมะ opacity, radius ตบท้ายด้วยความเร็ว speedX, speedY
  • เจ้า setInterval อันนี้สรุปแบบรวดรัดว่าเราใช้ในการบอกว่า ทำสิ่งนั้น ทุกๆเท่าไหร่ อย่างอันนี้สร้างหิมะลูกใหม่ ทุกๆ 50ms
https://js21days-challenge.firebaseapp.com/01-falling-snow/index.html

Day2 : Countdown Timer

นับถอยหลังเข้าสู่ปีหน้า

  • ประกาศค่า const ของ SECOND, MINUTE, HOUR, DAY ไว้ข้างนอก
  • สร้าง countDown() โดย get เวลาปัจจุบัน และ set เวลาปีหน้า จากนั้นคำนวณระยะห่างของเวลาว่าเหลืออีกเท่าไหร่จะถึงปีหน้า แน่นอนว่าค่าที่ออกมาจะเป็นหน่วย ms
  • จากนั้นเรามาคำนวณวันที่เหลือ ชั่วโมงที่เหลือ นาทีที่เหลือ และวินาทีที่เหลือ ตอนแรกเราจะเริ่มที่วันที่เหลือก่อน เราจะ set text ที่ element days โดยคำนวณว่าเหลือวันเท่าไหร่
const daysElement = document.getElementById('days');     daysElement.innerText = Math.floor(unixTimeLeft / DAY);

แล้วก็คำนวณของ hours ต่อ โดยการ mod เอาวันออกก่อน

const hoursElement = document.getElementById('hours');     hoursElement.innerText = Math.floor(unixTimeLeft % DAY / HOUR);
  • พบว่าโค้ดมัน duplicate กันแน่ๆ 4 ชุด ดังนั้นเราจึงสร้าง setElementInnerText(id, text) ขึ้นมา เพื่อ set text element
function setElementInnerText(id, text) {
  const element = document.getElementById(id);
  element.innerText = text;
}
  • พอเอา countDown() ไปใส่ใน run() พบว่า เลขคำนวณถูก แต่มันไม่ countdown ลงมา จึงให้คำนวณใหม่ทุกๆวินาที
setInterval(countDown, SECOND);

จากนั้นก็ได้ตัว countdown มาแล้วจ้า ประยุกต์ใช้กับอื่นๆได้อีกเนาะ

https://js21days-challenge.firebaseapp.com/02-countdown-timer/index.html เดี๋ยวมาแก้ link เพิ่ม แปลกๆอยู่

Day3 : Async/Await

เรื่องสำคัญที่เราต้องรู้เมื่อเราเขียน javascript!!

1) Asynchronous code ใน javascript ทำงานอย่างไร?

การทำงานแบบ Asynchronous คือ โค้ดที่ถูกเรียกใช้ก่อน อาจจะเสร็จทีหลังก็ได้ เช่นในตัวอย่าง simulateAsyncAPI() ขึ้นมา แบบนี้

function simulateAsyncAPI(text, timeout) {
  setTimeout(() => console.log(text), timeout);
}

เป็นการจำลอง code แบบ asynchronous อย่างใน function นี้จะบอกว่า จะพ่น log ไปเมื่อถึงเวลา timeout เช่น พิมพ์ A เมื่อเวลาผ่านไป 1000 ms

simulateAsyncAPI('A', 1000);

และเมื่อเอา statement เหล่านี้มาวางเรียงกันหล่ะ คิดว่าอันไหนทำเสร็จก่อนหล่ะ?

simulateAsyncAPI('A', 1000);
simulateAsyncAPI('B', 500);
simulateAsyncAPI('C', 100);

คำตอบคือ C เพราะว่ามีเวลา timeout ไวกว่าตัวอื่น คือ เมื่อครบ 100 ms จะพิมพ์ C พอครบ 500 ms จะพิมพ์ B และพิมพ์ A เมื่อครบ 1000 ms

แล้วถ้าสมมุติว่าเราให้มันทำทีละอย่างหล่ะ? เช่นอ่านไฟล์ A เสร็จแล้วค่อยอ่านไฟล์ B ทั้งๆที่ไฟล์ B อาจจะเล็กกว่า เราจะทำอย่างไรหล่ะ?

2) Callback

นิยมใช้มากใน node.js  Callback ก็คือ function ที่เราเรียกใช้หลังจากทำงานเสร็จแล้ว ตัวอย่าง สร้าง function มาแบบเดิมเลย เพิ่ม callback ลงไป การทำงานก็คล้ายๆเดิม พ่น log เมื่อถึงเวลา timeout จากนั้นทำ callback() ตามมา

function simulateAsyncAPI(text, timeout, callback) {
  setTimeout(() => {
    console.log(text);
    callback();
  }, timeout);
}

เช่น เมื่อครบ 1000 ms ให้พิมพ์ A แล้วพิมพ์ Callback ต่อ

simulateAsyncAPI('A', 1000, () => {
  console.log('Callback');
});

จากตัวอย่างในตอนแรก ทำอย่างไรให้พิมพ์ A B C เรียงกันหล่ะ แบบนี้ไง เรียกซ้อนๆกัน

simulateAsyncAPI('A', 1000, () => {
  simulateAsyncAPI('B', 500, () => {
    simulateAsyncAPI('C', 100, () => {
    });
  });
});

แต่ทีนี้ของ C เป็น Callback เปล่าๆ ถ้าใส่ไปแบบนี้หล่ะ

simulateAsyncAPI('A', 1000, () => {
  simulateAsyncAPI('B', 500, () => {
    simulateAsyncAPI('C', 100);
  });
});

มันจะ error เพราะ Callback มัน undefined ไม่ใช่ function เนอะ จึงต้องมีการ check ว่าเราใส่ Callback ไปหรือไม่

function simulateAsyncAPI(text, timeout, callback) {
  setTimeout(() => {
    console.log(text);
    if (callback) {
      callback();
    }
  }, timeout);
}

แต่ถ้าซ้อนไปเยอะๆ มันจะทำให้อ่านยาก แก้ไขยาก maintenance ยาก เลยกลายเป็น Callback Hell ดังนั้นจึงเกิด concept ใหม่ๆขึ้นก็คือ Promise นั่นเองงงง

3) Promise

สร้าง function ขึ้นมาแบบเดิม แต่เราจะ return ด้วย Promise object ที่มี constructor คือ function ตัวหนึ่ง ที่มี parameter คือ resolve และ reject

function simulateAsyncAPI(text, timeout) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(text);
      resolve();
    }, timeout)
  });
}

เมื่อเราเรียกใช้งาน asynchronous เรียบร้อยแล้ว จากนั้นจะต้องเรียกใช้งาน resolve เป็นการบอก Promise ของเราว่า asynchronous ทำงานเสร็จแล้วนะ จะคล้ายๆ callback แต่การเรียกใช้งานไม่ต้องใส่ callback เข้าไป แต่จะใช้ method then จะมีการเรียกใช้เมื่อ asynchronous ทำงานเสร็จแล้ว

ถ้าใน method then จะมีการ check ว่าถ้า return เป็น Promise แล้ว จะรอให้ Promise เสร็จก่อน สิ่งที่ return ถัดไปจะถูก pass เข้า then ถัดไปแบบอัตโนมัติเลย เช่น เมื่อ Promise ของ B ทำงานเสร็จแล้ว ใส่ then ต่อเพื่อไปทำงานที่ C

simulateAsyncAPI('A', 1000)
  .then(() => {
    return simulateAsyncAPI('B', 500);
  })
  .then(() => {
    return simulateAsyncAPI('C', 100);
  })

และเราสามารถใส่ catch เพื่อจัดการ error ได้ เช่น ถ้ามี error ให้ show error ออกมา ซึ่งการ show error เราจะใช้ reject ในการบอก error message ว่าให้ออกมาเป็นอะไร

function simulateAsyncAPI(text, timeout) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (text === 'B') return reject('B got rejected');
      console.log(text);
      resolve();
    }, timeout)
  });
}
simulateAsyncAPI('A', 1000)
  .then(() => {
    return simulateAsyncAPI('B', 500);
  })
  .then(() => {
    return simulateAsyncAPI('C', 100);
  })
  .catch((error) => {
    console.error(error);
  })

ผลออกมาจะเป็นดังนี้

เป็นการเรียกใช้ที่สวยงามกว่า Callback เนอะ แต่การเรียกใช้ Promise จะมีการ chaining ไปเรื่อยๆ ก็จะเริ่มกลับมาอ่านยากอีกหล่ะ ซึ่งนำไปสู่ concept ใหม่นั่นคือ

กว่าจะหารูปเจอ เฮ้ออออ อันนี้จากงาน Google I/O Bangkok Extended 2019 ที่พี่ตี๋เล่นมุขแล้วทุกคนในห้องเงียบเลย เนื้อหาคือใน Firebase Cloud Function รองรับ async/await ตาม node8 แล้ว

4) async/await

คง simulateAsyncAPI() ของเดิมไว้ เพิ่มเติมคือการเรียกใช้งาน

จากนั้นสร้าง run() ขึ้นมา โดยมีคำว่า async อยู่ด้านหน้า function และไส้ในก็ต้องตามด้วย await เสมอ การทำงานเหมือนเรียกใช้ function ปกติเลย

async function run() {
  await simulateAsyncAPI('A', 1000);
}

ลองเรียกต่ออีกนิดนึง

async function run() {
  await simulateAsyncAPI('A', 1000);
  await simulateAsyncAPI('B', 500);
  await simulateAsyncAPI('C', 100);
}

จะพบ error เนื่องจากเรายังไม่ได้ handle error ที่ throw มาจาก asynchronous นั่นเอง

จึงต้องใส่ try และ catch ลงไป ผลจะเหมือนกับตอน Promise เมื่อกี้เลย

async function run() {
  try {
    await simulateAsyncAPI('A', 1000);
    await simulateAsyncAPI('B', 500);
    await simulateAsyncAPI('C', 100);
  } catch(error) {
    console.error(error);
  }
}

และเมื่อ comment code ตรงนี้ทิ้ง มันจะก็จะพิพม์ A B C เหมือนเดิมหล่ะ

if (text === 'B') return reject('B got rejected');

Day4 : Eye Rolling

ตรวจจับการเคลื่อนไหวของเม้าส์ โดยสร้าง emoji ให้เคลื่อนที่ดวงตาตามเม้าส์ที่เราเลื่อนไปได้ และนี่คือหน้าตาน้องเบื้องต้นจ้า

ในไฟล์ index.html มีการสร้าง class ใบหน้า ที่มีตาสองข้าง และปาก

<body>
  <div class="face">
    <div class="eyes">
      <div class="eye"></div>
      <div class="eye"></div>
    </div>
    <span class="mouth"></span>
  </div>
  <script src="./start.js"></script>
</body>

แต่ละส่วนถูกวาดใน style.css เรียบร้อยแล้วจ้า

  • ดึง element ของ body แล้วเพิ่ม event listener  เพื่อฟังการเคลื่อนไหวตอนเลื่อนเม้าส์ เช่นในที่นี้ถ้ามีการเลื่อนเม้าส์ จะให้พิมพ์ Called ออกมา
function run() {
    const bodyElement = document.querySelector('body');

    function onMouseMove() {
      console.log('Called');
    }

    bodyElement.addEventListener('mousemove', onMouseMove);
  }
  • จากนั้นมาหาพิกัดตำแหน่งที่เราเลื่อนเม้าส์ และดูว่าเลื่อนลูกตาไปกี่องศา

โดยหา element ของแต่ละลูกตา ด้วย eyeElement.getBoundingClientRect() และคำนวณจากมุมซ้ายบนของหน้าจอ ไปยังค่า left และ top ของลูกตา

  • จากนั้นคำนวณจุดศูนย์กลางของดวงตา แล้วไปคำนวณ radian และได้ angle ต่อ แล้วเอาไป transform ลูกตา เป็นอันเสร็จ
https://js21days-challenge.firebaseapp.com/04-eye-rolling/index.html เดี๋ยวมาเปลี่ยนน้องเพิ่ม

Day5: Watercolor Painting

ทำ canvas วาดสีนํ้ากันเถอะ

  • ไฟล์ css นั้น set เจ้า cursor เป็นกากบาท
  • set ขนาดของ canvas ให้เต็มจอเหมือนตอนทำหิมะตก และให้ context เป็น 2d แน่นอนว่าเราวาดผ่าน context
  • กำหนดค่า previousPoint ให้ค่า x = 0 และ y = 0 เป็นค่า default
  • เพิ่ม listener mousemove ให้เหมือนตอนทำลูกตากลิ้งๆแบบเมื่อกี้ และสร้าง onMouseMove({ pageX, pageY }) เพิ่ม โดยเริ่มวาดจากพิกัดที่ได้มา จากนั้น set ให้จุดจบและจุดตัด ที่มี default เป็นสี่เหลี่ยมเปลี่ยนเป็นวงกลม กำหนดความกว้างและสี
  • วาดจากจุด previous ไป current และลงสี
  • ตอนแรกเริ่มวาด context.beginPath(); พอวาดเสร็จอย่าลืมปิดด้วย context.closePath();
ผลที่ได้ มันจะแปลกๆ ตรงเป็นเส้นๆนี่แหละ เป็นเส้นจาก 0,0 ไป x,y
  • ผลมันแปลกๆ เพราะเราเริ่มที่ 0,0 และจบที่ x,y ดังนั้นเราจะ update เจ้า previous เท่ากับ current อันมะกี้ ออกมาเป็นเส้นๆหล่ะ แต่มันควรเริ่มตอนแรกที่ 0,0 จริงเหรอ? มันควรจะเริ่มจากจุดที่เม้าส์ไปวางเนอะ
  • ดังนั้นเพิ่ม event อีกตัวคือ mouseenter เวลาที่เราเลื่อนเม้าส์เข้าไป จะให้มัน init ค่า previous จากตอนนี้เลย ประมาณนี้ให้เห็นภาพง่ายๆ
function onMouseEnter({ pageX, pageY}) {
  previousPoint.x = pageX;
  previousPoint.y = pageY;
}
canvas.addEventListener('mouseenter', onMouseEnter);

ผลที่ได้เริ่มสวยงามขึ้น

  • แต่เราอยากให้แบบเวลาเลื่อนเม้าส์เร็วๆสีจะจางลง เส้นจะเล็กลงอะเนอะ พอเลื่อนเม้าส์ช้าๆสีจะเข้มขึ้น และเส้นใหญ่ขึ้น แบบวาดสีนํ้าจริงๆ ดังนั้นจึงมีการคำนวณ distance เพื่อเอาไปคำนวณ opacity และ ความกว้างของเส้น เป็นอันเสร็จจ้า
https://js21days-challenge.firebaseapp.com/05-watercolor-painiting/index.html เดี๋ยวค่อยมา random สีเพิ่ม

Day6: Duck Hunt

เกมส์ยิงเป็ดจ้าาาาา random เป็ด 5 ตัวเพื่อยิงเป็ดจ้า

  • text "YOU WIN" ซ่อนไว้ก่อน แล้ว css ทำพื้นหลัง ตัวเป็ด cursor กากบาท
  • createDucks() สร้างเป็ดด้วย array 5 ตัว โดย map พิกัด x และ y ก่อน ซึ่งค่า x random มาอีกที (เจ้า random จะอธิบายการทำงานในของวันแรก) จากซ้ายสุดไปขวาสุด ส่วนค่า y จะมาที่ความสูง ให้บินจากล่างขึ้นบน และมีการ speedX ไปซ้ายขวา และ speedY ให้บินขึ้นอย่างเดียวไม่ให้บินลง
  • เอาเป็ดที่ได้มาหลายตัวมาสร้าง element ที่ setupDuckElement โดยสร้าง class ที่ชื่อว่า duck กำหนดพิกัดของเป็ดตัวนั้น เป็น pixel ใส่ภาพน้องเป็ดลงไปเป็น default ก่อน และเพิ่ม element ที่เพิ่งสร้างไปยัง body
  • นำเป็ดที่ได้ใส่การเคลื่อนที่ moveDuck(duckElement, duck) โดยรับค่า left กับ top จาก getBoundingClientRect() และ set พิกัดของเป็ดใหม่ ตอนแรกเป็ดยังไม่บิน ดังนั้นจึงใส่ setInterval ไปด้วย
  • ทำให้เหมือนเป็ดบิน โดยสร้าง getDuckBackgrounImage(duck, duckElement) เพื่อดูว่ามันบินไปทางซ้ายหรือขวา และถ้า set เป็นรูป 1 ไปแล้ว ให้เปลี่ยนเป็นรูป 2 จะเหมือนน้องเป็ดบินมากขึ้น
  • พอเป็ดบินก็เหมือนหิมะลอย ออกนอกจดแล้วหายไปเลย ดังนั้นจะมันบินกลับโดยเอา speed แต่ละแกน คูณกับ -1
  • จากนั้นสร้าง function ยิงเป็ด โดยเพิ่ม event listener addEventListener('click', shootDuck) และเพิ่ม shootDuck(event) รับ target ของ element จากนั้นให้มันค่อยๆตกลงมา และถ้ายิงเป็ดครบแล้ว (ดูแล้วไม่มี element ที่ชื่อ duck แล้ว) จะขึ้น "YOU WIN" โดยให้ opacity เป็น 1 เป็นอันจบจ้า
https://js21days-challenge.firebaseapp.com/06-duck-hunt/index.html มายิงกันเองจ้า

Day7: Konami Code

สมัยยุค '90 ก็เป็นยุครุ่งเรืองของเกมส์ตลับ และเกมส์นึงที่ดังมากๆก็คือ Contra นั่นเอง สร้างโดยบริษัท Konami และมี Konami Code เป็นสูตรลับ "ขึ้น ขึ้น ลง ลง ซ้าย ขวา ซ้าย ขวา บี เอ" ทำให้เราได้ตัวละครลับ หรือได้พลังชีวิตเพิ่ม

มีหลายๆบริษัทหรือเว็บไซต์ที่เอาเจ้า Konami Code มาใช้ เช่น

  • Bank of Canada เมื่อกดสูตรเสร็จแล้วจะมีเสียงเพลงและเงินตกลงมา
https://www.bankofcanada.ca/banknotes/bank-note-series/commemorative-notes/banknote150/
  • หรือของ Buzzfeed เมื่อกดสูตรแล้วจะมีลูกกลมๆหล่นมาจากด้านบน (ฟังเสร็จได้ content ไปทำต่อเลยทีเดียววว
https://www.buzzfeed.com/

ในตอนนี้จะทำเว็บกดสูตรเสร็จแล้วหิมะไหลลงมาจ้าา

  • เราจะเพิ่ม event listener ตอนกด keyboard document.addEventListener('keydown', onKeyDown); เมื่อเอา event ไป log ดู จะได้แบบนี้ เอา key ที่ได้ไปใช้ต่อ
  • สร้าง array ที่ชื่อว่า konamiCode ใส่สูตรไว้ และสร้างตัวแปร index เพื่อเอาไป check ว่ากดถูกตามสูตรไหม ถ้าใช่ ก็จะมีการเพิ่ม index ขึ้นหนึ่ง ถ้าไม่ให้ reset ค่ากลับไปเป็น 0
event.key === konamiCode[index] ? index++ : index = 0;

ถ้า index มีค่าเท่ากับ konamiCode.length แล้ว ให้แสดงหิมะตก เป็นอันจบจ้า เยเย้

มากดเล่นที่ลิ้งด้านล่างจ้า

https://js21days-challenge.firebaseapp.com/07-konami-code/index.html

ตอนนี้เราก็เรียนจบไป 7 วันแล้ว โค้ดก็อัพลง github แล้ว จะมีอัพเดตเรื่อยๆเนอะ

mikkipastel/js21days
my homework for study with https://www.js21.dev/. Contribute to mikkipastel/js21days development by creating an account on GitHub.

อีก 14 วันต่อไปจะเป็นอย่างไรต่อ ติดตามได้จ้าาา

อย่าลืมกด like กด share บทความกันด้วยนะคะ :)

Posted by MikkiPastel on Sunday, 10 December 2017

14 วันถัดไปจ้า

มาสรุปสิ่งที่ได้จาก js21days challenge [week2]
หลังจากจบ week แรกแล้ว มาต่อ day 8 กันต่อเลยดีกว่า
มาสรุปสิ่งที่ได้จาก js21days challenge [week3]
มาถึงช่วงโค้งสุดท้ายของคอร์สนี้กันแล้วนะจ๊ะ เลยมาเล่าเรื่องของการทำเว็บส่งการบ้านแบบสั้นๆด้วยจ้า เราจะใช้ Firebase Hosting เพื่อเพิ่ม site ใหม่ในโปรเจก Firebase ส่วนตัวของเรา จะได้เว็บส่งการบ้านนี้มา

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.