Vuex Action vs Mutations


173

ใน Vuex ตรรกะของการมีทั้ง "การกระทำ" และ "การกลายพันธุ์คืออะไร"

ฉันเข้าใจตรรกะของส่วนประกอบที่ไม่สามารถแก้ไขสถานะ (ซึ่งดูเหมือนว่าฉลาด) แต่การมีทั้งการกระทำและการกลายพันธุ์ดูเหมือนว่าคุณกำลังเขียนฟังก์ชันหนึ่งเพื่อเรียกใช้ฟังก์ชันอื่นเพื่อเปลี่ยนสถานะ

อะไรคือความแตกต่างระหว่าง "การกระทำ" และ "การกลายพันธุ์" พวกเขาทำงานร่วมกันอย่างไรและ moreso ฉันสงสัยว่าทำไมนักพัฒนา Vuex จึงตัดสินใจทำเช่นนี้


2
ดู "ไปที่การทำงาน" ฉันคิดว่า: vuex.vuejs.org/en/mutations.html#on-to-actions
Roy J

การสนทนาที่เกี่ยวข้อง: github.com/vuejs/vuex/issues/587
chuck911

1
คุณไม่สามารถเปลี่ยนสถานะร้านค้าโดยตรง วิธีเดียวในการเปลี่ยนสถานะของร้านค้าคือโดยการกระทำการกลายพันธุ์อย่างชัดเจน เพื่อที่เราต้องการการกระทำที่จะกระทำการกลายพันธุ์
Suresh Sapkota

1
@SureshSapkota ข้อความนั้นมีความสับสนอย่างมากเนื่องจากทั้งคู่mutationsและactionsมีการกำหนดไว้ในเอกสาร vuex เป็นวิธีการเปลี่ยนสถานะ คุณไม่จำเป็นต้องดำเนินการใด ๆ เพื่อทำการกลายพันธุ์
เกรแฮม

1
การกลายพันธุ์เป็นชื่อที่ใช้ในการปรับเปลี่ยน / กลายพันธุ์วัตถุสถานะของคุณ การกระทำนั้นค่อนข้างคล้ายกับการกลายพันธุ์ แต่แทนที่จะเป็นการผ่าเหล่าของรัฐการกระทำก่อกลายพันธุ์ การดำเนินการสามารถมีรหัสแบบอะซิงโครนัสหรือตรรกะทางธุรกิจใดก็ได้ Vuex แนะนำวัตถุสถานะควรจะกลายพันธุ์ภายในฟังก์ชั่นการกลายพันธุ์ มันเป็นยังแนะนำไม่ให้ทำงานหนักหรือรหัสการปิดกั้นภายในฟังก์ชั่นการกลายพันธุ์ตั้งแต่มันซิงโครในธรรมชาติ
Emmanuel Neni

คำตอบ:


221

คำถามที่ 1 : ทำไมนักพัฒนา Vuejs จึงตัดสินใจทำเช่นนี้

ตอบ:

  1. เมื่อแอปพลิเคชันของคุณมีขนาดใหญ่และเมื่อมีผู้พัฒนาหลายรายกำลังทำงานในโครงการนี้คุณจะพบ "การจัดการของรัฐ" (โดยเฉพาะ "สถานะทั่วโลก") จะมีความซับซ้อนมากขึ้นเรื่อย ๆ
  2. วิธี vuex (เหมือนกับRedux in react.js ) นำเสนอกลไกใหม่ในการจัดการสถานะรักษาสถานะและ "บันทึกและติดตามได้" (นั่นหมายถึงทุกการกระทำที่แก้ไขสถานะสามารถติดตามได้ด้วยเครื่องมือดีบัก: vue-devtools )

คำถามที่ 2 : "การกระทำ" และ "การกลายพันธุ์" ต่างกันอย่างไร

ลองดูคำอธิบายอย่างเป็นทางการก่อน:

การกลายพันธุ์:

การกลายพันธุ์ของ Vuex นั้นเป็นเหตุการณ์สำคัญ: การกลายพันธุ์แต่ละครั้งมีชื่อและตัวจัดการ

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    INCREMENT (state) {
      // mutate state
      state.count++
    }
  }
})

การกระทำ: การกระทำเป็นเพียงฟังก์ชั่นที่จัดส่งการกลายพันธุ์

// the simplest action
function increment (store) {
  store.dispatch('INCREMENT')
}

// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
  dispatch('INCREMENT', amount)
}

นี่คือคำอธิบายของฉันของข้างต้น:

  • การกลายพันธุ์เป็นวิธีเดียวที่จะแก้ไขสถานะ
  • การกลายพันธุ์ไม่สนใจตรรกะทางธุรกิจมันแค่ใส่ใจกับ "รัฐ"
  • การกระทำคือตรรกะทางธุรกิจ
  • การกระทำสามารถส่งมากกว่าหนึ่งการกลายพันธุ์ในแต่ละครั้งมันใช้ตรรกะทางธุรกิจมันไม่สนใจเกี่ยวกับการเปลี่ยนแปลงข้อมูล (ซึ่งจัดการโดยการกลายพันธุ์)

80
ข้อเท็จจริงที่ว่าการกระทำ "เป็นตรรกะทางธุรกิจ" และสามารถส่งการกลายพันธุ์หลายครั้งพร้อมกันจะมีประโยชน์ นั่นคือคำตอบที่ฉันต้องการ ขอบคุณ.
Kobi

11
คุณกำลังพูดว่าคุณ "ส่งการกลายพันธุ์" การใช้ถ้อยคำที่ถูกต้องไม่ใช่สิ่งที่คุณให้สัญญาไว้หรือไม่?
ProblemsOfSumit

4
คุณจัดส่งการกระทำและการกลายพันธุ์
eirik

4
การจัดส่งไม่ทำงานใน vue 2.0 สำหรับการกลายพันธุ์อีกต่อไปคุณต้องยอมรับการกลายพันธุ์ในการดำเนินการ
SKLTFZ

18
@Kaicui คำตอบนี้ไม่มีโน้ตเกี่ยวกับการกลายพันธุ์ที่มักจะเป็นแบบซิงโครนัสและการกระทำที่อาจเป็นแบบอะซิงโครนัส นอกจากนั้นเป็นคำตอบที่ดี!
ตัดสินใจ

58

การกลายพันธุ์เป็นแบบซิงโครนัสในขณะที่การกระทำสามารถแบบไม่พร้อม

หากต้องการวางไว้ในอีกทางหนึ่ง: คุณไม่จำเป็นต้องดำเนินการใด ๆ หากการดำเนินการของคุณซิงโครนัส


2
นี่จริงตอบคำถามที่ฉันจะทำเกี่ยวกับวิธีการตัวอย่าง todomvc ทำให้ไม่ใช้การกระทำ
sksallaj

7
'คุณไม่จำเป็นต้องดำเนินการถ้าการดำเนินงานของคุณซิงโคร' : นั่นไม่เป็นความจริง: คุณทำกระทำความจำเป็นถ้าคุณต้องการที่จะแต่งการกลายพันธุ์จากหลายโมดูลเดียวกันเพราะคุณไม่สามารถเรียกการกระทำอื่นจากการกระทำ
Raymundus

1
การติดตามผลที่ชัดเจนของคำตอบนี้คือ "ถ้าอย่างนั้นทำไมไม่เพียงแค่มีการกระทำและกำจัดการกลายพันธุ์"
Michael Mrozek

34

ฉันเชื่อว่าการมีความเข้าใจเกี่ยวกับแรงจูงใจเบื้องหลังการกลายพันธุ์และการกระทำอนุญาตให้เราตัดสินได้ดีขึ้นว่าจะใช้เมื่อไหร่และอย่างไร นอกจากนี้ยังช่วยให้โปรแกรมเมอร์ปราศจากภาระความไม่แน่นอนในสถานการณ์ที่ "กฎ" กลายเป็นคลุมเครือ หลังจากให้เหตุผลเล็กน้อยเกี่ยวกับจุดประสงค์ของพวกเขาฉันก็สรุปได้ว่าถึงแม้จะมีวิธีที่ผิดในการใช้ Actions and Mutations แต่ฉันไม่คิดว่ามันมีวิธีการที่เป็นที่ยอมรับ

ก่อนอื่นเรามาลองทำความเข้าใจว่าทำไมเราถึงผ่านการกลายพันธุ์หรือการกระทำ

ทำไมต้องผ่านแผ่นความร้อนในตอนแรก ทำไมไม่เปลี่ยนสถานะโดยตรงในส่วนประกอบ?

การพูดอย่างเคร่งครัดคุณสามารถเปลี่ยนstateได้โดยตรงจากส่วนประกอบของคุณ นี่stateเป็นเพียงวัตถุ JavaScript และไม่มีอะไรน่าอัศจรรย์ที่จะคืนค่าการเปลี่ยนแปลงที่คุณทำไว้

// Yes, you can!
this.$store.state['products'].push(product)

อย่างไรก็ตามด้วยการทำเช่นนี้คุณจะทำให้การกลายพันธุ์ของรัฐกระจายไปทั่วสถานที่ คุณสูญเสียความสามารถเพียงแค่เปิดโมดูลเดียวที่อยู่อาศัยสถานะและอย่างรวดเร็วดูชนิดของการดำเนินการที่สามารถนำไปใช้กับมัน การกลายพันธุ์แบบรวมศูนย์จะช่วยแก้ปัญหานี้ได้แม้ว่าจะมีค่าใช้จ่ายสำหรับแผ่นบางสำเร็จรูป

// so we go from this
this.$store.state['products'].push(product)

// to this
this.$store.commit('addProduct', {product})

...
// and in store
addProduct(state, {product}){
    state.products.push(product)
}
...

ฉันคิดว่าถ้าคุณเปลี่ยนบางสิ่งบางอย่างให้สั้นลงด้วยสำเร็จรูปคุณจะต้องการให้ชิ้นส่วนเล็ก ๆ ดังนั้นฉันจึงสันนิษฐานว่าการกลายพันธุ์นั้นหมายถึงการห่อหุ้มบาง ๆ รอบ ๆ การดำเนินงานดั้งเดิมในรัฐโดยแทบไม่มีตรรกะทางธุรกิจ กล่าวอีกนัยหนึ่งการกลายพันธุ์นั้นมีความหมายว่าส่วนใหญ่จะใช้เหมือนเซทเทอร์

ตอนนี้คุณได้รวมการกลายพันธุ์ของคุณไว้ที่ศูนย์กลางแล้วคุณมีภาพรวมที่ดีขึ้นเกี่ยวกับการเปลี่ยนแปลงสถานะของคุณและเนื่องจากการใช้เครื่องมือ (vue-devtools) ของคุณก็รับรู้ถึงตำแหน่งนั้น ๆ นอกจากนี้ยังควรคำนึงว่าปลั๊กอินของ Vuex หลายตัวไม่ได้ดูสถานะโดยตรงเพื่อติดตามการเปลี่ยนแปลงพวกเขาค่อนข้างพึ่งพาการกลายพันธุ์สำหรับสิ่งนั้น การเปลี่ยนแปลง "ไม่อยู่ในขอบเขต" ของรัฐจึงไม่สามารถมองเห็นได้

ดังนั้นmutations, actionsสิ่งที่เป็นอยู่แล้วแตกต่างกันอย่างไร

การดำเนินการเช่นการกลายพันธุ์ยังอยู่ในโมดูลของร้านค้าและสามารถรับstateวัตถุได้ ซึ่งหมายความว่าพวกเขายังสามารถกลายพันธุ์ได้โดยตรง แล้วประเด็นของการมีทั้งคู่คืออะไร? หากเราให้เหตุผลว่าการกลายพันธุ์จะต้องมีขนาดเล็กและเรียบง่ายก็หมายความว่าเราจำเป็นต้องมีวิธีการอื่นในการสร้างตรรกะทางธุรกิจที่ซับซ้อนยิ่งขึ้น การกระทำเป็นวิธีการที่จะทำเช่นนี้ และเนื่องจากตามที่เราได้กำหนดไว้ก่อนหน้านี้ vue-devtools และปลั๊กอินต่างตระหนักถึงการเปลี่ยนแปลงผ่านการกลายพันธุ์เพื่อให้สอดคล้องกันเราควรใช้การกลายพันธุ์จากการกระทำของเรา นอกจากนี้เนื่องจากการกระทำมีขึ้นเพื่อให้ครอบคลุมและตรรกะที่พวกเขาแค็ปซูลอาจไม่ตรงกันก็ทำให้รู้สึกว่าการกระทำก็จะทำแบบอะซิงโครนัสตั้งแต่เริ่มต้น

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

... ซึ่งนำไปสู่คำถามที่น่าสนใจ

เหตุใดการกลายพันธุ์จึงไม่ได้รับ Getters

ฉันยังไม่พบคำตอบที่น่าพอใจสำหรับคำถามนี้ ฉันได้เห็นคำอธิบายบางอย่างโดยทีมหลักที่ฉันพบว่าที่สงสัยที่สุด หากฉันสรุปการใช้งานของพวกเขา Getters นั้นตั้งใจที่จะคำนวณส่วนขยาย (และแคชบ่อย) ให้เป็นสถานะ กล่าวอีกนัยหนึ่งพวกเขายังคงเป็นรัฐแม้ว่าจะต้องมีการคำนวณล่วงหน้าและโดยปกติพวกเขาจะอ่านอย่างเดียว อย่างน้อยก็เป็นวิธีที่กระตุ้นให้ใช้งาน

ดังนั้นการป้องกันมิวเตชั่นจากการเข้าถึง Getters โดยตรงหมายความว่าหนึ่งในสามสิ่งจำเป็นในขณะนี้หากเราจำเป็นต้องเข้าถึงการทำงานบางอย่างที่เสนอโดยหลัง: (1) การคำนวณสถานะที่ Getter ทำซ้ำนั้นอยู่ที่ไหนสักแห่งที่สามารถเข้าถึงได้ เพื่อการกลายพันธุ์ (กลิ่นเหม็น) หรือ (2) การคำนวณตามตัวอักษร (หรือผู้ทะเยอทะยานที่เกี่ยวข้อง) นั้นถูกส่งผ่านเป็นอาร์กิวเมนต์ที่ชัดเจนเพื่อการกลายพันธุ์ (ขี้ขลาด) หรือ (3) ตรรกะของ Getter นั้นซ้ำกันโดยตรงภายในการกลายพันธุ์ ไม่มีประโยชน์เพิ่มเติมของการแคชตามที่ Getter จัดให้ (stench)

ต่อไปนี้เป็นตัวอย่างของ (2) ซึ่งในสถานการณ์ส่วนใหญ่ที่ฉันพบดูเหมือนว่าตัวเลือก "น้อยที่สุด"

state:{
    shoppingCart: {
        products: []
    }
},

getters:{
    hasProduct(state){
        return function(product) { ... }
    }
}

actions: {
    addProduct({state, getters, commit, dispatch}, {product}){

        // all kinds of business logic goes here

        // then pull out some computed state
        const hasProduct = getters.hasProduct(product)
        // and pass it to the mutation
        commit('addProduct', {product, hasProduct})
    }
}

mutations: {
    addProduct(state, {product, hasProduct}){ 
        if (hasProduct){
            // mutate the state one way
        } else {
            // mutate the state another way 
        }
    }
}

สำหรับฉันดูเหมือนว่าข้างต้นไม่เพียง แต่จะซับซ้อน แต่ยังค่อนข้าง "รั่ว" เนื่องจากบางส่วนของรหัสที่อยู่ในการดำเนินการอย่างชัดเจน oozing จากตรรกะภายในของการกลายพันธุ์

ในความคิดของฉันนี่เป็นข้อบ่งชี้ของการประนีประนอม ฉันเชื่อว่าการอนุญาตให้การกลายพันธุ์เพื่อรับ Getters นำเสนอความท้าทายบางอย่างโดยอัตโนมัติ มันอาจเป็นได้ทั้งการออกแบบของ Vuex เองหรือเครื่องมือ (vue-devtools et al) หรือเพื่อรักษาความเข้ากันได้แบบย้อนหลังบางอย่างหรือการรวมกันของความเป็นไปได้ที่ระบุไว้ทั้งหมด

สิ่งที่ฉันไม่เชื่อก็คือการส่ง Getters ไปยังการกลายพันธุ์ของคุณเองนั้นเป็นสัญญาณที่บ่งบอกว่าคุณกำลังทำอะไรผิดไป ฉันเห็นว่าเป็นเพียง "การปะแก้" หนึ่งในข้อบกพร่องของกรอบงาน


1
สำหรับฉันนั่นคือคำตอบที่ดีที่สุด หลังจากอ่านเสร็จฉันได้ "คลิก" นี้คุณรู้สึกเมื่อคุณรู้สึกว่าคุณเข้าใจบางสิ่ง
Robert Kusznier

Getters จะถูกcomputedส่งออกเป็นหลัก เป็นแบบอ่านอย่างเดียว วิธีที่ดีกว่าในการดูการกลายพันธุ์อาจเป็นการลบสิ่งที่if elseคุณมี เอกสาร vuex บอกว่าคุณสามารถทำบ้านได้มากกว่า 1 รายการcommitในการดำเนินการ ดังนั้นจึงเป็นเหตุผลที่จะสมมติว่าคุณสามารถกระทำการกลายพันธุ์บางอย่างขึ้นอยู่กับตรรกะ ฉันคิดว่าการกระทำเป็นวิธีหนึ่งที่จะบอกให้มิวเตชั่นกลายเป็นไฟ
Tamb

@Tamb: ทั้งรัฐและ Getters เสนอข้อมูลเชิงบริบท มันสมเหตุสมผลที่พวกเขาจะถูกสอบถามก่อนตัดสินใจว่าจะแก้ไขรัฐอย่างไร เมื่อข้อมูลนั้นสามารถดึงข้อมูลได้อย่างสมบูรณ์จากรัฐมันก็สมเหตุสมผลว่าตรรกะทั้งหมดถูกห่อหุ้มอยู่ในการกลายพันธุ์เดียวเนื่องจากมันสามารถเข้าถึงรัฐได้ นี่เป็นขั้นตอนการดำเนินงานมาตรฐานสำหรับผู้ตั้งค่า สิ่งที่สมเหตุสมผลน้อยกว่าคือการมีวิธีการที่แตกต่างอย่างสิ้นเชิงเพียงแค่ตอนนี้เราต้องค้นหา Getter เพื่อหาข้อมูลที่คล้ายกัน
Michael Ekoka

@ แทม: สิ่งที่คุณแนะนำคือเมื่อเราต้องการสอบถาม Getters เราควรเปลี่ยนรูปแบบข้างต้นและย้ายตรรกะการเลือกไปยังพร็อกซีแอ็คชันซึ่งสามารถเข้าถึง Getter และสามารถรวมกลุ่มกลายพันธุ์เป็นใบ้ขนาดเล็กร่วมกัน มันใช้งานได้ แต่มันยังคงวนเวียนอยู่และไม่ได้ระบุกลิ่นเหม็นที่ฉันอ้างถึงในคำตอบของฉันมันแค่ย้ายไปที่อื่น
Michael Ekoka

เอกสารบอกว่าใช้ getters เมื่อคุณต้องการคำนวณสถานะ ดังนั้นจึงดูเหมือนถูกต้องในวันที่พวกเขาคล้ายกับคุณสมบัติที่คำนวณได้ Idk สิ่งที่คุณกำลังทำอยู่โดยบอกว่าแอ็คชั่นสามารถประสานการกลายพันธุ์ด้วยกัน เอกสารกล่าวอย่างชัดเจนว่าจะวางตรรกะทางธุรกิจไว้ในการดำเนินการ
Tamb

15

ฉันคิดว่าคำตอบ TLDR คือการกลายพันธุ์นั้นหมายถึงการซิงโครนัส / การทำธุรกรรม ดังนั้นหากคุณต้องการเรียกใช้ Ajax หรือทำโค้ดอะซิงโครนัสอื่น ๆ คุณต้องทำเช่นนั้นในการดำเนินการจากนั้นทำการคอมมิวเตชั่นหลังจากนั้นเพื่อตั้งค่าสถานะใหม่


1
ดูเหมือนว่าจะเป็นการสรุปของเอกสาร; ซึ่งไม่มีอะไรผิดปกติกับ อย่างไรก็ตามปัญหาของคำตอบนี้คือสิ่งที่ยืนยันไม่จำเป็นต้องเป็นจริง คุณสามารถแก้ไขสถานะภายในการกลายพันธุ์เมื่อเรียกใช้ฟังก์ชันอะซิงโครนัส / AJAX ซึ่งสามารถเปลี่ยนแปลงได้ในการเรียกกลับที่สมบูรณ์ ฉันคิดว่านี่เป็นสิ่งที่ทำให้เกิดความสับสนอย่างมากเกี่ยวกับสาเหตุที่การกระทำควรใช้สำหรับแนวทางการพัฒนาที่ดีที่สุดเมื่อทำงานกับ Vuex ฉันรู้ว่ามันเป็นแหล่งของความสับสนสำหรับฉันเมื่อฉันเริ่มทำงานกับ Vuex
Erutan409

8

ความแตกต่างที่สำคัญระหว่างการกระทำและการกลายพันธุ์:

  1. ภายในการดำเนินการคุณสามารถเรียกใช้รหัสแบบอะซิงโครนัส แต่ไม่ใช่ในการกลายพันธุ์ ดังนั้นใช้การกระทำสำหรับรหัสแบบอะซิงโครนัสมิฉะนั้นใช้การกลายพันธุ์
  2. ภายในการกระทำที่คุณสามารถเข้าถึง getters, รัฐ, การกลายพันธุ์ (กระทำพวกเขา), การกระทำ (ส่งพวกเขา) ในการกลายพันธุ์ที่คุณสามารถเข้าถึงรัฐ ดังนั้นหากคุณต้องการเข้าถึงเฉพาะการกลายพันธุ์ที่รัฐใช้มิฉะนั้นจะใช้การกระทำ

5

ให้เป็นไปตาม docs

การดำเนินการคล้ายกับการกลายพันธุ์ความแตกต่างคือ:

  • แทนที่จะกรรมวิธีรัฐกระทำ กระทำการกลายพันธุ์
  • การดำเนินการสามารถมีการดำเนินการแบบอะซิงโครนัสโดยพลการ

พิจารณาตัวอย่างต่อไปนี้

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++               //Mutating the state. Must be synchronous
    }
  },
  actions: {
    increment (context) {
      context.commit('increment') //Committing the mutations. Can be asynchronous.
    }
  }
})

ตัวจัดการการดำเนินการ ( เพิ่มขึ้น ) ได้รับวัตถุบริบทซึ่งเปิดเผยชุดวิธีการ / คุณสมบัติเดียวกันบนอินสแตนซ์ที่เก็บดังนั้นคุณสามารถโทร context.commit เพื่อกระทำการกลายพันธุ์หรือเข้าถึงสถานะและ getters ผ่าน context.state และ context.getters


1
สามารถเรียกได้จากฟังก์ชั่น 'การกลายพันธุ์' ซึ่งเป็นวิธีการจากองค์ประกอบ vuejs?
Alberto Acuña

@ AlbertoAcuñaฉันมีคำถามเดียวกันเพราะเมื่อฉันลองทำเช่นนั้นมันจะพ่นข้อผิดพลาดที่การกลายพันธุ์ในท้องถิ่นไม่ได้กำหนดไว้
Rutwick Gangurde

5

ข้อจำกัดความรับผิดชอบ - ฉันเพิ่งเริ่มใช้ vuejs เท่านั้นนี่คือฉันคาดการณ์ถึงเจตนาการออกแบบ

การดีบัก Time Machine ใช้สแน็ปช็อตของรัฐและแสดงลำดับเวลาของการดำเนินการและการกลายพันธุ์ ในทางทฤษฎีเราน่าจะมีactionsข้างบันทึกของเซทเทอร์สเตตและผู้ทะเยอทะยานเพื่ออธิบายการกลายพันธุ์ แต่แล้ว:

  • เราจะมีอินพุตที่ไม่บริสุทธิ์ (ผลลัพธ์ async) ซึ่งทำให้ตัวตั้งค่าและตัวรับสัญญาณ นี่จะเป็นเรื่องยากที่จะทำตามตัวตั้งค่าและการเชื่อมต่อแบบ async และ getters ที่แตกต่างกันอย่างมีเหตุผล ที่ยังคงสามารถเกิดขึ้นได้กับการmutationsทำธุรกรรม แต่จากนั้นเราสามารถพูดได้ว่าการทำธุรกรรมจะต้องมีการปรับปรุงเมื่อเทียบกับมันเป็นเงื่อนไขการแข่งขันในการกระทำ การกลายพันธุ์ที่ไม่ระบุตัวตนในการดำเนินการอาจทำให้ข้อผิดพลาดประเภทนี้ซ้ำได้ง่ายขึ้นเนื่องจากการเขียนโปรแกรม async นั้นบอบบางและยาก
  • บันทึกธุรกรรมจะอ่านยากเนื่องจากจะไม่มีชื่อสำหรับการเปลี่ยนแปลงสถานะ มันจะคล้ายโค้ดมากขึ้นและภาษาอังกฤษน้อยลงและขาดการจัดกลุ่มเชิงตรรกะของการกลายพันธุ์
  • มันอาจจะมีเล่ห์เหลี่ยมและมีประสิทธิภาพน้อยกว่าในการบันทึกการกลายพันธุ์ใด ๆ บนวัตถุข้อมูลซึ่งตรงข้ามกับตอนนี้ที่มีจุดต่างที่กำหนดไว้พร้อมกัน - ก่อนและหลังการเรียกฟังก์ชันการกลายพันธุ์ ฉันไม่แน่ใจว่าปัญหาใหญ่แค่ไหน

เปรียบเทียบบันทึกธุรกรรมต่อไปนี้กับการกลายพันธุ์ที่มีชื่อ

Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])

ด้วยบันทึกธุรกรรมที่ไม่มีการกลายพันธุ์ที่มีชื่อ:

Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]

ฉันหวังว่าคุณสามารถคาดการณ์จากตัวอย่างนั้นความซับซ้อนที่เพิ่มขึ้นใน async และการกลายพันธุ์ที่ไม่ระบุชื่อภายในการกระทำ

https://vuex.vuejs.org/en/mutations.html

ตอนนี้ลองนึกภาพเรากำลังตรวจแก้จุดบกพร่องของแอปและดูบันทึกการกลายพันธุ์ของ devtool สำหรับการกลายพันธุ์ทุกครั้งที่บันทึกไว้ devtool จะต้องจับภาพ "ก่อน" และ "หลัง" ภาพรวมของรัฐ อย่างไรก็ตามการโทรกลับแบบอะซิงโครนัสภายในตัวอย่างการกลายพันธุ์ข้างต้นทำให้เป็นไปไม่ได้: การโทรกลับยังไม่ถูกเรียกเมื่อการกลายพันธุ์ถูกทำให้เกิดขึ้นและไม่มีทางที่ devtool จะรู้ได้เมื่อการโทรกลับจะถูกเรียก โดยพื้นฐานแล้วไม่สามารถติดตามได้!


4

การกลายพันธุ์:

Can update the state. (Having the Authorization to change the state).

ดำเนินการ:

Actions are used to tell "which mutation should be triggered"

ด้วยวิธี Redux

Mutations are Reducers
Actions are Actions

ทำไมต้องทั้งคู่ ??

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


2

สิ่งนี้ทำให้ฉันสับสนเช่นกันดังนั้นฉันจึงทำการสาธิตอย่างง่าย

component.vue

<template>
    <div id="app">
        <h6>Logging with Action vs Mutation</h6>
        <p>{{count}}</p>
        <p>
            <button @click="mutateCountWithAsyncDelay()">Mutate Count directly with delay</button>
        </p>
        <p>
            <button @click="updateCountViaAsyncAction()">Update Count via action, but with delay</button>
        </p>
        <p>Note that when the mutation handles the asynchronous action, the "log" in console is broken.</p>
        <p>When mutations are separated to only update data while the action handles the asynchronous business
            logic, the log works the log works</p>
    </div>
</template>

<script>

        export default {
                name: 'app',

                methods: {

                        //WRONG
                        mutateCountWithAsyncDelay(){
                                this.$store.commit('mutateCountWithAsyncDelay');
                        },

                        //RIGHT
                        updateCountViaAsyncAction(){
                                this.$store.dispatch('updateCountAsync')
                        }
                },

                computed: {
                        count: function(){
                                return this.$store.state.count;
                        },
                }

        }
</script>

store.js

import 'es6-promise/auto'
import Vuex from 'vuex'
import Vue from 'vue';

Vue.use(Vuex);

const myStore = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {

        //The WRONG way
        mutateCountWithAsyncDelay (state) {
            var log1;
            var log2;

            //Capture Before Value
            log1 = state.count;

            //Simulate delay from a fetch or something
            setTimeout(() => {
                state.count++
            }, 1000);

            //Capture After Value
            log2 = state.count;

            //Async in mutation screws up the log
            console.log(`Starting Count: ${log1}`); //NRHG
            console.log(`Ending Count: ${log2}`); //NRHG
        },

        //The RIGHT way
        mutateCount (state) {
            var log1;
            var log2;

            //Capture Before Value
            log1 = state.count;

            //Mutation does nothing but update data
            state.count++;

            //Capture After Value
            log2 = state.count;

            //Changes logged correctly
            console.log(`Starting Count: ${log1}`); //NRHG
            console.log(`Ending Count: ${log2}`); //NRHG
        }
    },

    actions: {

        //This action performs its async work then commits the RIGHT mutation
        updateCountAsync(context){
            setTimeout(() => {
                context.commit('mutateCount');
            }, 1000);
        }
    },
});

export default myStore;

หลังจากทำการวิจัยนี้ข้อสรุปที่ฉันได้รับก็คือการกลายพันธุ์เป็นการประชุมที่เน้นเฉพาะการเปลี่ยนแปลงข้อมูลเพื่อแยกข้อกังวลที่ดีขึ้นและปรับปรุงการบันทึกก่อนและหลังข้อมูลที่อัปเดต ในขณะที่การกระทำเป็นเลเยอร์ของนามธรรมที่จัดการตรรกะระดับที่สูงขึ้นแล้วเรียกการกลายพันธุ์อย่างเหมาะสม


0

1. จากเอกสาร :

การดำเนินการคล้ายกับการกลายพันธุ์ความแตกต่างคือ:

  • แทนที่จะทำการผ่าเหล่าของรัฐการกระทำกระทำการกลายพันธุ์
  • การดำเนินการสามารถมีการดำเนินการแบบอะซิงโครนัสโดยพลการ

การดำเนินการสามารถมีการดำเนินการแบบอะซิงโครนัส แต่การกลายพันธุ์ไม่สามารถทำได้

2. เราเรียกการกลายพันธุ์เราสามารถเปลี่ยนสถานะได้โดยตรง และเรายังสามารถดำเนินการเพื่อเปลี่ยนสถานะโดยใช้วิธีนี้:

actions: {
  increment (store) {
    // do whatever ... then change the state
    store.dispatch('MUTATION_NAME')
  }
}

การกระทำถูกออกแบบมาเพื่อจัดการกับสิ่งอื่น ๆ มากขึ้นเราสามารถทำหลายสิ่งในนั้น (เราสามารถใช้การทำงานแบบอะซิงโครนัส) จากนั้นเปลี่ยนสถานะโดยส่งการกลายพันธุ์ที่นั่น


0

เพราะไม่มีรัฐโดยไม่มีการกลายพันธุ์! เมื่อกระทำ - ชิ้นส่วนของตรรกะที่เปลี่ยนสถานะในลักษณะที่คาดการณ์ได้จะถูกดำเนินการ การกลายพันธุ์เป็นวิธีเดียวที่จะตั้งค่าหรือเปลี่ยนแปลงสถานะ (ดังนั้นจึงไม่มีการเปลี่ยนแปลงโดยตรง!) และนอกจากนี้ - พวกเขาจะต้องซิงโครนัส โซลูชันนี้ขับเคลื่อนการทำงานที่สำคัญมาก: การกลายพันธุ์กำลังเข้าสู่ระบบของ devtools และนั่นทำให้คุณสามารถอ่านและทำนายได้อย่างยอดเยี่ยม!

อีกสิ่งหนึ่ง - การกระทำ ดังที่ได้กล่าวไว้ - การกระทำก่อกลายพันธุ์ ดังนั้นพวกเขาจะไม่เปลี่ยนร้านค้าและไม่จำเป็นต้องให้ซิงโครนัส แต่พวกเขาสามารถจัดการตรรกะอะซิงโครนัสชิ้นพิเศษได้!


0

ดูเหมือนว่าไม่จำเป็นที่จะมีเลเยอร์พิเศษactionsเพียงเพื่อโทรmutationsหาตัวอย่างเช่น:

const actions = {
  logout: ({ commit }) => {
    commit("setToken", null);
  }
};

const mutations = {
  setToken: (state, token) => {
    state.token = token;
  }
};

ดังนั้นถ้าโทรactionsโทรlogoutทำไมไม่โทรหาการกลายพันธุ์ของตัวเองหรือไม่

แนวคิดทั้งหมดของการกระทำคือการเรียกการกลายพันธุ์หลายอย่างจากภายในการกระทำเดียวหรือทำการร้องขอ Ajax หรือตรรกะอะซิงโครนัสชนิดใด ๆ ที่คุณสามารถจินตนาการได้

ในที่สุดเราอาจมีการกระทำที่ทำให้หลายคำขอเครือข่ายและในที่สุดก็เรียกการกลายพันธุ์ที่แตกต่างกัน

ดังนั้นเราจึงพยายามที่จะสิ่งที่เป็นความซับซ้อนมากจากของเราVuex.Store()เป็นไปได้ในของเราactionsและใบนี้ของเราmutations, stateและgettersทำความสะอาดและตรงไปตรงมาและตกอยู่ในแนวเดียวกันกับชนิดของต้นแบบที่ทำให้ห้องสมุดเช่น Vue และตอบสนองความนิยม


0

ฉันใช้ Vuex อย่างมืออาชีพมาประมาณ 3 ปีแล้วและนี่คือสิ่งที่ฉันคิดว่าฉันเข้าใจถึงความแตกต่างที่สำคัญระหว่างการกระทำและการกลายพันธุ์คุณจะได้ประโยชน์จากการใช้มันร่วมกันได้อย่างไรและคุณจะทำให้ชีวิตของคุณลำบากขึ้นได้อย่างไร อย่าใช้มันอย่างดี

เป้าหมายหลักของ Vuex คือการนำเสนอรูปแบบใหม่เพื่อควบคุมพฤติกรรมของแอปพลิเคชันของคุณ: การทำปฏิกิริยา แนวคิดคือการถ่ายโอนออเคสตร้าของสถานะแอปพลิเคชันของคุณไปยังวัตถุพิเศษ: ร้านค้า มันให้วิธีการที่สะดวกในการเชื่อมต่อส่วนประกอบของคุณโดยตรงกับข้อมูลร้านค้าของคุณเพื่อนำไปใช้ตามความสะดวกของตนเอง สิ่งนี้จะช่วยให้ส่วนประกอบของคุณมุ่งเน้นไปที่งานของพวกเขา: การกำหนดเทมเพลตสไตล์และพฤติกรรมของส่วนประกอบพื้นฐานที่จะนำเสนอให้กับผู้ใช้ของคุณ ในขณะเดียวกันที่จัดเก็บจัดการกับการโหลดข้อมูลหนัก

นั่นไม่ได้เป็นเพียงข้อได้เปรียบของรูปแบบนี้เท่านั้น ความจริงที่ว่าร้านค้าเป็นแหล่งข้อมูลเดียวสำหรับทั้งแอปพลิเคชันของคุณมีศักยภาพที่ยอดเยี่ยมในการใช้งานข้อมูลนี้ซ้ำได้ในหลาย ๆ องค์ประกอบ นี่ไม่ใช่รูปแบบแรกที่พยายามแก้ไขปัญหาของการสื่อสารข้ามองค์ประกอบนี้ แต่ที่มันส่องคือมันบังคับให้คุณใช้พฤติกรรมที่ปลอดภัยมากกับแอปพลิเคชันของคุณโดยห้ามไม่ให้คอมโพเนนต์ของคุณปรับเปลี่ยนสถานะของข้อมูลที่แบ่งปันนี้ และบังคับให้แทนที่จะใช้ "จุดสิ้นสุดสาธารณะ" เพื่อขอการเปลี่ยนแปลง

แนวคิดพื้นฐานคือ:

  • ร้านค้ามีสถานะภายในซึ่งไม่ควรเข้าถึงได้โดยตรงโดยส่วนประกอบ (mapState ถูกแบนอย่างมีประสิทธิภาพ)
  • ร้านค้ามีการกลายพันธุ์ซึ่งเป็นการแก้ไขแบบซิงโครนัสกับสถานะภายใน งานเดียวของการกลายพันธุ์คือการแก้ไขสถานะ ควรถูกเรียกจากการดำเนินการเท่านั้น ควรตั้งชื่อเพื่ออธิบายสิ่งต่าง ๆ ที่เกิดขึ้นกับรัฐ (ORDER_CANCELED, ORDER_CREATED) ทำให้สั้นและหวาน คุณสามารถผ่านขั้นตอนเหล่านี้ได้โดยใช้ส่วนขยายเบราว์เซอร์ Vue Devtools (เป็นการดีสำหรับการดีบักเช่นกัน!)
  • ร้านค้ายังมีการดำเนินการซึ่งควรจะเป็นแบบซิงค์หรือส่งคืนสัญญา สิ่งเหล่านี้เป็นการกระทำที่ส่วนประกอบของคุณจะเรียกใช้เมื่อต้องการแก้ไขสถานะของแอปพลิเคชัน ควรตั้งชื่อด้วยการดำเนินการเชิงธุรกิจ(คำกริยาเช่น cancelOrder, createOrder) ที่นี่คุณจะตรวจสอบและส่งคำขอของคุณ แต่ละการกระทำอาจเรียกใช้คอมมิชชันที่ต่างกันในขั้นตอนต่าง ๆ หากจำเป็นต้องเปลี่ยนสถานะ
  • ในที่สุดร้านค้ามี getters ซึ่งเป็นสิ่งที่คุณใช้ในการเปิดเผยสถานะของคุณกับส่วนประกอบของคุณ คาดว่าพวกมันจะถูกใช้อย่างหนักในหลาย ๆ องค์ประกอบเมื่อแอปพลิเคชันของคุณขยาย Vuex แคช getters อย่างหนักเพื่อหลีกเลี่ยงรอบการคำนวณที่ไร้ประโยชน์ (ตราบใดที่คุณไม่เพิ่มพารามิเตอร์ลงใน getter ของคุณ - พยายามอย่าใช้พารามิเตอร์) ดังนั้นอย่าลังเลที่จะใช้มันอย่างกว้างขวาง ตรวจสอบให้แน่ใจว่าคุณตั้งชื่อที่ใกล้เคียงที่สุดเท่าที่จะเป็นไปได้

ที่กล่าวว่ามายากลเริ่มต้นเมื่อเราเริ่มออกแบบโปรแกรมของเราในลักษณะนี้ ตัวอย่างเช่น:

  • เรามีส่วนประกอบที่เสนอรายการคำสั่งซื้อให้กับผู้ใช้ที่มีความเป็นไปได้ที่จะลบคำสั่งซื้อเหล่านั้น
  • ส่วนประกอบมีการแมปตัวรับการจัดเก็บ (deletableOrders) ซึ่งเป็นอาร์เรย์ของวัตถุที่มีรหัส
  • ส่วนประกอบมีปุ่มในแต่ละแถวของคำสั่งซื้อและการคลิกของมันจะถูกแมปไปยังการดำเนินการของร้านค้า (deleteOrder) ซึ่งส่งผ่านออเดอร์วัตถุไปยังมัน (ซึ่งเราจำได้ว่ามาจากรายการของร้านค้าเอง)
  • การกระทำของ store deleteOrder ทำสิ่งต่อไปนี้:
    • มันตรวจสอบการลบ
    • มันเก็บคำสั่งที่จะลบชั่วคราว
    • มันกระทำการกลายพันธุ์ ORDER_DELETED ด้วยคำสั่ง
    • มันส่งการเรียกใช้ API เพื่อลบคำสั่งซื้อจริง (ใช่หลังจากแก้ไขสถานะ!)
    • รอการเรียกเพื่อสิ้นสุด (สถานะได้รับการอัปเดตแล้ว) และเมื่อเกิดความล้มเหลวเราเรียกการกลายพันธุ์ ORDER_DELETE_FAILED ด้วยคำสั่งที่เราเก็บไว้ก่อนหน้านี้
  • การกลายพันธุ์ ORDER_DELETED จะเป็นการลบคำสั่งซื้อที่กำหนดออกจากรายการคำสั่งซื้อที่ลบได้ (ซึ่งจะอัปเดตตัวเรียก)
  • การกลายพันธุ์ ORDER_DELETE_FAILED เพียงแค่นำกลับมาและแก้ไขสถานะเพื่อแจ้งข้อผิดพลาด (ส่วนประกอบอื่นการแจ้งเตือนข้อผิดพลาดจะติดตามสถานะนั้นเพื่อให้ทราบว่าจะแสดงเมื่อใด)

ในที่สุดเรามีประสบการณ์ผู้ใช้ที่ถือว่าเป็น "ปฏิกิริยา" จากมุมมองของผู้ใช้ของเรารายการจะถูกลบทันที ส่วนใหญ่เราคาดหวังว่าจุดสิ้นสุดของเราจะทำงานได้ดีดังนั้นจึงสมบูรณ์แบบ เมื่อมันล้มเหลวเรายังคงสามารถควบคุมวิธีการที่แอปพลิเคชันของเราจะตอบสนองได้เนื่องจากเราแยกความกังวลเกี่ยวกับสถานะของแอปพลิเคชันส่วนหน้าของเราด้วยข้อมูลจริง

คุณไม่จำเป็นต้องมีร้านค้าเสมอไป หากคุณพบว่าคุณกำลังเขียนร้านค้าที่มีลักษณะเช่นนี้:

export default {
  state: {
    orders: []
  },
  mutations: {
    ADD_ORDER (state, order) {
       state.orders.push(order)
    },
    DELETE_ORDER (state, orderToDelete) {
       state.orders = state.orders.filter(order => order.id !== orderToDelete.id)
    }
  },
  actions: {
    addOrder ({commit}, order) {
      commit('ADD_ORDER', order)
    },
    deleteOrder ({commit}, order) {
      commit('DELETE_ORDER', order)
    }
  },
  getters: {
    orders: state => state.orders
  }
}

สำหรับฉันดูเหมือนว่าคุณกำลังใช้ร้านค้าเป็นแหล่งข้อมูลเท่านั้นและอาจขาดหายไปในแง่มุมด้านปฏิกิริยาของมันโดยไม่ปล่อยให้มันควบคุมตัวแปรที่แอปพลิเคชันของคุณทำ โดยทั่วไปคุณสามารถและควรถ่ายโค้ดบางบรรทัดที่เขียนในส่วนประกอบของคุณไปยังร้านค้าของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.