Vuex 从状态加载现有表单数据以进行编辑

Posted

技术标签:

【中文标题】Vuex 从状态加载现有表单数据以进行编辑【英文标题】:Vuex load existing form data from state to edit 【发布时间】:2021-01-09 01:41:18 【问题描述】:

我通过本教程尝试学习 Vue,我已经完成并且它可以工作,但我正在尝试做出我正在努力的改变。

https://savvyapps.com/blog/definitive-guide-building-web-app-vuejs-firebase

所以有一个“设置”页面,其中包含用户个人资料(他们可以编辑自己的姓名等)。当“设置”/“个人资料”页面加载时,我希望表单加载他们现有的数据,这样他们就可以修改它并按保存。

它当前加载为 :placeholder="userProfile.name" 的占位符 - 我希望它只使用实际值填充表单,而不是将其作为占位符。

感觉这样做应该非常简单,但不能让它优雅地工作。

Settings.vue

<template>
  <section id="settings">
    <div class="col1">
      <h3>Settings</h3>
      <p>Update your profile</p>

      <transition name="fade">
        <p v-if="showSuccess" class="success">profile updated</p>
      </transition>

      <form @submit.prevent>
        <label for="name">Name</label>
        <input v-model.trim="name" type="text" id="name" />

        <label for="title">Job Title</label>
        <input v-model.trim="title" type="text" id="title" />

        <button @click="updateProfile()" class="button">Update Profile</button>
      </form>
    </div>
  </section>
</template>

<script>
import  mapState  from "vuex";

export default 
  data() 
    return 
      name: "",
      title: "",
      showSuccess: false,
    ;
  ,
  computed: 
    ...mapState(["userProfile"]),
  ,
  methods: 
    updateProfile() 
      this.$store.dispatch("updateProfile", 
        name: this.name !== "" ? this.name : this.userProfile.name,
        title: this.title !== "" ? this.title : this.userProfile.title,
      );

      this.name = "";
      this.title = "";

      this.showSuccess = true;

      setTimeout(() => 
        this.showSuccess = false;
      , 2000);
    ,
  ,
;
</script>

<style lang="scss" scoped>
</style>

我尝试将数据部分更改为此,这在我离开页面并返回时有效,但如果我刷新页面 (F5),则字段为空白,直到我离开页面并再次返回。

data() 
    return 
      name: this.$store.state.userProfile.name,
      title: this.$store.state.userProfile.title,
      showSuccess: false,
    ;
  ,

如果你需要,这里是我的商店:

存储/index.js

import Vue from "vue";
import Vuex from "vuex";
import * as fb from "../firebase";
import router from "../router/index";

Vue.use(Vuex);

// realtime firebase connection
fb.postsCollection.orderBy("createdOn", "desc").onSnapshot((snapshot) => 
  let postsArray = [];

  snapshot.forEach((doc) => 
    let post = doc.data();
    post.id = doc.id;

    postsArray.push(post);
  );

  store.commit("setPosts", postsArray);
);

const store = new Vuex.Store(
  state: 
    userProfile: ,
    posts: [],
  ,
  mutations: 
    setUserProfile(state, val) 
      state.userProfile = val;
    ,
    setPosts(state, val) 
      state.posts = val;
    ,
  ,
  actions: 
    async signup( dispatch , form) 
      // sign user up
      const  user  = await fb.auth.createUserWithEmailAndPassword(
        form.email,
        form.password
      );

      // create user profile object in userCollections
      await fb.usersCollection.doc(user.uid).set(
        name: form.name,
        title: form.title,
      );

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    ,
    async login( dispatch , form) 
      // sign user in
      const  user  = await fb.auth.signInWithEmailAndPassword(
        form.email,
        form.password
      );

      // fetch user profile and set in state
      dispatch("fetchUserProfile", user);
    ,
    async logout( commit ) 
      await fb.auth.signOut();

      // clear userProfile and redirect to /login
      commit("setUserProfile", );
      router.push("/login");
    ,
    async fetchUserProfile( commit , user) 
      // fetch user profile
      const userProfile = await fb.usersCollection.doc(user.uid).get();

      // set user profile in state
      commit("setUserProfile", userProfile.data());

      // change route to dashboard
      if (router.currentRoute.path === "/login") 
        router.push("/");
      
    ,
    async createPost( state , post) 
      await fb.postsCollection.add(
        createdOn: new Date(),
        content: post.content,
        userId: fb.auth.currentUser.uid,
        userName: state.userProfile.name,
        comments: 0,
        likes: 0,
      );
    ,
    async likePost(context,  id, likesCount ) 
      const userId = fb.auth.currentUser.uid;
      const docId = `$userId_$id`;

      // check if user has liked post
      const doc = await fb.likesCollection.doc(docId).get();
      if (doc.exists) 
        return;
      

      // create post
      await fb.likesCollection.doc(docId).set(
        postId: id,
        userId: userId,
      );

      // update post likes count
      fb.postsCollection.doc(id).update(
        likes: likesCount + 1,
      );
    ,
    async updateProfile( dispatch , user) 
      const userId = fb.auth.currentUser.uid;
      // update user object
      /*const userRef = */await fb.usersCollection.doc(userId).update(
        name: user.name,
        title: user.title,
      );

      dispatch("fetchUserProfile",  uid: userId );

      // update all posts by user
      const postDocs = await fb.postsCollection
        .where("userId", "==", userId)
        .get();
      postDocs.forEach((doc) => 
        fb.postsCollection.doc(doc.id).update(
          userName: user.name,
        );
      );

      // update all comments by user
      const commentDocs = await fb.commentsCollection
        .where("userId", "==", userId)
        .get();
      commentDocs.forEach((doc) => 
        fb.commentsCollection.doc(doc.id).update(
          userName: user.name,
        );
      );
    ,
  ,
  modules: ,
);

export default store;

编辑

我应该提到这些数据正在从 Firebase Firestore 加载到状态中。看起来这只是一个时间问题,当它在组件上设置 data() 时数据还没有完全加载 - 我添加了一些控制台日志。

Fetching user profile.. Settings.vue?e12e:29
Setting Data... index.js?4360:75
Performing setUserProfile commit.. index.js?4360:29
Setting user profile in state, last step..

再次,只是对 Vue 了解不够,还不知道如何最好地更改该顺序..

【问题讨论】:

【参考方案1】:

v-model 获取设置你传递给它的任何值。由于您要编辑状态属性,因此一旦您修改 &lt;input&gt; 的值,它就会尝试更改(又名 mutate)状态属性的值。这将打破不变性原则[1]。 这里的解决方案是将计算属性传递给v-model,它使用getter 和setter,告诉组件从哪里获取值以及如何更新它。

默认情况下,computed 属性是一种简写形式,仅包含 getter。基本上,

computed: 
  name() 
    return this.$store.state.userProfile.name
  

...可以写成:

computed: 
  name: 
    get() 
      return this.$store.state.userProfile.name
    
  

您需要添加一个 setter 来提交适当的突变,以便更新状态:

computed: 
  ...mapState(["userProfile"]),
  name: 
    get() 
      return this.userProfile.name
    ,
    set(val) 
      this.$store.commit('setUserProfile', 
        ...this.userProfile,
        name: val
      );
    
  ,
  title: 
    get() 
      return this.userProfile.title
    ,
    set(val) 
      this.$store.commit('setUserProfile', 
        ...this.userProfile,
        title: val
      );
    
  

计算的 setter 记录在 here。


[1] - 你使用 Vuex 的原因是你不想让任何组件直接modify your data。相反,您希望他们将更改提交到状态,以便使用该数据的每个组件都收到更改通知。如果您允许 v-model 直接更改您的数据,那么您将违反不变性原则,因此您的状态将不再是唯一的真相来源。

【讨论】:

这解决了问题。非常感谢您的详细解释:)【参考方案2】:

要考虑两件事,首先是当您想从 state crate getter 中的变量中获取值时(作为良好的 vuex 实践):

Vuex 文件:

const store = new Vuex.Store(
  state: 
    userProfile: ,
    posts: [],
  ,

  getters:
    getUserProfile: (state) => state.userProfile
  

Settigs.vue 所以,要完成你想要的,你可以在挂载的方法中加载 data() 中的变量:

export default 
  data() 
    return 
      name: "",
      title: "",
      showSuccess: false,
    ;
  ,
  computed: 
    ...mapState(["getUserProfile"]),
  ,

  mounted()
   this.name = getUserProfile.name
  

所以,如果你期望用户刷新页面而不丢失加载的数据,你不能单独使用 vuex,因为当你刷新页面时,vuex 系统也会重启。如果您想维护刷新页面后加载的数据,请使用 localstorage 和 vuex 或类似的解决方案。

【讨论】:

对不起,我还应该提到,在教程中,它会从 Firebase 数据库中获取数据(包括用户配置文件)。因此,如果我刷新页面(表单变为空白),导航到主屏幕,然后返回个人资料页面,它会再次填写表单,而无需我在问题中输入任何带有解决方案的内容。只是我还没有完全理解它,无法弄清楚如何让它在页面刷新后进行“获取”。【参考方案3】:

因为这只是一个时间问题:

我建议您将数据值绑定到您所在州的观察者。 每次状态更新时,您的组件都会简单地监听并相应地更新您的数据。

export default 
  data() 
    return 
      name: "",
      title: "",
      showSuccess: false,
    ;
  ,
  computed: 
    ...mapState(["userProfile"]),
  ,
  watch: 
    userProfile: 
      handler( name, title ) 
        this.name = name;
        this.title = title;
      ,
      deep: true, // deep is to listen to objects properly
      immediate: true // immediate so the watcher triggers right away when the component is mounted
    
  ,
  methods: 
    updateProfile() 
      this.$store.dispatch("updateProfile", 
        name: this.name !== "" ? this.name : this.userProfile.name,
        title: this.title !== "" ? this.title : this.userProfile.title,
      );
      
            /*  I just wouldn't reset the values here since they'll be updated in the watcher
      this.name = "";
      this.title = ""; */

      this.showSuccess = true;

      setTimeout(() => 
        this.showSuccess = false;
      , 2000);
    ,
  ,
;

【讨论】:

以上是关于Vuex 从状态加载现有表单数据以进行编辑的主要内容,如果未能解决你的问题,请参考以下文章

Vuejs 2,VUEX,编辑数据时的数据绑定

Vuex 状态随突变而变化 - apollo graphql 查询

Vue.js - 从 API 获取数据并在 Vuex 状态更改时重新加载组件

我应该如何构建多用途 Vue 组件以从不同的 Vuex 路径加载数据?

如何在 Vuex 中通过 POST 请求发送状态数据?

Vuetify 使用带有 Vuex 的 API 的外部数据的数据表