การคืนสัญญาจากการกระทำของ Vuex


131

ฉันเพิ่งเริ่มโยกย้ายสิ่งต่างๆจาก jQ ไปยังเฟรมเวิร์กที่มีโครงสร้างมากกว่าคือ VueJS และฉันก็ชอบมัน!

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

อันนี้ฉันรู้สึกว่าดีจากการออกแบบ แต่ไม่รู้ว่ามันขัดแย้งกับวัฏจักร Vuex ของการไหลของข้อมูลแบบทิศทางเดียวหรือไม่

โดยพื้นฐานแล้วถือเป็นแนวทางปฏิบัติที่ดีในการคืนสัญญา (- เหมือน) วัตถุจากการกระทำหรือไม่? ฉันถือว่าสิ่งเหล่านี้เป็นเหมือน async wrappers ด้วยสถานะของความล้มเหลวและสิ่งที่คล้ายกันดังนั้นดูเหมือนว่าจะเหมาะสมที่จะคืนสัญญา ผู้กลายพันธุ์ในทางตรงกันข้ามจะเปลี่ยนสิ่งต่าง ๆ และเป็นโครงสร้างที่บริสุทธิ์ภายในร้านค้า / โมดูล

คำตอบ:


256

actionsใน Vuex เป็นแบบอะซิงโครนัส วิธีเดียวที่จะทำให้ฟังก์ชันการโทร (ผู้เริ่มต้นดำเนินการ) ทราบว่าการดำเนินการเสร็จสมบูรณ์แล้ว - คือการส่งคืนคำสัญญาและแก้ไขในภายหลัง

นี่คือตัวอย่าง: myActionส่งคืน a Promise, ทำการโทร http และแก้ไขหรือปฏิเสธในPromiseภายหลัง - ทั้งหมดแบบอะซิงโครนัส

actions: {
    myAction(context, data) {
        return new Promise((resolve, reject) => {
            // Do something here... lets say, a http call using vue-resource
            this.$http("/api/something").then(response => {
                // http success, call the mutator and change something in state
                resolve(response);  // Let the calling function know that http is done. You may send some data back
            }, error => {
                // http failed, let the calling function know that action did not work out
                reject(error);
            })
        })
    }
}

ตอนนี้เมื่อคอมโพเนนต์ Vue ของคุณเริ่มmyActionทำงานมันจะได้รับวัตถุ Promise นี้และสามารถรู้ได้ว่ามันสำเร็จหรือไม่ นี่คือโค้ดตัวอย่างสำหรับคอมโพเนนต์ Vue:

export default {
    mounted: function() {
        // This component just got created. Lets fetch some data here using an action
        this.$store.dispatch("myAction").then(response => {
            console.log("Got some data, now lets show something in this component")
        }, error => {
            console.error("Got nothing from server. Prompt user to check internet connection and try again")
        })
    }
}

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

และหมายเหตุสุดท้ายเกี่ยวกับmutators- ดังที่คุณชี้ให้เห็นอย่างถูกต้องว่าเป็นแบบซิงโครนัส พวกเขาเปลี่ยนสิ่งในและมักจะเรียกจากstate actionsไม่จำเป็นต้องผสมPromisesกับmutatorsที่actionsจับส่วนนั้น

แก้ไข: มุมมองของฉันเกี่ยวกับวัฏจักร Vuex ของการไหลของข้อมูลทิศทางเดียว:

หากคุณเข้าถึงข้อมูลเช่นthis.$store.state["your data key"]ในส่วนประกอบของคุณกระแสข้อมูลจะเป็นแบบทิศทางเดียว

สัญญาจากการกระทำเป็นเพียงการบอกให้ส่วนประกอบรู้ว่าการกระทำนั้นสมบูรณ์

ส่วนประกอบอาจรับข้อมูลจากฟังก์ชันการแก้ไขสัญญาในตัวอย่างข้างต้น (ไม่ใช่แบบทิศทางเดียวจึงไม่แนะนำ) หรือโดยตรงจาก$store.state["your data key"]ที่เป็นแบบทิศทางเดียวและเป็นไปตามวงจรชีวิตของข้อมูล vuex

ย่อหน้าข้างบนถือว่า mutator ของคุณใช้Vue.set(state, "your data key", http_data)เมื่อการเรียก http เสร็จสิ้นในการกระทำของคุณ


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

1
@ceejayoz เห็นด้วยรัฐควรเป็นแหล่งความจริงเดียวสำหรับวัตถุข้อมูลทั้งหมด แต่คำสัญญาเป็นวิธีเดียวที่จะสื่อสารกลับไปยังผู้ริเริ่มการกระทำ ตัวอย่างเช่นหากคุณต้องการแสดงปุ่ม "ลองอีกครั้ง" หลังจากที่ http ล้มเหลวข้อมูลนั้นจะไม่เข้าสู่สถานะ แต่สามารถสื่อสารกลับผ่านทางไฟล์Promise.reject().
มานี

1
ที่สามารถจัดการได้อย่างง่ายดายภายในร้านค้า Vuex การกระทำของตัวเองสามารถทำให้เครื่องกลายพันธุ์failedที่ตั้งค่าstate.foo.failed = trueไว้ซึ่งส่วนประกอบสามารถจัดการได้ ไม่จำเป็นต้องมีการส่งคำสัญญาไปยังส่วนประกอบสำหรับสิ่งนั้นและในฐานะโบนัสสิ่งอื่นใดที่ต้องการตอบสนองต่อความล้มเหลวเดียวกันก็สามารถทำได้จากร้านค้าเช่นกัน
ceejayoz

4
@ceejayoz ดูการเขียนการดำเนินการ (ส่วนสุดท้าย) ในเอกสาร - vuex.vuejs.org/en/actions.html - การดำเนินการเป็นแบบอะซิงโครนัสดังนั้นการส่งคืน Promise จึงเป็นความคิดที่ดีดังที่ระบุไว้ในเอกสารเหล่านั้น อาจจะไม่ใช่ในกรณี $ http ด้านบน แต่ในบางกรณีเราอาจจำเป็นต้องทราบเมื่อการดำเนินการเสร็จสิ้น
มานี

6
@DanielPark ใช่ "มันขึ้นอยู่กับสถานการณ์และความชอบของนักพัฒนาแต่ละคน ในกรณีของฉันฉันต้องการหลีกเลี่ยงค่ากลางเช่น{isLoading:true}ในสถานะของฉันจึงหันไปใช้คำสัญญา ความชอบของคุณอาจแตกต่างกันไป ในตอนท้ายของวันวัตถุประสงค์ของเราคือการเขียนโค้ดที่ไม่เกะกะและดูแลรักษาได้ ไม่ว่าสัญญาจะบรรลุวัตถุประสงค์นั้นหรือสถานะ vuex - ให้นักพัฒนาและทีมแต่ละคนตัดสินใจ
มานี

41

สำหรับข้อมูลเกี่ยวกับหัวข้อปิด: คุณไม่จำเป็นต้องสร้างสัญญา axios จะส่งคืนตัวเอง:

อ้างอิง: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4

ตัวอย่าง:

    export const loginForm = ({ commit }, data) => {
      return axios
        .post('http://localhost:8000/api/login', data)
        .then((response) => {
          commit('logUserIn', response.data);
        })
        .catch((error) => {
          commit('unAuthorisedUser', { error:error.response.data });
        })
    }

ตัวอย่างอื่น:

    addEmployee({ commit, state }) {       
      return insertEmployee(state.employee)
        .then(result => {
          commit('setEmployee', result.data);
          return result.data; // resolve 
        })
        .catch(err => {           
          throw err.response.data; // reject
        })
    }

อีกตัวอย่างหนึ่งของasync-await

    async getUser({ commit }) {
        try {
            const currentUser = await axios.get('/user/current')
            commit('setUser', currentUser)
            return currentUser
        } catch (err) {
            commit('setUser', null)
            throw 'Unable to fetch current user'
        }
    },

ตัวอย่างสุดท้ายไม่ควรซ้ำซ้อนเนื่องจากแอ็คชัน axios เป็นค่าเริ่มต้นเป็นแบบอะซิงโครนัสอยู่แล้วหรือไม่
nonNumericalFloat

9

การปฏิบัติ

ADD_PRODUCT : (context,product) => {
  return Axios.post(uri, product).then((response) => {
    if (response.status === 'success') {  
      context.commit('SET_PRODUCT',response.data.data)
    }
    return response.data
  });
});

ตัวแทน

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})

2
การตอบสนองที่ไม่ทำงานนี้ไม่ได้กำหนดไว้ในส่วนประกอบ
Nand Lal

1
ฉันคิดว่าคุณลืมเพิ่มผลตอบแทนในฟังก์ชัน ADD_PRODUCT
Bhaskararao Gummidi

ควรเป็นตัวพิมพ์เล็ก "a" ใน "axios"
bigp

ฉันใช้ Axois เป็น const ที่นำเข้าจาก 'axios'
Bhaskararao Gummidi

0

TL: DR; คืนคำสัญญาจากการกระทำของคุณเมื่อจำเป็นเท่านั้น แต่แห้งผูกมัดการกระทำเดียวกัน

เป็นเวลานานฉันยังคิดว่าการกระทำที่ส่งคืนนั้นขัดแย้งกับวัฏจักร Vuex ของการไหลของข้อมูลแบบทิศทางเดียว

แต่มีกรณี EDGEที่การคืนคำสัญญาจากการกระทำของคุณอาจ "จำเป็น"

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

ตัวอย่างโง่ ๆ

หน้าที่ผู้ใช้สามารถแก้ไขชื่อผู้ใช้ใน navbar และใน / profile page (ซึ่งมี navbar) ทั้งสองทริกเกอร์การดำเนินการ "เปลี่ยนชื่อผู้ใช้" ซึ่งเป็นแบบอะซิงโครนัส หากคำสัญญาล้มเหลวหน้าควรแสดงเฉพาะข้อผิดพลาดในองค์ประกอบที่ผู้ใช้พยายามเปลี่ยนชื่อผู้ใช้

แน่นอนว่ามันเป็นตัวอย่างที่โง่ แต่ฉันไม่เห็นวิธีแก้ปัญหานี้โดยไม่ต้องทำซ้ำรหัสและทำการเรียกแบบเดียวกันใน 2 การกระทำที่แตกต่างกัน


-1

actions.js

const axios = require('axios');
const types = require('./types');

export const actions = {
  GET_CONTENT({commit}){
    axios.get(`${URL}`)
      .then(doc =>{
        const content = doc.data;
        commit(types.SET_CONTENT , content);
        setTimeout(() =>{
          commit(types.IS_LOADING , false);
        } , 1000);
      }).catch(err =>{
        console.log(err);
    });
  },
}

home.vue

<script>
  import {value , onCreated} from "vue-function-api";
  import {useState, useStore} from "@u3u/vue-hooks";

  export default {
    name: 'home',

    setup(){
      const store = useStore();
      const state = {
        ...useState(["content" , "isLoading"])
      };
      onCreated(() =>{
        store.value.dispatch("GET_CONTENT" );
      });

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