javascript - React JS - 如何在更新状态 [Hooks] 之前防止渲染

标签 javascript reactjs react-hooks

我有一个从 API 获取数据以向用户显示一些详细信息的组件:

const ItemDetail = ({match}) => {
    const [item, setItem] = useState(null);

    useEffect(() => {
        const abort = new AbortController();
        fetchItem(abort);

        return function cleanUp(){
            abort.abort();
        }
    },[]);

    const fetchItem = async (abort) => {
        const data = await fetch(`https://fortnite-api.theapinetwork.com/item/get?id=${match.params.id}`, {
            signal: abort.signal
        });

        const fetchedItem = await data.json();
        setItem(fetchedItem.data.item);
    }

    return (
        <h1 className="title">{item.name}</h1>
    );
}

export default ItemDetail;

但是当导航到达该组件时,控制台显示错误无法访问未定义的名称,可能是因为状态尚未更新。

检查项目是否正确,如果尚未更新则返回空值?像这样:

if(!item) return null;

return (
        <h1 className="title">{item.name}</h1>
);

或者在那种情况下应该更好地使用由 React.Component 扩展的类并正确处理其生命周期?

最佳答案

您可以通过以下两种方式之一处理此问题:

  1. 让组件以“正在加载”状态呈现自身,或者

  2. 在您拥有数据之前不要创建组件——例如,将获取操作移至其父级,并且仅在父级具有渲染它的数据(您将其作为 props 传递)后才创建组件。 (lifting state up的一般原理的具体例子。)

因为这很常见,您可能想要编写一个可以重用的钩子(Hook),而不是每次都必须重新编写逻辑。例如,这里有一个 useFetchJSON 示例,它使用 fetch 来获取和解析您可能在 child 中使用的一些 JSON:

function useFetchJSON(url, init, deps) {
    // Allow the user to leave off `init` even if they include `deps`
    if (typeof deps == "undefined" && Array.isArray(init)) {
        deps = init;
        init = undefined;
    }
    if (!deps) {
        console.warn(
            "Using `useFetchJSON` with no dependencies array means you'll " +
                "re-fetch on EVERY render. You probably want an empty dependency " +
                "array instead."
        );
    }
    const [loading, setLoading] = useState(true);
    const [data, setData] = useState(undefined);
    const [error, setError] = useState(undefined);

    useEffect(() => {
        setLoading(true);
        setData(undefined);
        setError(undefined);
        fetch(url, init)
            .then((response) => {
                if (!response.ok) {
                    throw new Error(`HTTP error ${response.status}`);
                }
                return response.json();
            })
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, deps);

    return [loading, data, error];
}

用法:

const [loading, data, error] = useFetchJSON(/*...*/, []);

然后使用loading(加载期间设置的标志),data(加载的数据,如果它不是undefined) , 或错误(发生的错误,如果它不是 undefined):

实例:

const { useState, useEffect } = React;

const fakeData = [
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },
  {
    "userId": 1,
    "id": 3,
    "title": "fugiat veniam minus",
    "completed": false
  },
  {
    "userId": 1,
    "id": 4,
    "title": "et porro tempora",
    "completed": true
  },
  {
    "userId": 1,
    "id": 5,
    "title": "laboriosam mollitia et enim quasi adipisci quia provident illum",
    "completed": false
  }
];

function fakeFetch(url, init) {
    return new Promise((resolve) => {
        // A fake delay to simulate network
        setTimeout(() => {
            resolve({
                ok: true,
                status: 200,
                json() {
                    return Promise.resolve(fakeData);
                }
            });
        }, 3000);
    });
}

function useFetchJSON(url, init, deps) {
    // Allow the user to leave off `init` even if they include `deps`
    if (typeof deps == "undefined" && Array.isArray(init)) {
        deps = init;
        init = undefined;
    }
    if (!deps) {
        console.warn(
            "Using `useFetchJSON` with no dependencies array means you'll " +
                "re-fetch on EVERY render. You probably want an empty dependency " +
                "array instead."
        );
    }
    const [loading, setLoading] = useState(true);
    const [data, setData] = useState(undefined);
    const [error, setError] = useState(undefined);

    useEffect(() => {
        setLoading(true);
        setData(undefined);
        setError(undefined);
        fakeFetch(url, init)
            .then((response) => {
                if (!response.ok) {
                    throw new Error(`HTTP error ${response.status}`);
                }
                return response.json();
            })
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, deps);

    return [loading, data, error];
}

// Usage:

function Example() {
    const [loading, todos, error] = useFetchJSON("https://jsonplaceholder.typicode.com/todos/", []);

    return (
        <div>
            {loading && <em>Loading todos...</em>}
            {error && <strong>Error loading todos: {String(error)}</strong>}
            {todos && (
                <ul>
                    {todos.map((todo) => (
                        <li key={todo.id}>{todo.title}</li>
                    ))}
                </ul>
            )}
        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

关于javascript - React JS - 如何在更新状态 [Hooks] 之前防止渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57203298/

相关文章:

javascript - 如何从轮子中获取 Canvas 的值(value)

javascript - 您应该在构造函数中定义组件状态的所有属性吗?

reactjs - 使用 useHistory、useLocation Hook 的 Hook 调用无效

javascript - 仅搜索 ui.bootstrap typeahead 中的特定字段

javascript - Bootstrap 卡不能一直 flex 到全宽

reactjs - 我是否错误地安装了 create-react-app?

javascript - 在javascript字符串中添加html标签

reactjs - 应用程序 useEffect 未通过 react 路由器触发

javascript - 如何在不包含依赖项的情况下访问 useEffect 中的最新状态(更新状态时)

javascript - 我可以将 javascript 与 bottle(框架)一起使用吗?