เรื่องราวการสอนด้วยตนเองของ JL #25
ซีรีส์ [เรื่องราวการสอนตนเองของ JL]
[Net Ninja] JS Regular Expressions [Net Ninja] Vue JS2 (Part1, Part2, Part3) [Net Ninja] Vuex [Net Ninja] Python3 (Part1, Part2, Part3) [Net Ninja] Django (Part1, Part2, Part3) [Net Ninja] Sass (Part1, Part2) [Sean Larkin] Webpack4 (Part1, Part2, Part3, Part4) [Sarah Drasner] VueJS (Part1, Part2, Part3, Part4, Part5, Part6, Part7) [Evan You] Advanced VueJS (Part1(current), Part2, Part3, Part4, Part5, Part6, Part7)
😃 นี่เป็นส่วน ส่วนแรก ของบทสรุปของฉันเกี่ยวกับ “ฟีเจอร์ Advanced Vue.js จาก Ground Up” ของ Evan You ใน FrontendMasters
[1. Reactivity] 1-1. Introducing Reactivity 1-2. Challenge 1: Getters & Setters 1-3. Challenge 2: Dependency Tracker 1-4. Challenge 3: Mini Observer
[ 1–1. แนะนำปฏิกิริยา ]
Vue ติดตามการเปลี่ยนแปลงอย่างไร = การเปลี่ยนแปลงสถานะสะท้อนถึงการเปลี่ยนแปลงใน DOM อย่างไร
เริ่มต้นด้วยการเขียนโปรแกรมง่ายๆ เรามีตัวแปร a ซึ่งมีค่าเป็น 3 เจ้านายของคุณให้ข้อกำหนดแก่คุณ: ค่าของตัวแปร b
ควรเป็น 10 เท่าของค่าของ a
เสมอ
let a = 3; let b = a * 10; console.log(b) // 30
› ทุกอย่างดูดีไปหมด
a = 4; console.log(b) // 30
› หลังจากนั้น ระบบจะขอให้คุณเปลี่ยน a
เป็น 4 ตอนนี้สิ่งต่างๆ ไม่ตรงกัน เนื่องจากคุณลืมอัปเดต b
! ('b' ไม่ได้รับการอัปเดตโดยอัตโนมัติ เนื่องจากเป็นขั้นตอน จึงไม่รักษาความสัมพันธ์ให้ตรงกัน)
b = a * 10; console.log(b) // 40
> ที่ตายตัว. แต่ตอนนี้เรามีการทำซ้ำและแหล่งที่มาของข้อบกพร่องเมื่อใดก็ตามที่เราต้องการเปลี่ยนแปลง a
แทนที่จะต้องอัปเดต b
ด้วยตนเองทุกครั้งที่คุณเปลี่ยน a
เราต้องการให้ความสัมพันธ์นั้นได้รับการเปิดเผย ดังนั้นเราจึงตระหนักดีว่าสิ่งที่ผู้จัดการของคุณต้องการให้คุณสร้างจริงๆ คือสเปรดชีต
› ค่าของเซลล์ B1 จะแสดงออกมาอย่างชัดเจนด้วยสูตร ดังนั้นจึงอัปเดตโดยอัตโนมัติทุกครั้งที่คุณแก้ไขเซลล์ A1
เราจะแสดงสิ่งนั้นในแง่การเขียนโปรแกรมอย่างไร
onAChanged(() => { b = a * 10 })
› เพื่อให้เป็นไปตามข้อกำหนดของโปรแกรมของเราด้วยวิธีที่ง่ายที่สุด สมมติว่าเราใช้ฟังก์ชันมหัศจรรย์นี้: onAChanged
คุณสามารถคิดได้ว่าเป็นผู้เฝ้าดู การติดต่อกลับเหตุการณ์ การสมัครรับข้อมูล ไม่สำคัญ สิ่งสำคัญในตอนนี้คือ b
จะปฏิบัติตามข้อกำหนดที่เป็น 10 เท่าของ a
เสมอ ตราบใดที่เราใช้ฟังก์ชันนี้อย่างถูกต้อง
ก่อนที่เราจะเจาะลึกถึงวิธีการใช้ฟังก์ชันนั้น เรามาแปลปัญหาเป็นสิ่งที่ใกล้เคียงกับการพัฒนาเว็บกันดีกว่า
<span class="cell b1"></span> document .querySelector(‘.cell.b1’) .textContent = state.a * 10
› เราต้องการให้แน่ใจว่าเซลล์ 'b1' ได้รับการซิงค์อยู่เสมอ เช่นเดียวกับโค้ดความจำเป็นเริ่มต้นของเรา เราต้องเข้าถึง DOM และอัปเดตเนื้อหาข้อความของเซลล์โดยไม่จำเป็น
<span class="cell b1"></span> onStateChanged(() => { document .querySelector(‘.cell.b1’) .textContent = state.a * 10 })
› เพื่อให้มันชัดเจน เพียงแค่ห่อมันไว้ในฟังก์ชั่นเวทย์มนตร์ของเรา!
<span class="cell b1"> {{ state.a * 10 }} </span> onStateChanged(() => { view = render(state) })
ด้วยการแยกออกไปไกลออกไป เราจึงได้สร้างไลบรารี Vue ขึ้นมา
- ยิ่งไปกว่านั้น เรามาเปลี่ยนมาร์กอัปให้เป็นเทมเพลตกันดีกว่า โดยปกติเทมเพลตจะคอมไพล์เป็นฟังก์ชันการเรนเดอร์ ดังนั้นโค้ด JavaScript ของเราจึงสามารถทำให้ง่ายขึ้นได้
- เทมเพลต (หรือฟังก์ชันการเรนเดอร์ JSX) คือโครงสร้างที่ช่วยให้เราสามารถประกาศความสัมพันธ์ระหว่างสถานะและมุมมอง กล่าวอีกนัยหนึ่ง เทมเพลตจะเทียบเท่ากับสูตรในสเปรดชีต
onStateChanged(() => { view = render(state) })
การแสดงภายใน “
view = render(state)
” นี้เป็นการแสดงนามธรรมระดับที่สูงมากว่าระบบการเรนเดอร์ Vue ทั้งหมดทำงานอย่างไร
การมอบหมายให้ view
นี้สามารถตีความได้แตกต่างกันไปขึ้นอยู่กับว่าคุณมองอย่างไร ในบริบท DOM เสมือน เราสามารถคิดได้ว่า view
เป็นแผนผัง DOM เสมือนใหม่ แต่โดยทั่วไปแล้ว ให้ข้าม DOM เสมือนและคิดว่ามันเป็นการใช้ผลข้างเคียงของการกลายพันธุ์ DOM
พูดตามตรง นี่เป็นเพียงครึ่งเดียวของภาพ: ยังมีคำถามว่าจะแมปอินพุตของผู้ใช้กับการเปลี่ยนแปลงสถานะอย่างเปิดเผยได้อย่างไร ในแง่ Cycle นั่นคือครึ่งหนึ่งที่เชื่อมโยงความตั้งใจของผู้ใช้กับสถานะ นั่นคือโดเมนที่ Rx เก่งมาก แต่เมื่อพิจารณาจากความยาวของการพูดคุยนี้ Evan You จึงมุ่งเน้นไปที่ส่วน state -> view
ตอนนี้คำถามคือ “แอปพลิเคชันรู้ได้อย่างไรว่าเมื่อใดควรเรียกใช้ฟังก์ชันอัปเดตนี้อีกครั้ง”
onStateChanged(() => { view = render(state) })
มาหาคำตอบกัน! นี่เป็นเวอร์ชันที่สร้างสรรค์อย่างยิ่งเกี่ยวกับวิธีการทำงาน:
let update, state const onStateChanged = _update => { update = _update } const setState = newState => { state = newState update() }
› แทนที่จะอนุญาตให้ผู้ใช้จัดการสถานะโดยพลการ เราต้องการให้ผู้ใช้เรียกใช้ฟังก์ชันเพื่อจัดการสถานะเสมอ ฟังก์ชันนี้เรียกว่า
“setState
”
...และว้าว เราได้ใช้งาน React แล้ว! (แม้ว่าจะเป็นเวอร์ชันที่เรียบง่ายมากเพียงเพื่อให้เข้าใจประเด็น)
onStateChanged(() => { view = render(state) }) setState({ a: 5 })
› ตอบสนอง บังคับให้คุณทริกเกอร์การเปลี่ยนแปลงสถานะของ setState ของคุณ
onStateChanged(() => { view = render(state) }) state.a = 5
› เราสามารถจัดการสถานะได้โดยตรง โดยไม่ต้อง โดยไม่ต้องเรียก setState ใน AngularJS จะใช้การตรวจสอบสกปรกที่สกัดกั้นเหตุการณ์ เช่น การคลิก เพื่อดำเนินการรอบสรุปและตรวจสอบทุกสิ่งเพื่อดูว่ามีอะไรเปลี่ยนแปลงหรือไม่ ใน VueJS จะแปลงออบเจ็กต์สถานะให้เป็น reactive ด้วยการใช้ ES5 object.defineProperty API Vue จะแปลงคุณสมบัติเหล่านี้เป็น getters และ setters ในกรณีของ state.a
Vue จะแปลง a
เป็น getter และ setter
autorun(() => { console.log(state.count) })
› หากเราเปลี่ยนชื่อฟังก์ชัน onStateChanged เป็นฟังก์ชัน การทำงานอัตโนมัติ นี่เป็นรูปแบบพื้นฐานของระบบการติดตามการพึ่งพาที่แชร์กันทั่วไปใน Knockout.js, Meteor Tracker , Vue.js และ MobX (รูปแบบการจัดการสถานะสำหรับ React)
[ 1–2. ความท้าทายที่ 1: Getters & Setters ]
คำแนะนำ:
ใช้ฟังก์ชัน convert
ที่:
- ใช้
Object
เป็นอาร์กิวเมนต์ - แปลงคุณสมบัติของวัตถุในตำแหน่งเป็น
getter
/setters
โดยใช้Object.defineProperty
- วัตถุที่แปลงแล้วควรคงลักษณะการทำงานดั้งเดิมไว้ แต่ในขณะเดียวกันก็ บันทึก การดำเนินการ
get
/set
ทั้งหมด
ที่คาดหวัง:
let realValue Object.defineProperty(obj, 'foo', { get(){ return 'bar' }, set(newValue){ } }) const obj = { foo: 123 } convert(obj) obj.foo // should log: 'getting key "foo": 123' obj.foo = 234 // should log: 'setting key "foo" to 234' obj.foo // should log: 'getting key "foo": 234'
แม่แบบ:
<script> function convert(obj){ // Implement this! </script>
สารละลาย:
[ 1–3. ความท้าทายที่ 2: ตัวติดตามการพึ่งพา ]
คำแนะนำ:
งานคือการเชื่อมโยงอินสแตนซ์ของการขึ้นต่อกันกับการคำนวณ
- สร้างคลาส
Dep
ด้วยสองวิธี:depend
และnotify
›› เชื่อมโยง การคำนวณ กับ การพึ่งพา การคำนวณ ควรได้รับการพิจารณาว่าเป็นสมาชิกของ การพึ่งพา
- สร้างฟังก์ชัน
autorun
ที่ใช้ฟังก์ชันอัปเดต (การคำนวณ)
›› เมื่อคุณเข้าสู่ฟังก์ชั่น ทุกอย่างจะพิเศษขึ้น เราอยู่ในโซน ปฏิกิริยา
- ภายในฟังก์ชันตัวอัปเดต คุณสามารถพึ่งพาอินสแตนซ์ของ
Dep
ได้อย่างชัดเจนโดยการเรียกdep.depend()
›› เมื่อคุณอยู่ในโซน ปฏิกิริยา คุณสามารถลงทะเบียน การขึ้นต่อกัน ได้
- หลังจากนั้น คุณสามารถทริกเกอร์ฟังก์ชันตัวอัปเดตให้ทำงานอีกครั้งได้โดยโทร
dep.notify()
ที่คาดหวัง:
const dep = new Dep() autorun(() => { dep.depend() console.log('updated') }) // should log: "updated" dep.notify() // should log: "updated"
แม่แบบ:
<script> // a class representing a dependency // exposing it on window is necessary for testing window.Dep = class Dep { // Implement this! } function autorun (update) { // Implement this! } </script>
เทมเพลตพร้อมคำแนะนำ:
- JavaScript เป็นแบบเธรดเดียว ในเวลาใดก็ตาม สามารถดำเนินการได้เพียงฟังก์ชันเดียวเท่านั้น ดังนั้นการสร้างฟังก์ชันจะทำเครื่องหมายตัวเองว่าเป็นฟังก์ชันที่กำลังดำเนินการอยู่ จากนั้นเราจะทราบได้ตลอดเวลาว่าฟังก์ชันนี้กำลังทำงานอยู่หรือไม่ (=ว่าเราอยู่ในฟังก์ชันนี้หรือไม่)
- เมื่อใดก็ตามที่เราเรียก
wrappedUpdate()
โค้ดภายในฟังก์ชันจะทำงาน รับประกันว่ารหัสจะสามารถเข้าถึงรหัสภายนอกฟังก์ชันผ่านตัวแปร globalactiveUpdate
ดังนั้นคลาสการพึ่งพาของเราสามารถเข้าถึงactiveUpdate
ได้ - ออบเจ็กต์
Set
ช่วยให้คุณจัดเก็บค่าที่ไม่ซ้ำกันทุกประเภท ไม่ว่าจะเป็น "ค่าดั้งเดิม" หรือการอ้างอิงออบเจ็กต์
สารละลาย:
- เราลงทะเบียน
wrappedUpdate()
เป็นการอัปเดตที่ใช้งานอยู่ ดังนั้น เมื่อการขึ้นต่อกันของเราเปลี่ยนแปลงและฟังก์ชันอัปเดตถูกเรียกอีกครั้ง เราจะเรียกwrappedUpdate()
อีกครั้ง ดังนั้นเคล็ดลับการติดตามการพึ่งพาของเราจะยังคงใช้ได้กับการทำซ้ำในอนาคต ดังนั้นจึงรวบรวมการพึ่งพาอย่างต่อเนื่อง
›› นี่เป็นสิ่งสำคัญ เนื่องจากฟังก์ชันการอัปเดตของเราอาจมีเงื่อนไขในบางกรณี ดังนั้นเมื่อตัวแปรตัวหนึ่งเป็นจริง ฟังก์ชันอาจรวบรวมการขึ้นต่อกันบางอย่าง และเมื่อตัวแปรอื่นถูกพลิก ตัวแปรนั้นอาจรวบรวมการขึ้นต่อกันอื่นๆ ดังนั้น ระบบรวบรวมการขึ้นต่อกันของเราต้อง แบบไดนามิก ปรับสมดุล การขึ้นต่อกัน เพื่อให้แน่ใจว่ารายการ การขึ้นต่อกัน เป็นข้อมูลล่าสุดอยู่เสมอ
››› แม้ว่าเครื่องมือติดตามการพึ่งพาของเราจะใช้งานได้กับกรณีทดสอบของเรา แต่ก็พลาดกรณี Edge บางส่วนไป (เช่น คุณจะจัดการกับอาร์เรย์อย่างไร แล้วคุณสมบัติที่เพิ่มเข้ามาใหม่ล่ะ)
[ 1–4. ความท้าทายที่ 3: ผู้สังเกตการณ์จิ๋ว ]
คำแนะนำ:
ตอนนี้เป็นเวลาที่จะรวบรวมความท้าทายทั้งสองก่อนหน้านี้เข้าด้วยกัน รวมสองฟังก์ชันก่อนหน้านี้ เปลี่ยนชื่อ convert()
(จาก Challenge 1) เป็น observe()
และคง autorun()
(จาก Challenge 2):
observe()
แปลงคุณสมบัติใน วัตถุ ที่ได้รับ และทำให้มัน มีปฏิกิริยา สำหรับคุณสมบัติ ที่แปลงแล้ว แต่ละรายการ ได้รับการกำหนดอินสแตนซ์Dep
ซึ่งติดตามรายการฟังก์ชัน อัปเดต ที่สมัครรับข้อมูล และทริกเกอร์ให้ทำงานอีกครั้งเมื่อ ตัวตั้งค่า ถูกเรียกใช้autorun()
รับฟังก์ชัน อัปเดต และเรียกใช้อีกครั้งเมื่อคุณสมบัติที่ฟังก์ชัน อัปเดต สมัครรับข้อมูลมีการเปลี่ยนแปลง ฟังก์ชัน อัปเดต เรียกว่า "สมัครสมาชิก" กับพร็อพเพอร์ตี้หากอาศัยพร็อพเพอร์ตี้นั้นในระหว่างการประเมิน
คำแนะนำ (ในคำอื่น ๆ ):
- ใช้
Dep
คลาส &autorun()
จาก Challenge 2 - คัดลอกและแก้ไข
convert()
จาก Challenge 1 เพื่อเชื่อมต่อdep.depend()
เพื่อให้dep.notify()
โทรไปที่getters
และsetters
- เมื่อเชื่อมต่อ
autorun()
และconvert()
(เปลี่ยนชื่อเป็นobserve()
) เราสามารถเข้าถึงคุณสมบัติที่รวบรวมการอ้างอิงโดยการเรียกdep.depend()
การเปลี่ยนแปลงคุณสมบัติทำให้dep.notify()
ทริกเกอร์การเปลี่ยนแปลง
ที่คาดหวัง:
const state = { count: 0 } observe(state) autorun(() => { console.log(state.count) // should call dep.depend() "getter" }) // should immediately log "count is: 0" state.count++ // should log "count is: 1" // should call dep.notify() "setter"
แม่แบบ:
<script> function observe (obj) { // Implement this! } function autorun (update) { // Implement this! } </script>
สารละลาย:
ผู้สอนประจำชั้นเรียน “Evan You” เป็นผู้สร้างและหัวหน้าโครงการของ Vue.js
สามารถพบได้ที่ เว็บไซต์ส่วนตัวของเขา, GitHub, LinkedIn, Twitter, Medium(Evan You) และ Frontend Masters
ขอบคุณที่อ่าน! 💕 ถ้าคุณชอบโพสต์บนบล็อกนี้ กรุณาปรบมือ👏