简体   繁体   English

使用 Firebase arrayUnion 更新对象中的数据

[英]Updating data in an Object using Firebase arrayUnion

I am looking to update data in an object without changing the index of the object within the array it is contained.我希望更新对象中的数据而不更改它包含的数组中对象的索引。 As it currently stands, the code removes the current object from the array and then applies array Union to update the array but pushes the component to the end of the array but I am looking to just update the data without the component losing its index.按照目前的情况,代码从数组中删除当前对象,然后应用数组联合来更新数组,但将组件推送到数组的末尾,但我希望只更新数据而不会丢失组件的索引。 This is the code I am currently working with, I looked through the Firebase docs to see if there was a way to just update the component but couldn't find anything if anyone could point me in the right direction, please.这是我目前正在使用的代码,我查看了 Firebase 文档,看看是否有办法只更新组件,但如果有人能指出我正确的方向,我找不到任何东西。

      await firestore.update(project, {
        pages: firestore.FieldValue.arrayRemove(page),
      });

      await firestore.update(project, {
        pages: firestore.FieldValue.arrayUnion(newPage),
      });

arrayUnion adds new items to the array and arrayRemove removes items from an array. arrayUnion向数组添加新项, arrayRemove从数组中删除项。 There isn't any way to update an existing item in array directly.没有任何方法可以直接更新数组中的现有项目。

You would have to fetch the document, manually add/update the item at relevant index and then update the whole array back to the document.您必须获取文档,手动添加/更新相关索引处的项目,然后将整个数组更新回文档。

Unfortunately there is no field transform to replace a value like this:不幸的是,没有字段转换来替换这样的值:

firestore.FieldValue.arrayReplace(page, newPage);

Storing arrays and making changes by index in remote databases is generally discouraged.通常不鼓励在远程数据库中存储数组并按索引进行更改。 This older Firebase blog post covers some of the reasons why even though it was written with the Firebase Realtime Database in mind.这篇较旧的 Firebase 博客文章涵盖了一些原因,即使它是在考虑 Firebase 实时数据库的情况下编写的。

If the order of that array is important, you have two options:如果该数组的顺序很重要,您有两个选择:

  • fetch the array, mutate it, and then write it back.获取数组,对其进行变异,然后将其写回。 (simple) (简单的)
  • fetch the array, find the relevant index, update that index only.获取数组,找到相关索引,仅更新该索引。 (difficult) (难的)

To achieve the first result, you would make use of a transaction to find the previous value and replace it:要获得第一个结果,您将使用事务来查找先前的值并替换它:

const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId");

function replacePage(oldPage, newPage) {
  return db.runTransaction(async (t) => {
    const snapshot = await t.get(projectDocRef);

    if (!snapshot.exists) {
      // no previous data, abort.
      return "aborted";
    }

    const pagesArray = snapshot.get("pages");

    const index = pagesArray.findIndex((page) => page === oldPage);

    if (index === -1)
      return "not-found";

    pagesArray[index] = newPage;

    await t.set(projectDocRef, { pages: pagesArray }, { merge: true });

    return "replaced";
  });
}

replacePage("index", "shop")
  .then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
  .catch((err) => console.error('failed: ', err));

Note: Anything beyond this point is educational.注意:超出这一点的任何内容都是教育性的。 There are many issues with this approach at scale.这种方法在规模上存在许多问题。

Because Firestore doesn't support array entry replacement by index, you'll need to implement a way to update an index using something Firestore understands - maps.由于 Firestore 不支持按索引替换数组条目,因此您需要实现一种使用 Firestore 理解的内容更新索引的方法 - 映射。 Using some FirestoreDataConverter trickery, you can use the converter to serialize your array as a map when you write it to Cloud Firestore and deserialize it back to an array when you read it.使用一些FirestoreDataConverter技巧,您可以在将数组写入 Cloud Firestore 时使用转换器将其序列化为映射,并在读取时将其反序列化回数组。 The major trade-off here is in how you will be able to query your data.这里的主要权衡在于您将如何查询您的数据。 You will be able to perform queries by index (such as where('pages.0', '==', 'shop') ) but you'll lose the ability to use array-contains queries (such as where('pages', 'array-contains', 'shop') ).您将能够按索引执行查询(例如where('pages.0', '==', 'shop') ),但您将无法使用array-contains查询(例如where('pages', 'array-contains', 'shop') )。

First, you need to define the converter:首先,您需要定义转换器:

// const obj = {};
// setNestedProperty(obj, ["a", "b", "c"], true)
// obj is now { "a": { "b": { "c": true } } }
function setNestedProperty(originalObj, pathPropsArray, val) {
  const props = pathPropsArray.slice(0, -1);
  const lastProp = pathPropsArray[pathPropsArray.length-1];
  const parent = props.reduce((obj, p) => obj[p] ? obj[p] : (obj[p] = {}), originalObj);
  parent[lastProp] = val;
}

const pagesArrayConverter = {
  toFirestore(data) {
    if (data.pages !== undefined) {
      // step 1) convert array to map
      const pagesAsMap = {};
      data.pages.forEach((page, index) => {
        if (page !== undefined) {
          pagesAsMap[index] = page;
        }
      });
      data.pages = pagesAsMap;

      // step 2) if there are any mutations to "pages"
      //         while you are changing it, make the
      //         changes now before uploading to Firestore
      Object.keys(data)
        .filter(k => k.startsWith("pages."))
        .forEach(k => {
          const nestedValue = data[k];
          data[k] = undefined;
          delete data[k];
          setNestedProperty(pagesAsMap, k.slice(6).split("."), nestedValue);
        });
    }
    return data;
  },

  fromFirestore(snapshot, options) {
    const data = snapshot.data(options);
    if (data.pages !== undefined) {
      const pagesAsArray = [];
      Object.entries(data.pages)
        .map(([index, page]) => pagesAsArray[index] = page);
      // `pagesAsArray` may have empty elements, so we need
      // to fill in the gaps with `undefined`:
      data.pages = Array.from(pagesAsArray);
    }
    return data;
  }
};

Which you would then attach to a query/reference like this:然后您将附加到这样的查询/参考:

const db = firebase.firestore();
const projectDocRef = db.doc("projects/projectId")
  .withConverter(pagesArrayConverter)

If you already know that the previous value has an index of 2, you can just use:如果您已经知道前一个值的索引为 2,则可以使用:

await projectDocRef.set({ "pages.2": newPage }, { merge: true });

If you need to find it like before, you can use a transaction:如果你需要像以前一样找到它,你可以使用一个事务:

function replacePage(oldPage, newPage) {
  return db.runTransaction(aysnc (t) => {
    const snapshot = await t.get(projectDocRef);

    if (!snapshot.exists) {
      // no previous data, abort.
      return "missing";
    }

    const data = snapshot.data();
    // data is a { pages: Page[] }
  
    const index = data.pages.findIndex((page) => page === oldPage);

    if (index === -1)
      return "not-found";

    await t.set(projectDocRef, { ["pages." + oldIndex]: newPage }, { merge: true });
    return "replaced";
  });
}

replacePage("index", "shop")
  .then((result) => console.log("Page replacement was " + (result === "replaced" ? "" : " not") + " successful"))
  .catch((err) => console.error('failed: ', err));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM