.reduce function

เนื้อหาของคอร์สนี้ เรียบเรียงขึ้นจาก React.js Fundamental
หากยังจำกันได้ ใน tutorial บทแรกๆ ของ ซีรี่ย์ React Fundamental นี้มีการกล่าวถึงเรื่องการเขียนโปรแกรมแบบ Declarative โดยยกตัวอย่าง function หน้าตาประหลาดๆ มาตัวหนึ่งชื่อว่า .reduce() ขึ้นมา

ฟังก์ชั่น reduce() (รวมถึง map() ด้วย) มีความสำคัญต่อการเขียนโปรแกรมแบบ declarative ใน Javascript อย่างมาก เพราะมีการนำมาใช้บ่อย ดังนั้น หากไม่มาทำความเข้าใจกับมันให้ลึกพอ อาจจะเกิดธาตุไฟเข้าแทรกได้

มาลองดูตัวอย่างแรกกันเลย

นี่คือฟังก์ชั่นสำหรับการหาค่าเฉลี่ยของ array ที่ชื่อ scores โดยมีวิธีการดังนี้

เรียกใช้ reduce() ฟังก์ชั่นกับ scores ซึ่งเจ้า reduce() นี้ เป็น ฟังก์ขั่นของ Array ใน javascript อยู่แล้ว ดังนั้น สามารถเรียกใช้บน array ได้โดยตรง

reduce() จะรับ parameter สองตัว ตัวแรกคือ callback function ที่มักจะเรียกกันสั้นๆ ว่า reducer ส่วนอีกตัวหนึ่งคือค่า initial value หรือค่าตั้งต้นก่อนการ reduce

ตัว reduce จะใช้ชุดคำสั่งใน reducer สำหรับการ “ลดรูป” จาก array ให้กลายเป็นค่าเพียงค่าเดียว โดยไม่จำกัดว่า มันต้องเป็นแต่เพียงตัวเลขเท่านั้น จะเป็น string ก็ยังได้

ภายใน reducer นั้น จะเป็นการนำค่า total หรือผลรวมมาบวกเข้ากับ item ล่าสุดใน array นั้นๆ

จังหวะแรกที่ reducer เริ่มทำงาน มันจะนำค่า initialValue (argument ตัวที่สองของฟังก์ชั่น .reduce() ) ใส่เป็น argument ให้กับ reducer เพื่อนำมาบวกกับ total แปลว่า ตอนเริ่มต้น ค่า total ภายใน reducer จะมีค่าเท่ากับ 0 จากนั้นก็จะนำสมาชิกตัวแรกใน array บวกเข้าไป

ในจังหวะแรกนั้น reducer จะรับ argument สองตัว กล่าวคือ

total = 0

item = 89 (สมาชิกตัวแรกของ array scores)

ผลลัพธ์ที่ได้ออกมาจะเท่ากับ 0 + 89 = 89

ขั้นตอนต่อมา จะมีการส่งผ่านผลลัพธ์ที่ได้จาก reducer ในรอบแรก ให้กลายมาเป็น argument ชื่อ total ให้กับ reducer ในรอบถัดไป

ดังนั้น ในรอบสองของการรัน reducer จะรับ argument สองตัวคือ

total = 89

item = 76 (สมาชิกตัวที่สองของ array scores)

.reduce() จะทำงานวนแบบนี้ไปจนกว่าจะใช้ item จาก array scores จนหมดตัวสุดท้าย ซึ่งผลลัพธ์ที่ได้ออกมานั้นก็คือ 307

โดยสรุปการทำงานของ .reduce() จาก code ข้างต้นจะเป็นดังนี้

scores = [89, 76, 47, 95]

ขั้นที่ 1 => total: 0, item: 89 => 0 + 89 => 89

ขั้นที่ 2 => total: 89, item: 76 => 89 + 76 => 165

ขั้นที่ 3 => total: 165, item: 47 => 165 + 47 => 212

ขั้นที่ 4 => total: 212, item: 95 => 212 + 95 => 307

ที่ผ่านมาเป็นการใช้งาน reduce แบบพื้นฐาน ต่อไปจะเป็นตัวอย่างที่พิศดารขึ้นมาอีกหน่อย

achat de viagra pharmacie.reduce เป็นฟังก์ชั่นที่สามารถนำไปใช้งานได้มากกว่าแค่การหาค่ารวมของ array ที่มีสมาชิกเป็นตัวเลข อย่างการแปลง array ให้เป็น object ฟังก์ชั่นเทพตัวนี้ก็ช่วยคุณได้

เรามาดูตัวอย่างของจริงกัน

สมมติให้เรามี array ดังต่อไปนี้

โจทย์คือ แปลงค่า array ตัวนี้ ให้กลายเป็น object ที่แสดงให้รู้ว่า อาหารแต่ละชนิดมีจำนวนโหวตเท่าไหร่ โดยแสดงออกมาในรูปของ

ดังนั้น เมื่อเราต้องการผลลัพธ์ออกมาเป็น object ค่า initialValue ก็ต้องเป็น object เช่นกัน (สังเกตมั๊ยครับว่า ค่า initialValue จะมี Type แบบเดียวกับ output ของ .reduce)

จากนั้นเราก็มากำหนดค่าให้กับ reducer ให้ทำงานตามโจทย์ที่วางเอาไว้ ดังนี้

ตัว foods นั้นก็ทำหน้าที่ในการสะสมคะแนนให้กับอาหารแต่ละตัว

เมื่อได้ทั้ง initialValue และ Reducer มาพร้อมแล้ว เราก็มาไล่ดูการทำงานกัน

จำนวนรอบการทำงานของ .reduce ในครั้งนี้ จะมีทั้งหมด 8 รอบ (ตามจำนวนของสมาชิกใน array votes นั่นเอง) โดยจะมีลำดับขั้นการทำงานดังนี้

step 1=> foods: {}, name: ‘tacos’ => foods: {‘tacos’: 1}

step 2=> foods: {‘tacos’: 1}, name: ‘pizza’ => foods: {‘tacos’: 1, ‘pizza’:1}

step 3=> foods: {‘tacos’: 1, ‘pizza’:1}, name: ‘pizza’ => foods: {‘tacos’: 1, ‘pizza’:2}

step 8=> foods: {tacos: 2, pizza: 2, fries: 1, ice cream: 2}, name: ‘pizza’ => foods: {tacos: 2, pizza: 3, fries: 1, ice cream: 2}

ทีนี้ คุณผู้อ่านคงพอจะเข้าใจหลักการทำงานของ .reduce มากขึ้นแล้ว ได้เวลาเข้าสู้โปรเจ็กท์ของเราต่อ

Container VS Component

จากเนื้อหาในตอนที่แล้ว ผมได้ติดค้างในส่วนของ path /results เอาไว้ ซึ่งเป็น path ที่ใช้แสดงผลการแข่งขันของ github user ทั้งสองฝ่าย ดังนั้น ในตอนนี้เราจะมาสร้าง component สำหรับ path /results กัน

ให้ทำการสร้างไฟล์ใน app/components/ ชื่อว่า Results.js (app/components/Results.js) ขึ้นมา โดยมีข้อมูลดังต่อไปนี้

จากนั้นก็ตามระเบียบครับ เมื่อมี component ก็ต้องมี container ของมันเป็นของคู่กัน ให้สร้างไฟล์ใหม่ใน app/containers/ ชื่อว่า ResultsContainer.js โดยมีเนื้อหาดังนี้

เมื่อเราสร้างทั้ง component และ container จนครบแล้ว จากนั้นก็ต้องไปเพิ่ม path ใน routes.js

ให้เราเปิดไฟล์ app/config/routes.js แล้วแก้ไข code ดังนี้

จากนั้นให้ลองเข้าไปที่ localhost:8080/results เพื่อดูว่ามี error อะไรหรือไม่ และหน้าเพจแสดงคำว่า Results อย่างที่คาดหวังไว้หรือไม่

จากนั้นให้เรากลับไปปรับ code ให้ ResultsContainer โดยให้เปิด app/containers/ResultsContainer.js แล้วเพิ่ม code ดังนี้

งานนี้เรามีการเซตค่า state ไว้สองตัวคือ isLoading เพื่อใช้สำหรับตรวจสอบว่า component โหลดเสร็จหรือยัง กับ scores เพื่อเก็บค่าคะแนนของ github user แต่ละคน

ประเด็นคือ component ตัวนี้ต้องการใช้ช้อมูลของ PlayersInfo ที่ถูกส่งมาจาก ConfirmBattleContainer (ซึ่งเราได้สร้างเอาไว้ในบทความตอนที่แล้ว) ตาม code ข้างล่างนี้

app/containers/ConfirmBattleContainer.js

จะเห็นว่ามีการส่งข้อมูล playersInfo ผ่านมาทาง  router ดังนั้นผมจึงต้องเอา console.log มาดักใน componentDidMount เพื่อตรวจดูว่าเจ้า playersInfo มันอยู่ที่ไหน เมื่อเราเปิด localhost:8080 แล้วกรอกข้อมูลและคลิก submit มาจนถึง path /results เมื่อเปิด console ดูเราจะเห็นข้อมูลดังนี้

screen-shot-2559-11-06-at-9-09-04-pm

โดยสรุปก็คือ เราสามารถ access ค่า playersInfo ผ่าน this.props.location.state.playersInfo

เมื่อได้ข้อสรุปแล้ว ก็ให้เปิด app/containers/ResultsContainer.js แล้วแก้ code ดังนี้

เมื่อได้ playersInfo มาอยู่ในมือ เราก็นำมาใช้งานซะ โดยเริ่มจากส่งผ่านต่อไปยัง Results component จากนั้น ก็นำมาใช้เป็น argument ของฟังก์ชั่น battle() ภายใน githubHelpers.js (ซึ่งเป็นฟังก์ชั่นที่เรายังไม่ได้สร้าง) promises function ที่จะคำนวนคะแนนของ github user ทั้งสองฝ่าย แล้วคืนค่า scores กลับมา

จาก code จะเห็นว่า เมื่อ githubHelper.battle ทำงานเสร็จแล้ว มันจะคืนค่า scores กลับมา แล้ว .then() ก็จะรับช่วงไปทำงานต่อ โดยมันจะทำการ set state ทั้งค่า scores และ isLoading (อ้อ อย่าลืมใส่ .bind(this) เข้าไปด้วยล่ะ มิฉะนั้น callback function ภายใน .then() จะไม่รู้จักกับ this ของ React)

ก่อนที่เราจะไปสร้าง function battle ใน githubHelper (ซึ่งเป็นงานยาก) ให้เราไปจัดการกับ Results component ก่อนดีกว่า

ก็ไม่มีอะไรมาก แค่เติม propTypes เข้าไป และพิมพ์ข้อมูลของ props ทั้งหมดลงหน้าจอผ่านฟังก์ชั่น JSON.stringify เพื่อใช้ตรวจสอบว่ามี props อะไรส่งมาที่ component ตัวนี้บ้าง

เริ่มต้นสร้างฟังก์ชั่น Battle()

ให้เปิดไฟล์ app/utils/githubHelpers.js ขึ้นมา แล้วเพิ่ม code เข้าไปดังนี้

เรามาดูกันแบบ  slow motion กันครับ

เริ่มจาก

ฟังก์ชั่น battle จะรับ argument เป็น array ของ players แล้วนำสมาชิกแต่ละตัวไปยิงใส่เป็น argument ของฟังก์ชั่น getPlayersData โดยเนื้อหาภายใน getPlayersData มีดังนี้

ฟังก์ชั่นนี้จะทำการเรียกใช้ฟังก์ชั่น getRepos และ getTotalStars ซึ่งทั้งสองฟังก์ชั่นทำงานดังนี้

ตัว getRepos จะ call github API เพื่อดึง repos ทั้งหมดของ user คนนั้นมา ส่วน getTotalStars จะเป็นค้นหาจำนวนผลรวม star ของทุกๆ repos ของ user คนดังกล่าว โดยใช้ฟังก์ชั่น reduce ในการทำการคำนวน (ลองนั่งอ่าน code reduce ดูนะครับ แล้วลองทำความเข้าใจด้วยตัวเองดู หากสงสัยตรงไหน ให้ย้อนกลับไปอ่านส่วนแรกของบทความตอนนี้ได้)

กลับมาที่ฟังก์ชั่น getPlayersData เมื่อคำนวนผลรวมของจำนวน star จากทุกๆ repos ของ player ได้แล้ว ก็จะทำการ return object ที่มี properties สองตัว กล่าวคือ followers และ totalStars

กลับมาที่ฟังก์ชั่นพระเอกของเรา Battle

เมื่อได้ผลลัพธ์จาก getPlayersData จากทั้งสอง player แล้ว (ผ่านการใช้งาน axiox.all) สิ่งที่มันจะทำต่อไปก็คือ ทำการคำนวน คะแนนรวมของ player แต่ละคนโดยใช้สูตร

followers * 3 + total stars

จากนั้นก็ return ผลคะแนนของทั้งสองคนออกไป ถือเป็นอันจบกระบวนการของฟังก์ชั่น battle

หากเราลองเข้าไปลองเล่นระบบแบบเต็ม loop เมื่อไปถึง path /results แล้วเราจะเห็นภาพดังต่อไปนี้

screen-shot-2559-11-06-at-11-16-46-pm

หากเห็นภาพดังกล่าว โดยไม่เกิด error บน console ก็ถือเป็นอันจบงาน

หากใครต้องการใช้ code ต้นแบบเพื่ออ้างอิง สามารถเข้าไปดูได้ที่

https://github.com/himaeng/react-fun-course/tree/master/09-reduce

สรุป

เนื้อหาในตอนนี้เน้นเรื่องของการใช้งานฟังก์ชั่น reduce ของ array ใน Javascript ซึ่งเป็นฟังก์ชั่นที่ช่วยให้เราสามารถเขียนโปรแกรมในแบบ Declarative ได้อย่างมีประสิทธิภาพสูงขึ้น