从Vuex存储的本地副本更新数据

问题描述 投票:1回答:1

我正在实现一个用户配置文件编辑页面,该页面最初包含从vuex存储中加载的数据。然后,用户可以自由编辑他的数据并将其最终存储在商店中。

由于用户还可以单击取消按钮以恢复到其原始状态,所以我决定为从商店中获取的用户数据创建一个“本地”视图副本。这些数据将保留在视图中,并且当用户按保存时,它们将被保存在商店中。

视图如下:

<template class="user-profile">
  <v-form>
    <template v-if="profile.avatar">
      <div class="text-center">
        <v-avatar width="120" height="120">
          <img
            :src="profile.avatar"
            :alt="profile.firstname"
          >
        </v-avatar>
      </div>
    </template>
    <div class="text-center mt-4">
      <v-btn
        color="primary"
        dark
        @click.stop="showImageDialog=true"
      >
        Change Image
      </v-btn>
    </div>
    <v-row>
      <v-col>
        <v-text-field
          label="First name"
          single-line
          disabled
          v-model="profile.firstname"
        ></v-text-field>
      </v-col>
      <v-col>
        <v-text-field
          label="Last name"
          single-line
          disabled
          v-model="profile.lastname"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-text-field
      label="Email"
      single-line
      v-model="profile.email"
    ></v-text-field>
    <v-text-field
      id="title"
      label="Title"
      single-line
      v-model="profile.title"
    ></v-text-field>
    <v-textarea
      no-resize
      clearable
      label="Biography"
      v-model="profile.bio"
    ></v-textarea>
    <v-dialog
      max-width="500"
      v-model="showImageDialog"
    >
      <v-card>
        <v-card-title>
          Update your profile picture
        </v-card-title>
        <v-card-text>
          <v-file-input @change="setImage" accept="image/*"></v-file-input>
          <template v-if="userAvatarExists">
            <vue-cropper
              ref="cropper"
              :aspect-ratio="16 / 9"
              :src="profile.avatar"
            />
          </template>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn
            color="green darken-1"
            text
            @click="showImageDialog=false"
          >
            Cancel
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="uploadImage"
          >
            Upload
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    <div class="mt-8">
      <v-btn @click="onUpdateUser">Update</v-btn>
    </div>
  </v-form>
</template>

<script>
  import { mapGetters, mapActions } from 'vuex'
  import VueCropper from 'vue-cropperjs';
  import 'cropperjs/dist/cropper.css';

  export default {
    components: { VueCropper},

    mounted() {
      this.profile = this.getUserProfile ? this.getUserProfile : {}
    },
    data() {
      return {
        profile: {},
        avatar: null,
        userAvatarExists: false,
        showImageDialog: false,
      }
    },
    watch: {
      getUserProfile(newData){
        this.profile = newData;
      },
      deep: true
    },
    computed: {
      ...mapGetters({
        getUserProfile: 'user/me',
      })
    },
    methods: {
      ...mapActions({
        storeAvatar: 'user/storeAvatar',
        updateUser: 'user/update'
      }),
      onUpdateUser() {
        const data = {
          id: this.profile.id,
          email: this.profile.email,
          title: this.profile.title,
          bio: this.profile.bio,
          avatar: this.profile.avatar,
        }
        this.updateUser(data)
      },
      uploadImage() {
        this.$refs.cropper.getCroppedCanvas().toBlob((blob => {
          this.storeAvatar(blob).then((filename => {
            this.profile.avatar = filename.data
            this.$refs.cropper.reset()
          }));
          this.showImageDialog = false
        }));
      },
      setImage(file) {
        this.userAvatarExists = true;
        if (file.type.indexOf('image/') === -1) {
          alert('Please select an image file');
          return;
        }
        if (typeof FileReader === 'function') {
          const reader = new FileReader();
          reader.onload = (event) => {
            this.$refs.cropper.replace(event.target.result);
          };
          reader.readAsDataURL(file);
        } else {
          alert('Sorry, FileReader API not supported');
        }
      }
    }
  }
</script>

问题/问题

  1. 您可以从代码中看到,用户更改了个人资料后图片,则图像应基于v-if="profile.avatar"。问题是在profile.avatar功能中设置uploadImage,模板看不到此更改,因此不会渲染任何图像。但是,如果我更改代码,使profile.avatar变为只是avatar(不再位于profile对象中),模板开始查看更改并渲染图像正确地。为什么这样?它与制作一个从商店复制watch功能?
  2. 一般来说,将配置文件保留为本地文件是一种好方法查看状态,还是应该将其存储在vuex存储中,即使它只是临时数据吗?
  3. 您可以在mounted功能中看到,我正在设置profile基于getUserProfile吸气剂的值。这是因为切换时似乎没有再次调用watch功能路线。还有其他方法吗?
vue.js vuejs2 vuex
1个回答
0
投票

该问题归因于数据属性的反应性

您已使用个人资料作为对象,默认情况下它没有任何属性,例如头像或名字,只是空的]]

在vue js中,如果要声明对象,则声明中的关键内容只是反应性的一部分。配置文件中的键一旦更改,它就会重新渲染模板

但是您仍然可以使用$ set将新属性添加到数据属性对象中>

在您声明的数据中说个人资料:{}

如果要在运行时使用中将头像设置为新的反应性属性

this.$set(this.profile, key, value)

this.$set(this.profile, avatar, imageData)

在上面的代码中,setIuploadImage函数

uploadImage() {
        var self = this;
        self.$refs.cropper.getCroppedCanvas().toBlob((blob => {
          self.storeAvatar(blob).then((filename => {
            self.$set(self.profile, "avatar", filename.data)
            self.$refs.cropper.reset()
          }));
          self.showImageDialog = false
        }));
      },

这在vuejs中的arrow函数中不起作用,因此仅将this保留在另一个变量“ self”中,并在arrow函数中使用]]

也已装入函数中,如果this.getUserProfile返回空对象

,则按照javascript,空对象始终是真实的,直接将对象分配给配置文件不会使对象成为反应对象
mounted() {
      this.profile = this.getUserProfile ? this.getUserProfile : {}
    },

上面的代码可以写成

mounted() {
  if (this.getUserProfile && Object.keys(this.getUserProfile).length) {
    var self = this;
    Object.keys(this.getUserProfile).map(key => {
      self.$set(self.profile, key, self.getUserProfile[key])
    });
  } else {
    this.profile = {};
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.