typescript - 从已解析的 Promise 返回 json 对象并在加载后分配它

标签 typescript react-native

我在类中有一个方法,它返回 JSON 对象,然后在类之外的函数中创建此类的实例,调用其中一个方法并期望读取返回的 JSON 值。但发生的情况是,分配给该方法的变量在方法收集数据之前就被调用,导致未定义。我尝试过短路(&&),这以前对我有用,但这次不行。我还尝试使用 hooks 而不是 let 并返回它,这最终对我有用,但由于某种原因,它正在循环,就好像我把它放在 while(1) 中一样。所有方法均取自 SQLite 教程并进行了修改。

function Screen() {
    const a = new App();
    let prodReturned = null;
    prodReturned = a.getProducts();
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>text: {prodReturned && JSON.stringify(prodReturned)}</Text>
        </View>
    );
}
export default class App extends Component {

getProducts() {
        let products=[];
        this.listProduct().then((data) => {
            products = data;
            console.log(typeof(data),data,'this returns object type and correct json form')
            return products;
        }).catch((err) => {
            console.log(err);   
        })
}

listProduct() {
        return new Promise((resolve) => {
            const favdrinks = [];
            this.initDB().then((db) => {
                db.transaction((tx) => {
                    tx.executeSql('SELECT p.favorites FROM drinks p', []).then(([tx, results]) => {
                        console.log("Query completed");
                        var len = results.rows.length;
                        for (let i = 0; i < len; i++) {
                            let row = results.rows.item(i);
                            console.log(`Drinks favorited: ${row.favorites}`)
                            const { favorites } = row;
                            favdrinks.push({
                                favorites
                            });
                        }
                        console.log(favdrinks);
                        resolve(favdrinks);
                    });
                }).then((result) => {
                    this.closeDatabase(db);
                }).catch((err) => {
                    console.log(err);
                });
            }).catch((err) => {
                console.log(err);
            });
        });
}

}

如何确保在准备好后调用该方法并将返回值分配给变量?

最佳答案

React 组件必须同步渲染。这就是 React 的设计方式;它不能也永远不会改变。您听说过的任何异步渲染方式都是一种抽象,它会缓存旧的渲染结果并同步返回它们。因此,您将永远无法执行代码指示的操作。

但是,我怀疑您实际上并不关心该函数是否在数据加载之前被调用(这是您的代码所建议的),而是您希望调用它并显示加载菜单,直到异步操作完成并给你它的结果。这是 React 中的一个常见问题,可以通过稍后设置初始空状态来解决。

import React, { useState } from 'react';
function MyComponent() {
    // Since you're using TypeScript, you can type this by
    // setting the generic parameter
    // e.g. useState<Product[] | null>(null);
    const [products, setProducts] = useState(null);
    listProducts().then(function(loadedProducts) {
        setProducts(loadedProducts);
    });
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>text: {products !== null && JSON.stringify(products)}</Text>
        </View>
    );
}

为了使其更像 ES6:

import React, { useState } from 'react';
const MyComponent = () => {
    const [products, setProducts] = useState(null);
    listProducts().then(setProducts);
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>text: {products && JSON.stringify(products)}</Text>
        </View>
    );
}

这样做只有一个问题:每次调用 MyComponent 时,都会加载数据。在某些情况下您可能需要这样做,但在绝大多数情况下,您只想在发生变化时重新加载产品。这就是 useEffect Hook 的用武之地。它接受一个仅在依赖项列表发生更改时才会被调用的函数。

useEffect(() => {
    // This is called once on the first render and only 
    // called again whenever either dep1 or dep2 changes

    // Note that a change in dep1 or dep2 does not necessarily
    // mean your component will be called again (i.e. be
    // rerendered). That only happens if dep1 or dep2 came
    // from a useState hook, because to change a value from
    // a useState hook, you must call the setter, which tells
    // React to rerender.
    console.log(dep1 + dep2);
}, [dep1, dep2]);

如果将当前分钟添加到依赖项列表中,则产品列表最多每分钟更新一次。

useEffect(() => {
  listProducts().then(setProducts);
}, [
    // Time in milliseconds rounded down to the minute
    Math.floor(Date.now() / (60 * 1000))
]);

如果您只想调用该函数一次,请将依赖项列表清空。如果您想在每次渲染后调用它,则根本不要指定依赖项列表(即 useEffect(callback))。了解更多 here .

其他一些事情:您的 App 类毫无意义。您可能已经习惯了 Java 和 C# 等语言的面向对象编程,但现代 JavaScript 尽可能合理地避免使用类。此外,您不需要扩展 React.Component 因为您不需要使用 React 来渲染该类。我建议将函数移出类。此外,您似乎不确定 Promise 是如何工作的。它们是异步调用的,回调是在封闭函数完成后调用的,除非您使用 async/await。我会为你重构这个,但你真的不应该在没有基础知识的情况下接受如此困难的事情。试试this Promise guide首先,然后了解如何 async/await make it easy to avoid infinite .thens .

const getProducts = async () => {
    const data = await listProducts();
    // typeof isn't a function
    console.log(typeof data, data);
}

const listProducts = async () => {
    // Create the initDB() function the way I did this
    const db = await initDB();
    const favdrinks = await new Promise((resolve, reject) => {
        db.transaction(async tx => {
            const [tx, results] = await tx.executeSql(
                'SELECT p.favorites FROM drinks p',
                []
            );
            const favdrinks = [];
            console.log("Query completed");
            var len = results.rows.length;
            for (let i = 0; i < len; i++) {
                let row = results.rows.item(i);
                console.log(`Drinks favorited: ${row.favorites}`)
                const { favorites } = row;
                favdrinks.push({
                    favorites
                });
            }
            console.log(favdrinks);
            resolve(favdrinks);
        })
    });
    // Refactor this function as well
    await closeDatabase(db);
    return favdrinks;
}

把它们放在一起:

import React, { useState, useEffect } from 'react';
const listProducts = async () => {
    const db = await initDB();
    return new Promise((resolve, reject) => {
        db.transaction(async tx => {
            const [tx, results] = await tx.executeSql(
                'SELECT p.favorites FROM drinks p',
                []
            );
            const favdrinks = [];
            var len = results.rows.length;
            for (let i = 0; i < len; i++) {
                let row = results.rows.item(i);
                const { favorites } = row;
                favdrinks.push({
                    favorites
                });
            }
            resolve(favdrinks);
        })
    });
    await closeDatabase(db);
    return favdrinks;
}

const MyComponent = () => {
    const [products, setProducts] = useState(null);
    useEffect(() => {
        listProducts().then(setProducts);
    }, []); // empty dependency list means never update
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Text>text: {products && JSON.stringify(products)}</Text>
        </View>
    );
}

编辑:看到您对另一个答案的评论,您无法删除类逻辑。我认为您可能误解了您正在使用的任何框架的要求,但如果您严重无法避免它,您应该在 useEffect 回调中创建您正在使用的类的对象。

关于typescript - 从已解析的 Promise 返回 json 对象并在加载后分配它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65459599/

相关文章:

javascript - 有没有像 "correct"这样的东西用 React hooks 和 Typescript 定义状态?

特定组件中的 Angular 2 声明

javascript - 下划线 where es6/typescript 替代

javascript - React Native 将数据从子组件传递到父组件

ios - 即使在终止并重新启动应用程序后,如何在 native iOS 中下载音频文件?

android - APK 大小是否受到 React Native 中未使用的包的影响?

javascript - Firebase 响应具有 HTML 标签

javascript - 在这种情况下,将对象引用传递到 Date.toLocaleString() 中,TypeScript linter 到底要求什么?

android - 在 docker 容器中运行 Android 模拟器

angular - (Angular 5) 错误 : control. registerOnChange 不是函数